> ## Documentation Index
> Fetch the complete documentation index at: https://aitutorial.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Prompting Techniques

> Apply advanced techniques to improve the performance of your prompts

export const QuizQuestion = ({question, options, answer, explanation}) => {
  const [selected, setSelected] = useState(null);
  const [revealed, setRevealed] = useState(false);
  const handleSelect = index => {
    if (revealed) return;
    setSelected(index);
    setRevealed(true);
  };
  const isCorrect = selected === answer;
  const getOptionClass = i => {
    const classes = ['quiz-option'];
    if (revealed) {
      classes.push('quiz-option-disabled');
      if (i === answer) classes.push('quiz-option-correct'); else if (i === selected && !isCorrect) classes.push('quiz-option-wrong');
    }
    return classes.join(' ');
  };
  return <div className="quiz-card">
      <p className="quiz-question">{question}</p>
      <div className="quiz-options">
        {options.map((option, i) => <button key={i} onClick={() => handleSelect(i)} className={getOptionClass(i)}>
            <span className="quiz-letter">{String.fromCharCode(65 + i)}</span>
            {option}
          </button>)}
      </div>
      {revealed && <div className={`quiz-feedback ${isCorrect ? 'quiz-feedback-correct' : 'quiz-feedback-wrong'}`}>
          <strong>{isCorrect ? 'Correct!' : 'Incorrect.'}</strong> {explanation}
        </div>}
    </div>;
};

export const Quiz = ({title = "Check Your Understanding", children}) => {
  return <div style={{
    marginTop: '24px'
  }}>
      <div className="quiz-title">{title}</div>
      {children}
    </div>;
};

export const CodeEditor = ({file = 'src/hello_world.ts', lines, title = 'Code Example', repo = 'ai-tutorial/typescript-examples', height = '650px', functionName, theme: userTheme}) => {
  const STORAGE_KEY = 'openai_api_key';
  const GEMINI_STORAGE_KEY = 'gemini_api_key';
  const ANTHROPIC_STORAGE_KEY = 'anthropic_api_key';
  const PROVIDER_STORAGE_KEY = 'llm_playground_provider';
  if (!functionName) {
    console.warn('CodeEditor: functionName parameter is required');
  }
  const hasCreatedEnvRef = useRef(false);
  const vmRef = useRef(null);
  const [isMaximized, setIsMaximized] = useState(false);
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [isStuck, setIsStuck] = useState(false);
  const [iframeKey, setIframeKey] = useState(0);
  const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
  const [apiKey, setApiKey] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isValidating, setIsValidating] = useState(false);
  const [detectedTheme, setDetectedTheme] = useState('dark');
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const checkTheme = () => {
      const isDark = document.documentElement.classList.contains('dark');
      setDetectedTheme(isDark ? 'dark' : 'light');
    };
    checkTheme();
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const theme = userTheme || detectedTheme;
  const [selectedProvider, setSelectedProvider] = useState(() => {
    if (typeof window === 'undefined') return 'gemini';
    return localStorage.getItem(PROVIDER_STORAGE_KEY) || 'gemini';
  });
  const isApiKeyConfigured = () => {
    const openaiKey = localStorage.getItem(STORAGE_KEY);
    const geminiKey = localStorage.getItem(GEMINI_STORAGE_KEY);
    const anthropicKey = localStorage.getItem(ANTHROPIC_STORAGE_KEY);
    return openaiKey !== null && openaiKey.trim().length > 0 || geminiKey !== null && geminiKey.trim().length > 0 || anthropicKey !== null && anthropicKey.trim().length > 0;
  };
  const dispatchApiKeyChanged = () => {
    if (typeof window !== 'undefined' && window.dispatchEvent) {
      window.dispatchEvent(new CustomEvent('apiKeyChanged', {
        detail: {
          configured: isApiKeyConfigured()
        }
      }));
    }
  };
  const saveApiKey = apiKey => {
    if (apiKey && apiKey.trim()) {
      const trimmedKey = apiKey.trim();
      localStorage.setItem(STORAGE_KEY, trimmedKey);
      dispatchApiKeyChanged();
      return true;
    }
    return false;
  };
  const buildEnvContent = () => {
    const openaiKey = localStorage.getItem(STORAGE_KEY)?.trim();
    const geminiKey = localStorage.getItem(GEMINI_STORAGE_KEY)?.trim();
    const anthropicKey = localStorage.getItem(ANTHROPIC_STORAGE_KEY)?.trim();
    if (!openaiKey && !geminiKey && !anthropicKey) {
      return `OPENAI_MODEL=gpt-4.1-nano
OPENAI_API_KEY=sk-mock-key-1234567890abcdef
GEMINI_MODEL=gemini-2.5-flash-lite
GOOGLE_GENERATIVE_AI_API_KEY=
GOOGLE_API_KEY=
ANTHROPIC_API_KEY=
AI_PROVIDER=openai
# API key not found in browser storage
# To configure your API key:
# 1. For Gemini (free): Go to https://aistudio.google.com/apikey
# 2. For OpenAI: Go to https://platform.openai.com/api-keys
# 3. For Claude: Go to https://console.anthropic.com/settings/keys
# 4. Enter it in the configuration form above this editor
# 5. The .env file will be automatically updated with your key`;
    }
    const envLines = ['# Using the API key(s) you configured. This file will be created when the dialog is loaded.'];
    if (openaiKey) {
      envLines.push(`OPENAI_MODEL=gpt-4.1-nano`);
      envLines.push(`OPENAI_API_KEY=${openaiKey}`);
    }
    if (geminiKey) {
      envLines.push(`GEMINI_MODEL=gemini-2.5-flash-lite`);
      envLines.push(`# Vercel AI SDK uses GOOGLE_GENERATIVE_AI_API_KEY, LangChain uses GOOGLE_API_KEY`);
      envLines.push(`GOOGLE_GENERATIVE_AI_API_KEY=${geminiKey}`);
      envLines.push(`GOOGLE_API_KEY=${geminiKey}`);
    }
    if (anthropicKey) {
      envLines.push(`ANTHROPIC_API_KEY=${anthropicKey}`);
    }
    const provider = anthropicKey ? 'anthropic' : geminiKey ? 'gemini' : 'openai';
    envLines.push(`AI_PROVIDER=${provider}`);
    return envLines.join('\n');
  };
  const updateEnvFile = async vm => {
    if (!vm) return;
    try {
      await vm.applyFsDiff({
        create: {
          'env/.env': buildEnvContent(),
          'env/run.conf': `file=${file}`
        },
        destroy: []
      });
      hasCreatedEnvRef.current = true;
    } catch (error) {
      console.error('Failed to write env files:', error);
      hasCreatedEnvRef.current = false;
    }
  };
  useEffect(() => {
    if (!isApiKeyConfigured()) {
      setShowApiKeyDialog(true);
    }
    const handleApiKeyChanged = () => {
      if (isApiKeyConfigured()) {
        setShowApiKeyDialog(false);
      }
    };
    if (typeof window !== 'undefined') {
      window.addEventListener('apiKeyChanged', handleApiKeyChanged);
      return () => {
        window.removeEventListener('apiKeyChanged', handleApiKeyChanged);
      };
    }
  }, []);
  const validateApiKey = async (key, provider) => {
    try {
      const urls = {
        gemini: 'https://generativelanguage.googleapis.com/v1beta/models?key=' + encodeURIComponent(key.trim()),
        openai: 'https://api.openai.com/v1/models',
        anthropic: 'https://api.anthropic.com/v1/models'
      };
      const headerMap = {
        gemini: {
          'Content-Type': 'application/json'
        },
        openai: {
          'Authorization': `Bearer ${key.trim()}`,
          'Content-Type': 'application/json'
        },
        anthropic: {
          'x-api-key': key.trim(),
          'anthropic-version': '2023-06-01',
          'Content-Type': 'application/json'
        }
      };
      const url = urls[provider];
      const headers = headerMap[provider];
      const response = await fetch(url, {
        method: 'GET',
        headers
      });
      if (response.ok) {
        return {
          valid: true
        };
      } else if (response.status === 401 || response.status === 403) {
        return {
          valid: false,
          error: 'Invalid API key. Please check your key and try again.'
        };
      } else if (response.status === 429) {
        return {
          valid: false,
          error: 'Rate limit exceeded. Please try again later.'
        };
      } else {
        const errorData = await response.json().catch(() => ({}));
        return {
          valid: false,
          error: errorData.error?.message || `API request failed with status ${response.status}`
        };
      }
    } catch (err) {
      if (err.name === 'TypeError' && err.message.includes('fetch')) {
        return {
          valid: false,
          error: 'Network error. Please check your connection and try again.'
        };
      }
      return {
        valid: false,
        error: err.message || 'Failed to validate API key. Please try again.'
      };
    }
  };
  const handleSkipConfiguration = () => {
    const skipKey = 'sk-<configure-your-key>';
    saveApiKey(skipKey);
    setShowApiKeyDialog(false);
  };
  const handleApiKeySubmit = async e => {
    e.preventDefault();
    setError('');
    setSuccess(false);
    setIsSubmitting(true);
    const providerNames = {
      gemini: 'Gemini',
      openai: 'OpenAI',
      anthropic: 'Claude'
    };
    if (!apiKey || !apiKey.trim()) {
      setError(`Please enter your ${providerNames[selectedProvider]} API key`);
      setIsSubmitting(false);
      return;
    }
    const trimmedKey = apiKey.trim();
    if (selectedProvider === 'openai' && !trimmedKey.startsWith('sk-')) {
      setError('Invalid API key format. OpenAI API keys should start with "sk-"');
      setIsSubmitting(false);
      return;
    }
    if (selectedProvider === 'anthropic' && !trimmedKey.startsWith('sk-ant-')) {
      setError('Invalid API key format. Anthropic API keys should start with "sk-ant-"');
      setIsSubmitting(false);
      return;
    }
    setIsValidating(true);
    setError('');
    const validation = await validateApiKey(trimmedKey, selectedProvider);
    setIsValidating(false);
    if (!validation.valid) {
      setError(validation.error || 'Invalid API key. Please check your key and try again.');
      setIsSubmitting(false);
      return;
    }
    try {
      const storageKeys = {
        gemini: GEMINI_STORAGE_KEY,
        openai: STORAGE_KEY,
        anthropic: ANTHROPIC_STORAGE_KEY
      };
      localStorage.setItem(storageKeys[selectedProvider], trimmedKey);
      localStorage.setItem(PROVIDER_STORAGE_KEY, selectedProvider);
      dispatchApiKeyChanged();
      setSuccess(true);
      setApiKey('');
      setTimeout(() => {
        window.location.reload();
      }, 1000);
    } catch (err) {
      setError(err.message || 'Failed to save API key. Please try again.');
      setIsSubmitting(false);
    }
  };
  const baseFilePath = file || 'src/hello_world.ts';
  let filePath = baseFilePath;
  if (typeof lines === 'string' && lines.trim()) {
    const lineParts = lines.split('-');
    if (lineParts.length === 2) {
      filePath = `${filePath}:L${lineParts[0].trim()}-L${lineParts[1].trim()}`;
    } else {
      filePath = `${filePath}:L${lineParts[0].trim()}`;
    }
  } else if (typeof lines === 'object' && lines.start !== undefined) {
    filePath = lines.end !== undefined ? `${filePath}:L${lines.start}-L${lines.end}` : `${filePath}:L${lines.start}`;
  }
  const stackblitzUrl = `https://stackblitz.com/github/${repo}?file=${encodeURIComponent(filePath)}&embed=1&view=editor&theme=${theme}`;
  const loadSDK = () => {
    return new Promise((resolve, reject) => {
      if (window.StackBlitzSDK || window.stackblitzSDK) {
        resolve(window.StackBlitzSDK || window.stackblitzSDK);
        return;
      }
      if (document.querySelector('script[data-stackblitz-sdk]')) {
        const checkInterval = setInterval(() => {
          if (window.StackBlitzSDK || window.stackblitzSDK) {
            clearInterval(checkInterval);
            resolve(window.StackBlitzSDK || window.stackblitzSDK);
          }
        }, 100);
        setTimeout(() => {
          clearInterval(checkInterval);
          reject(new Error('SDK loading timeout'));
        }, 10000);
        return;
      }
      const script = document.createElement('script');
      script.src = 'https://unpkg.com/@stackblitz/sdk/bundles/sdk.umd.js';
      script.async = true;
      script.setAttribute('data-stackblitz-sdk', 'true');
      script.onload = () => {
        const sdk = window.StackBlitzSDK || window.stackblitzSDK;
        if (sdk) {
          resolve(sdk);
        } else {
          reject(new Error('SDK loaded but not available on window'));
        }
      };
      script.onerror = () => {
        reject(new Error('Failed to load StackBlitz SDK'));
      };
      document.head.appendChild(script);
    });
  };
  const LOAD_TIMEOUT_MS = 10000;
  const iframeElRef = useRef(null);
  const reloadCountRef = useRef(0);
  const handleRetry = () => {
    vmRef.current = null;
    hasCreatedEnvRef.current = false;
    reloadCountRef.current = 0;
    setIsStuck(false);
    setIframeKey(prev => prev + 1);
  };
  const iframeRef = iframe => {
    iframeElRef.current = iframe;
  };
  const connectToVM = async iframe => {
    const sdk = await loadSDK();
    return sdk.connect(iframe);
  };
  const handleIframeLoad = async () => {
    const iframe = iframeElRef.current;
    if (!iframe) return;
    if (reloadCountRef.current > 0) {
      try {
        const vm = await connectToVM(iframe);
        vmRef.current = vm;
        await updateEnvFile(vm);
      } catch (_) {}
      return;
    }
    try {
      if (vmRef.current) return;
      const vm = await Promise.race([connectToVM(iframe), new Promise((_, reject) => setTimeout(() => reject(new Error('connect timeout')), LOAD_TIMEOUT_MS))]);
      vmRef.current = vm;
      await updateEnvFile(vm);
    } catch (error) {
      console.error('Failed to connect to StackBlitz VM:', error);
      if (typeof window !== 'undefined' && window.gtag) {
        window.gtag('event', 'load_refresh_error', {
          event_category: 'stackblitz',
          event_label: file,
          error_message: error.message
        });
      }
      reloadCountRef.current = 1;
      setTimeout(() => {
        setIframeKey(prev => prev + 1);
      }, 2000);
    }
  };
  const isSafari = typeof navigator !== 'undefined' && (/^((?!chrome|android).)*safari/i).test(navigator.userAgent);
  if (isSafari) {
    return <div className="code-editor-dialog-container" style={{
      height: height
    }}>
        <div className="code-editor-dialog-box">
          <h2 className="code-editor-dialog-title">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2">
              <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
              <line x1="12" y1="9" x2="12" y2="13"></line>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
            Browser Not Supported
          </h2>
          <p className="code-editor-dialog-description">
            The interactive code editor is not supported on Safari. Please use <strong>Chrome</strong>, <strong>Edge</strong>, or <strong>Firefox</strong> to run the examples.
          </p>
        </div>
      </div>;
  }
  if (showApiKeyDialog) {
    return <div className="code-editor-dialog-container" style={{
      height: height
    }}>
        <div className="code-editor-dialog-box">
          <h2 className="code-editor-dialog-title">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2">
              <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
              <line x1="12" y1="9" x2="12" y2="13"></line>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
            Configure API Key
          </h2>

          <p className="code-editor-dialog-description">
            All interactive examples execute entirely within your browser environment, ensuring complete security and privacy.
            Your API key is stored locally in your browser's storage and is never transmitted to external servers.
          </p>

          <div className="llm-provider-tabs" style={{
      marginBottom: '16px'
    }}>
            <button type="button" onClick={() => {
      setSelectedProvider('gemini');
      setError('');
      setApiKey('');
    }} className={`llm-provider-tab ${selectedProvider === 'gemini' ? 'llm-provider-tab-active' : ''}`}>
              Gemini <span className="llm-provider-tab-badge">Free</span>
            </button>
            <button type="button" onClick={() => {
      setSelectedProvider('openai');
      setError('');
      setApiKey('');
    }} className={`llm-provider-tab ${selectedProvider === 'openai' ? 'llm-provider-tab-active' : ''}`}>
              OpenAI
            </button>
            <button type="button" onClick={() => {
      setSelectedProvider('anthropic');
      setError('');
      setApiKey('');
    }} className={`llm-provider-tab ${selectedProvider === 'anthropic' ? 'llm-provider-tab-active' : ''}`}>
              Claude
            </button>
          </div>

          {selectedProvider === 'gemini' && <div className="llm-gemini-recommendation" style={{
      marginBottom: '16px'
    }}>
              Gemini offers a generous free tier — great for learning! Get your free API key at{' '}
              <a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener noreferrer" className="code-editor-link">
                aistudio.google.com/apikey
              </a>
            </div>}

          {selectedProvider === 'openai' && <div className="code-editor-info-box">
              <p className="code-editor-info-box-title">
                Don't have an API key?
              </p>
              <p className="code-editor-info-box-text">
                Get one at{' '}
                <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="code-editor-link">
                  platform.openai.com/api-keys
                </a>
              </p>
            </div>}

          {selectedProvider === 'anthropic' && <div className="code-editor-info-box">
              <p className="code-editor-info-box-title">
                Don't have an API key?
              </p>
              <p className="code-editor-info-box-text">
                Get one at{' '}
                <a href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener noreferrer" className="code-editor-link">
                  console.anthropic.com/settings/keys
                </a>
              </p>
            </div>}

          <form onSubmit={handleApiKeySubmit}>
            <div className="code-editor-form-group">
              <label htmlFor="api-key-input" className="code-editor-label">
                {selectedProvider === 'gemini' ? 'Gemini' : 'OpenAI'} API Key
              </label>
              <input id="api-key-input" type="password" value={apiKey} onChange={e => {
      setApiKey(e.target.value);
      setError('');
      setSuccess(false);
    }} placeholder={selectedProvider === 'openai' ? 'sk-...' : 'Gemini API Key'} disabled={isSubmitting} className={`code-editor-input ${error ? 'code-editor-input-error' : ''}`} />
            </div>

            {isValidating && <div className="code-editor-message code-editor-message-info">
                <span className="code-editor-message-icon">⏳</span>
                <span>Validating API key...</span>
              </div>}

            {error && !isValidating && <div className="code-editor-message code-editor-message-error">
                <span className="code-editor-message-icon">⚠️</span>
                <span>{error}</span>
              </div>}

            {success && <div className="code-editor-message code-editor-message-success">
                <span className="code-editor-message-icon">✓</span>
                <span>API key saved successfully! Loading editor...</span>
              </div>}

            <button type="submit" disabled={isSubmitting || isValidating || !apiKey.trim()} className="code-editor-button">
              {isValidating ? 'Validating...' : isSubmitting ? 'Saving...' : 'Save API Key'}
            </button>
          </form>

          <button type="button" onClick={handleSkipConfiguration} disabled={isSubmitting || isValidating} className="code-editor-button-secondary">
            Skip Configuration
          </button>

          <div className="code-editor-footer">
            <p className="code-editor-footer-text">
              Alternatively, you may checkout the source code from{' '}
              <a href="https://github.com/ai-tutorial/typescript-examples" target="_blank" rel="noopener noreferrer" className="code-editor-link code-editor-link-break">
                https://github.com/ai-tutorial/typescript-examples
              </a>
              {' '}and run the examples locally.
            </p>
          </div>
        </div>
      </div>;
  }
  const toggleMaximize = () => setIsMaximized(!isMaximized);
  const toggleCollapse = () => setIsCollapsed(!isCollapsed);
  return <div className={`code-editor-wrapper ${isMaximized ? 'maximized' : ''} ${isCollapsed ? 'collapsed' : ''}`} data-theme={theme}>
      <div className="code-editor-header">
        <div className="code-editor-title">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
            <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
          </svg>
          {title}
        </div>
        <div className="code-editor-controls">
          {!isMaximized && <button className="code-editor-collapse-button" onClick={toggleCollapse} title={isCollapsed ? "Expand" : "Collapse"} type="button">
              <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                {isCollapsed ? <polyline points="6 9 12 15 18 9" /> : <polyline points="6 15 12 9 18 15" />}
              </svg>
            </button>}
          <button className="code-editor-maximize-button" onClick={toggleMaximize} title={isMaximized ? "Minimize" : "Maximize (Focus Mode)"} type="button">
            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              {isMaximized ? <><path d="M4 14h6v6" /><path d="M20 10h-6V4" /><path d="M14 10l7-7" /><path d="M3 21l7-7" /></> : <><path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="M21 3l-7 7" /><path d="M3 21l7-7" /></>}
            </svg>
          </button>
        </div>
      </div>

      {!isCollapsed && <div style={{
    position: 'relative',
    height: isMaximized ? 'auto' : height,
    flex: isMaximized ? 1 : 'none'
  }}>
          <iframe key={iframeKey} ref={iframeRef} onLoad={handleIframeLoad} src={stackblitzUrl} className="code-editor-iframe" style={{
    height: '100%',
    flex: isMaximized ? 1 : 'none'
  }} title={title || 'Code Example'} allow="accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />

          {isStuck && <div className="code-editor-stuck-overlay">
              <div className="code-editor-stuck-box">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2">
                  <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
                  <line x1="12" y1="9" x2="12" y2="13"></line>
                  <line x1="12" y1="17" x2="12.01" y2="17"></line>
                </svg>
                <p>StackBlitz is taking too long to load. This can happen when the repository was recently updated.</p>
                <button type="button" className="code-editor-button" onClick={handleRetry} style={{
    marginTop: '8px'
  }}>
                  Retry
                </button>
              </div>
            </div>}
        </div>}
    </div>;
};

export const LLMPlayground = ({defaultInput = '', defaultModel = 'gpt-4o-mini', defaultTemperature = 0.7, height = '600px', keepInput = false, defaultMode = 'chat', defaultMessages = [], response = '', forceSettingsOpen = false, title, theme: userTheme}) => {
  const {useState, useEffect, useMemo, useCallback, useRef} = React;
  const OPENAI_STORAGE_KEY = 'openai_api_key';
  const GEMINI_STORAGE_KEY = 'gemini_api_key';
  const ANTHROPIC_STORAGE_KEY = 'anthropic_api_key';
  const PROVIDER_STORAGE_KEY = 'llm_playground_provider';
  const SETTINGS_PANEL_KEY = 'llm_playground_settings_open';
  const SETTINGS_PANEL_WIDTH = 220;
  const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
  const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions';
  const ANTHROPIC_API_URL = 'https://api.anthropic.com/v1/messages';
  const MAX_TOKENS = 2000;
  const OPENAI_MODELS = [{
    value: 'gpt-4.1-nano',
    label: 'GPT-4.1 Nano'
  }, {
    value: 'gpt-4.1-mini',
    label: 'GPT-4.1 Mini'
  }, {
    value: 'gpt-4.1',
    label: 'GPT-4.1'
  }, {
    value: 'o4-mini',
    label: 'o4 Mini'
  }, {
    value: 'o3',
    label: 'o3'
  }];
  const GEMINI_MODELS = [{
    value: 'gemini-2.5-flash-lite',
    label: 'Gemini 2.5 Flash Lite'
  }, {
    value: 'gemini-2.5-flash',
    label: 'Gemini 2.5 Flash'
  }, {
    value: 'gemini-2.0-flash',
    label: 'Gemini 2.0 Flash'
  }, {
    value: 'gemini-2.5-pro',
    label: 'Gemini 2.5 Pro'
  }];
  const ANTHROPIC_MODELS = [{
    value: 'claude-haiku-4-5-20251001',
    label: 'Claude 4.5 Haiku'
  }, {
    value: 'claude-sonnet-4-6-20260326',
    label: 'Claude 4.6 Sonnet'
  }, {
    value: 'claude-opus-4-6-20260326',
    label: 'Claude 4.6 Opus'
  }];
  const PROVIDERS = {
    gemini: {
      label: 'Gemini',
      models: GEMINI_MODELS,
      storageKey: GEMINI_STORAGE_KEY,
      apiUrl: GEMINI_API_URL
    },
    openai: {
      label: 'OpenAI',
      models: OPENAI_MODELS,
      storageKey: OPENAI_STORAGE_KEY,
      apiUrl: OPENAI_API_URL
    },
    anthropic: {
      label: 'Claude',
      models: ANTHROPIC_MODELS,
      storageKey: ANTHROPIC_STORAGE_KEY,
      apiUrl: ANTHROPIC_API_URL
    }
  };
  const IconWarningSun = ({size = 14, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className={className}>
      <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
    </svg>;
  const IconLoadingSpinner = ({size = 14, className = '', strokeColor = 'currentColor', strokeWidth = '2', strokeLinecap = 'butt'}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={strokeColor} strokeWidth={strokeWidth} strokeLinecap={strokeLinecap} className={className}>
      <path d="M21 12a9 9 0 11-6.219-8.56"></path>
    </svg>;
  const IconWarningTriangle = ({size = 16, strokeColor = '#f59e0b', className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={strokeColor} strokeWidth="2" className={className}>
      <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
      <line x1="12" y1="9" x2="12" y2="13"></line>
      <line x1="12" y1="17" x2="12.01" y2="17"></line>
    </svg>;
  const IconClose = ({size = 10, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className={className}>
      <line x1="18" y1="6" x2="6" y2="18"></line>
      <line x1="6" y1="6" x2="18" y2="18"></line>
    </svg>;
  const IconPlus = ({size = 12, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className={className}>
      <line x1="12" y1="5" x2="12" y2="19"></line>
      <line x1="5" y1="12" x2="19" y2="12"></line>
    </svg>;
  const IconError = ({size = 14, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className={className}>
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="8" x2="12" y2="12"></line>
      <line x1="12" y1="16" x2="12.01" y2="16"></line>
    </svg>;
  const IconTrash = ({size = 14, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
      <polyline points="3 6 5 6 21 6"></polyline>
      <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
      <line x1="10" y1="11" x2="10" y2="17"></line>
      <line x1="14" y1="11" x2="14" y2="17"></line>
    </svg>;
  const IconSend = ({size = 16, fillColor = '#000', className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill={fillColor} className={className}>
      <path d="M1.101 21.757L23.8 12.028 1.101 2.3l.011 7.912 13.623 1.816-13.623 1.817-.011 7.912z" />
    </svg>;
  const IconCheckmark = ({size = 14, strokeColor = '#22c55e', className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={strokeColor} strokeWidth="2" className={className}>
      <polyline points="9 11 12 14 22 4"></polyline>
      <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
    </svg>;
  const IconXStatus = ({size = 14, strokeColor = '#ef4444', className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={strokeColor} strokeWidth="2" className={className}>
      <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
      <line x1="9" y1="9" x2="15" y2="15"></line>
      <line x1="15" y1="9" x2="9" y2="15"></line>
    </svg>;
  const IconChevron = ({size = 12, isOpen = false, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
      {isOpen ? <polyline points="18 15 12 9 6 15"></polyline> : <polyline points="6 9 12 15 18 9"></polyline>}
    </svg>;
  const IconInfo = ({size = 12, className = ''}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="16" x2="12" y2="12"></line>
      <line x1="12" y1="8" x2="12.01" y2="8"></line>
    </svg>;
  const isAdvancedMode = defaultMode === 'advanced' || defaultMessages && defaultMessages.length > 0;
  const mode = isAdvancedMode ? 'advanced' : 'chat';
  const [input, setInput] = useState(defaultInput);
  const [output, setOutput] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState('');
  const [provider, setProvider] = useState(() => {
    if (typeof window === 'undefined') return 'gemini';
    return localStorage.getItem(PROVIDER_STORAGE_KEY) || 'gemini';
  });
  const [model, setModel] = useState(() => {
    const initialProvider = typeof window !== 'undefined' && localStorage.getItem(PROVIDER_STORAGE_KEY) || 'gemini';
    return initialProvider === 'gemini' ? 'gemini-2.5-flash-lite' : defaultModel;
  });
  const [temperature, setTemperature] = useState(defaultTemperature);
  const [topP, setTopP] = useState(1.0);
  const [apiKey, setApiKey] = useState('');
  const [responseCount, setResponseCount] = useState(0);
  const responseCountRef = useRef(0);
  const [apiKeyInput, setApiKeyInput] = useState('');
  const [apiKeyProvider, setApiKeyProvider] = useState('gemini');
  const previousProviderRef = useRef(null);
  const [isSavingKey, setIsSavingKey] = useState(false);
  const textareaRef = useRef(null);
  const advancedTextareaRefs = useRef({});
  const responseAreaRef = useRef(null);
  const [messages, setMessages] = useState(() => {
    if (defaultMessages && defaultMessages.length > 0) {
      return defaultMessages;
    }
    return [{
      role: 'system',
      content: ''
    }, {
      role: 'user',
      content: ''
    }];
  });
  const [conversationHistory, setConversationHistory] = useState([]);
  const messagesEndRef = useRef(null);
  const isInitialMount = useRef(true);
  const [lastSentJson, setLastSentJson] = useState(null);
  const [lastResponseJson, setLastResponseJson] = useState(null);
  const [hasSubmitted, setHasSubmitted] = useState(false);
  const [isSettingsOpen, setIsSettingsOpen] = useState(() => {
    if (forceSettingsOpen) return true;
    if (typeof window === 'undefined') return true;
    const stored = localStorage.getItem(SETTINGS_PANEL_KEY);
    return stored !== null ? stored === 'true' : true;
  });
  const [isApiCallsOpen, setIsApiCallsOpen] = useState(false);
  const [apiCallTab, setApiCallTab] = useState('request');
  const [isMaximized, setIsMaximized] = useState(false);
  const [isCollapsed, setIsCollapsed] = useState(false);
  const toggleMaximize = () => setIsMaximized(!isMaximized);
  const toggleCollapse = () => setIsCollapsed(!isCollapsed);
  const headerTitle = title || (defaultMode === 'advanced' ? 'Advanced Playground' : 'LLM Playground');
  const [detectedTheme, setDetectedTheme] = useState('dark');
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const checkTheme = () => {
      const isDark = document.documentElement.classList.contains('dark');
      setDetectedTheme(isDark ? 'dark' : 'light');
    };
    checkTheme();
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const theme = userTheme || detectedTheme;
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const currentProvider = localStorage.getItem(PROVIDER_STORAGE_KEY) || 'gemini';
    const storageKey = PROVIDERS[currentProvider].storageKey;
    const storedKey = localStorage.getItem(storageKey);
    if (storedKey) {
      setApiKey(storedKey);
    } else {
      for (const [provKey, prov] of Object.entries(PROVIDERS)) {
        if (provKey === currentProvider) continue;
        const otherKey = localStorage.getItem(prov.storageKey);
        if (otherKey) {
          setApiKey(otherKey);
          setProvider(provKey);
          setModel(prov.models[0].value);
          localStorage.setItem(PROVIDER_STORAGE_KEY, provKey);
          break;
        }
      }
    }
    const handleApiKeyChanged = () => {
      const activeProvider = localStorage.getItem(PROVIDER_STORAGE_KEY) || 'gemini';
      const key = localStorage.getItem(PROVIDERS[activeProvider].storageKey);
      if (key) {
        setApiKey(key);
        setProvider(activeProvider);
        setModel(PROVIDERS[activeProvider].models[0].value);
      }
    };
    window.addEventListener('apiKeyChanged', handleApiKeyChanged);
    return () => window.removeEventListener('apiKeyChanged', handleApiKeyChanged);
  }, []);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    if (forceSettingsOpen) {
      setIsSettingsOpen(true);
      return;
    }
    localStorage.setItem(SETTINGS_PANEL_KEY, String(isSettingsOpen));
  }, [isSettingsOpen, forceSettingsOpen]);
  useEffect(() => {
    responseCountRef.current = responseCount;
  }, [responseCount]);
  const filterComments = useCallback(text => {
    return text.split('\n').filter(line => !line.trim().startsWith('//')).join('\n').trim();
  }, []);
  const autoResizeTextarea = useCallback(textarea => {
    if (!textarea) return;
    textarea.style.height = '0px';
    const scrollHeight = textarea.scrollHeight;
    const minHeight = 48;
    const maxHeight = 240;
    const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
    textarea.style.height = `${newHeight}px`;
  }, []);
  useEffect(() => {
    if (textareaRef.current) {
      textareaRef.current.style.height = '0px';
      textareaRef.current.style.height = `${Math.max(textareaRef.current.scrollHeight, 60)}px`;
    }
  }, []);
  useEffect(() => {
    if (isInitialMount.current) {
      isInitialMount.current = false;
      return;
    }
    if (messagesEndRef.current && conversationHistory.length > 0) {
      setTimeout(() => {
        if (messagesEndRef.current) {
          messagesEndRef.current.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest'
          });
        }
      }, 100);
    }
  }, [conversationHistory, isLoading]);
  useEffect(() => {
    Object.values(advancedTextareaRefs.current).forEach(textarea => {
      if (textarea) {
        autoResizeTextarea(textarea);
      }
    });
  }, [messages, autoResizeTextarea]);
  useEffect(() => {
    if (mode === 'advanced' && hasSubmitted && responseAreaRef.current && !isLoading) {
      setTimeout(() => {
        if (responseAreaRef.current) {
          responseAreaRef.current.scrollIntoView({
            behavior: 'smooth',
            block: 'start'
          });
        }
      }, 100);
    }
  }, [hasSubmitted, output, mode, isLoading]);
  const constructAdvancedMessages = useCallback(() => {
    return messages.filter(msg => msg.content.trim().length > 0);
  }, [messages]);
  const isFormValid = useMemo(() => {
    if (!apiKey) return false;
    if (mode === 'chat') {
      return input.trim().length > 0;
    } else {
      return messages.some(msg => msg.content.trim().length > 0);
    }
  }, [mode, apiKey, input, messages]);
  const handleApiKeySubmit = useCallback(async e => {
    e.preventDefault();
    setIsSavingKey(true);
    setError('');
    if (!apiKeyInput.trim()) {
      setError(`Please enter your ${PROVIDERS[apiKeyProvider].label} API key`);
      setIsSavingKey(false);
      return;
    }
    const trimmedKey = apiKeyInput.trim();
    if (apiKeyProvider === 'openai' && !trimmedKey.startsWith('sk-')) {
      setError('Invalid API key format. OpenAI API keys should start with "sk-"');
      setIsSavingKey(false);
      return;
    }
    try {
      const storageKey = PROVIDERS[apiKeyProvider].storageKey;
      localStorage.setItem(storageKey, trimmedKey);
      localStorage.setItem(PROVIDER_STORAGE_KEY, apiKeyProvider);
      setProvider(apiKeyProvider);
      setApiKey(trimmedKey);
      setApiKeyInput('');
      previousProviderRef.current = null;
      const providerModels = PROVIDERS[apiKeyProvider].models;
      setModel(providerModels[0].value);
      window.dispatchEvent(new CustomEvent('apiKeyChanged', {
        detail: {
          configured: true
        }
      }));
    } catch (err) {
      setError('Failed to save API key. Please try again.');
    } finally {
      setIsSavingKey(false);
    }
  }, [apiKeyInput, apiKeyProvider]);
  const handleRemoveProvider = useCallback(() => {
    if (!provider || typeof window === 'undefined') return;
    const confirmRemove = window.confirm(`Are you sure you want to remove the ${PROVIDERS[provider].label} API key?`);
    if (!confirmRemove) return;
    try {
      const storageKey = PROVIDERS[provider].storageKey;
      localStorage.removeItem(storageKey);
      const otherProviderKey = Object.keys(PROVIDERS).find(p => p !== provider && localStorage.getItem(PROVIDERS[p].storageKey));
      if (otherProviderKey) {
        const newApiKey = localStorage.getItem(PROVIDERS[otherProviderKey].storageKey);
        setProvider(otherProviderKey);
        setApiKey(newApiKey);
        setModel(PROVIDERS[otherProviderKey].models[0].value);
        localStorage.setItem(PROVIDER_STORAGE_KEY, otherProviderKey);
      } else {
        setApiKey('');
        setProvider('gemini');
        localStorage.removeItem(PROVIDER_STORAGE_KEY);
        if (!forceSettingsOpen) {
          setIsSettingsOpen(false);
        }
      }
      window.dispatchEvent(new CustomEvent('apiKeyChanged', {
        detail: {
          configured: !!otherProviderKey
        }
      }));
    } catch (err) {
      setError('Failed to remove provider. Please try again.');
    }
  }, [provider, forceSettingsOpen]);
  const handleSubmit = useCallback(async e => {
    if (e?.preventDefault) {
      e.preventDefault();
    }
    if (!apiKey || isLoading || !isFormValid) {
      return;
    }
    setIsLoading(true);
    setError('');
    setHasSubmitted(true);
    if (mode === 'advanced') {
      setLastResponseJson(null);
    }
    let requestMessages = [];
    let userMessageContent = '';
    if (mode === 'chat') {
      const filteredInput = filterComments(input);
      if (!filteredInput) {
        setError('Please enter a prompt (comments are not sent to the API)');
        setIsLoading(false);
        return;
      }
      userMessageContent = filteredInput;
      requestMessages = [{
        role: 'user',
        content: filteredInput
      }];
      setConversationHistory(prev => [...prev, {
        role: 'user',
        content: filteredInput
      }]);
      if (!keepInput) {
        setInput('');
      }
    } else {
      const advancedMessages = constructAdvancedMessages();
      if (advancedMessages.length === 0) {
        setError('Please fill in at least one field in Advanced mode');
        setIsLoading(false);
        return;
      }
      requestMessages = advancedMessages;
    }
    const isAnthropic = provider === 'anthropic';
    const jsonPayload = isAnthropic ? {
      model,
      messages: requestMessages.filter(m => m.role !== 'system'),
      ...requestMessages.find(m => m.role === 'system') && ({
        system: requestMessages.find(m => m.role === 'system').content
      }),
      temperature,
      top_p: topP,
      max_tokens: MAX_TOKENS
    } : {
      model,
      messages: requestMessages,
      temperature,
      top_p: topP,
      max_tokens: MAX_TOKENS
    };
    setLastSentJson(jsonPayload);
    try {
      const apiUrl = PROVIDERS[provider].apiUrl;
      const maxRetries = 3;
      let response;
      const headers = isAnthropic ? {
        'Content-Type': 'application/json',
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01',
        'anthropic-dangerous-direct-browser-access': 'true'
      } : {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
      };
      for (let attempt = 0; attempt <= maxRetries; attempt++) {
        response = await fetch(apiUrl, {
          method: 'POST',
          headers,
          body: JSON.stringify(jsonPayload)
        });
        if (response.status === 429 && attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000;
          setError(`Rate limited. Retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`);
          await new Promise(resolve => setTimeout(resolve, delay));
          setError('');
          continue;
        }
        break;
      }
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        if (response.status === 429) {
          throw Object.assign(new Error('Rate limit exceeded. You have used all your free tier quota.'), {
            isRateLimit: true
          });
        }
        throw new Error(errorData.error?.message || `HTTP ${response.status}: Failed to get response from ${PROVIDERS[provider].label}`);
      }
      const data = await response.json();
      const newResponse = isAnthropic ? data.content?.[0]?.text || 'No response generated' : data.choices?.[0]?.message?.content || 'No response generated';
      setLastResponseJson(data);
      if (mode === 'advanced') {
        setOutput(newResponse);
      } else {
        setConversationHistory(prev => [...prev, {
          role: 'assistant',
          content: newResponse
        }]);
        setOutput(newResponse);
      }
    } catch (err) {
      setError(err.isRateLimit ? {
        message: err.message,
        isRateLimit: true
      } : err.message || 'An error occurred while processing your request');
      if (mode === 'chat') {
        setConversationHistory(prev => prev.slice(0, -1));
      }
    } finally {
      setIsLoading(false);
    }
  }, [mode, input, apiKey, provider, isLoading, keepInput, constructAdvancedMessages, filterComments, isFormValid]);
  const errorMessage = typeof error === 'object' ? error.message : error;
  const isRateLimitError = typeof error === 'object' && error.isRateLimit;
  const renderErrorContent = () => <>
      {errorMessage}
      {isRateLimitError && <>
          {' '}
          <a href="https://ai.google.dev/gemini-api/docs/rate-limits" target="_blank" rel="noopener noreferrer" style={{
    color: 'inherit',
    textDecoration: 'underline'
  }}>
            Check your rate limits
          </a>
        </>}
    </>;
  const renderMarkdown = text => {
    const parts = text.split(/(\*\*[^*]+\*\*)/g);
    return parts.map((part, i) => {
      if (part.startsWith('**') && part.endsWith('**')) {
        return <strong key={i}>{part.slice(2, -2)}</strong>;
      }
      return part;
    });
  };
  const renderChatMessage = (message, index, isPlaceholder = false) => {
    const isUser = message.role === 'user';
    return <div key={index} className={isUser ? 'llm-chat-message llm-chat-message-user' : 'llm-chat-message llm-chat-message-assistant'}>
        <div className={`llm-chat-bubble ${isUser ? 'llm-chat-bubble-user' : 'llm-chat-bubble-assistant'} ${isPlaceholder ? 'llm-chat-bubble-placeholder' : ''}`}>
          {renderMarkdown(message.content)}
        </div>
      </div>;
  };
  const renderChatInterface = () => {
    const allMessages = [...conversationHistory];
    const placeholderMessages = [];
    if (response && !hasSubmitted && conversationHistory.length === 0) {
      if (defaultInput && defaultInput.trim()) {
        const filteredInput = filterComments(defaultInput);
        if (filteredInput) {
          placeholderMessages.push({
            role: 'user',
            content: filteredInput,
            isPlaceholder: true
          });
          placeholderMessages.push({
            role: 'assistant',
            content: response,
            isPlaceholder: true
          });
        }
      }
    }
    const hasPlaceholderMessages = placeholderMessages.length > 0;
    return <div className="llm-chat-interface">
        {hasPlaceholderMessages && <div className="llm-warning-banner">
            <IconWarningSun size={14} />
            <span>This is a sample conversation. Click the send button to make a real API call.</span>
          </div>}
        {allMessages.length === 0 && placeholderMessages.length === 0 && !isLoading && <div className="llm-chat-empty-state">
            Start a conversation using the API key you provided!
          </div>}
        {placeholderMessages.map((msg, idx) => renderChatMessage(msg, `placeholder-${idx}`, true))}
        {allMessages.map((msg, idx) => renderChatMessage(msg, `real-${idx}`, false))}
        {isLoading && <div className="llm-chat-loading">
            <div className="llm-chat-loading-bubble">
              <IconLoadingSpinner size={14} className="llm-chat-loading-spinner" />
              <span>Thinking...</span>
            </div>
          </div>}
        <div ref={messagesEndRef} />
      </div>;
  };
  const renderApiKeyForm = () => <div className="llm-api-key-form">
      <div className="llm-api-key-form-section">
        <h3 className="llm-api-key-form-title">
          <IconWarningTriangle size={16} strokeColor="#f59e0b" />
          <span>Configure an API Key to make the most of the tutorial</span>
        </h3>
        <p className="llm-api-key-form-description">
          Your API key is stored locally in your browser's storage and is never transmitted to external servers.
        </p>
      </div>

      <div className="llm-provider-tabs">
        {Object.entries(PROVIDERS).map(([key, prov]) => <button key={key} type="button" onClick={() => {
    setApiKeyProvider(key);
    setError('');
    setApiKeyInput('');
  }} className={`llm-provider-tab ${apiKeyProvider === key ? 'llm-provider-tab-active' : ''}`}>
            {prov.label}
            {key === 'gemini' && <span className="llm-provider-tab-badge">Free Tier</span>}
          </button>)}
      </div>

      {apiKeyProvider === 'gemini' && <div className="llm-gemini-recommendation">
          Gemini offers a generous free tier — great for learning! Get your free API key at{' '}
          <a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener noreferrer" className="llm-api-key-form-link">
            aistudio.google.com/apikey
          </a>
        </div>}

      {apiKeyProvider === 'openai' && <p className="llm-api-key-form-description" style={{
    marginTop: '4px'
  }}>
          <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="llm-api-key-form-link">
            Don't have an API key? Get one here
          </a>
        </p>}

      <form onSubmit={handleApiKeySubmit}>
        <div className="llm-form-group">
          <div className="llm-form-row">
            <input id="api-key-input" type="password" value={apiKeyInput} onChange={e => {
    setApiKeyInput(e.target.value);
    setError('');
  }} placeholder={apiKeyProvider === 'openai' ? 'OpenAI API Key (sk-...)' : 'Gemini API Key'} disabled={isSavingKey} className={error ? 'llm-api-key-input llm-api-key-input-error' : 'llm-api-key-input'} onBlur={e => {
    e.target.style.borderColor = error ? 'var(--llm-accent-red)' : 'var(--llm-border-color)';
  }} />
            <button type="submit" disabled={isSavingKey || !apiKeyInput.trim()} className="llm-api-key-save-button">
              {isSavingKey ? 'Saving...' : 'Save'}
            </button>
            {previousProviderRef.current && <button type="button" className="llm-api-key-cancel-button" onClick={() => {
    const prev = previousProviderRef.current;
    const prevKey = localStorage.getItem(PROVIDERS[prev].storageKey);
    setProvider(prev);
    setApiKey(prevKey || '');
    setModel(PROVIDERS[prev].models[0].value);
    localStorage.setItem(PROVIDER_STORAGE_KEY, prev);
    setApiKeyInput('');
    setError('');
    previousProviderRef.current = null;
  }}>
                Cancel
              </button>}
          </div>
        </div>
        {error && <div className="llm-error-message llm-error-message-small">
            {renderErrorContent()}
          </div>}
      </form>
    </div>;
  const renderChatInput = () => {
    return <div className="llm-chat-input-container">
        <textarea ref={textareaRef} value={input} onChange={e => setInput(e.target.value)} placeholder="Type a message" disabled={isLoading} className="llm-chat-input-textarea" />
      </div>;
  };
  const renderAdvancedInput = () => <div className="llm-advanced-input-container">
      {messages.map((message, index) => <div key={index} className="llm-advanced-message-box">
          <div className="llm-advanced-message-header">
            <div className="llm-advanced-message-header-row">
              <span className="llm-advanced-message-number">
                #{index + 1}
              </span>
              <select value={message.role} onChange={e => {
    const newMessages = [...messages];
    newMessages[index].role = e.target.value;
    setMessages(newMessages);
  }} disabled={isLoading} className={`llm-advanced-role-select ${message.role === 'system' ? 'llm-advanced-role-select-system' : message.role === 'user' ? 'llm-advanced-role-select-user' : 'llm-advanced-role-select-assistant'}`}>
                <option value="system">system</option>
                <option value="user">user</option>
                <option value="assistant">assistant</option>
              </select>
            </div>
            <button type="button" onClick={() => {
    const newMessages = messages.filter((_, i) => i !== index);
    setMessages(newMessages.length > 0 ? newMessages : [{
      role: 'system',
      content: ''
    }]);
  }} disabled={isLoading || messages.length <= 1} className="llm-advanced-remove-button" title="Remove message">
              <IconClose size={10} />
            </button>
          </div>
          <textarea ref={el => {
    if (el) {
      advancedTextareaRefs.current[index] = el;
    }
  }} value={message.content} onChange={e => {
    const newMessages = [...messages];
    newMessages[index].content = e.target.value;
    setMessages(newMessages);
    autoResizeTextarea(e.target);
  }} placeholder={`Enter ${message.role} message content...`} disabled={isLoading} className="llm-textarea-base llm-textarea-enabled llm-advanced-textarea" />
        </div>)}

      <button type="button" onClick={() => {
    setMessages([...messages, {
      role: 'user',
      content: ''
    }]);
  }} disabled={isLoading} className="llm-advanced-add-button">
        <IconPlus size={12} />
        Add Message
      </button>
    </div>;
  return <div className={`code-editor-wrapper ${isMaximized ? 'maximized' : ''} ${isCollapsed ? 'collapsed' : ''}`} data-theme={theme}>
      <div className="code-editor-header">
          <div className="code-editor-title">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
            </svg>
            {headerTitle}
          </div>
          <div className="code-editor-controls">
            {!isMaximized && <button className="code-editor-collapse-button" onClick={toggleCollapse} title={isCollapsed ? "Expand" : "Collapse"} type="button">
              <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                {isCollapsed ? <polyline points="6 9 12 15 18 9" /> : <polyline points="6 15 12 9 18 15" />}
              </svg>
            </button>}
            <button className="code-editor-maximize-button" onClick={toggleMaximize} title={isMaximized ? "Minimize" : "Maximize (Focus Mode)"} type="button">
              <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                {isMaximized ? <><path d="M4 14h6v6" /><path d="M20 10h-6V4" /><path d="M14 10l7-7" /><path d="M3 21l7-7" /></> : <><path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="M21 3l-7 7" /><path d="M3 21l7-7" /></>}
              </svg>
            </button>
          </div>
        </div>

      {!isCollapsed && <div className={mode === 'chat' ? 'llm-playground-container llm-playground-container-chat llm-playground-container-base' : 'llm-playground-container llm-playground-container-base'} style={{
    height: isMaximized ? 'auto' : height,
    flex: isMaximized ? 1 : 'none'
  }}>
        <div className="llm-playground-main">
          <div className="llm-playground-content">
            {mode === 'chat' ? <>
                {}
                {!apiKey && <div className="llm-api-key-form-wrapper">
                    {renderApiKeyForm()}
                  </div>}

                {}
                {renderChatInterface()}

                {}
                {error && <div className="llm-error-wrapper">
                    <div className="llm-error-message">
                      <IconError size={14} />
                      {renderErrorContent()}
                    </div>
                  </div>}

                {}
                <div className="llm-playground-input-area">
                  <div className="llm-chat-input-wrapper">
                    {renderChatInput()}
                  </div>
                  {apiKey && <button onClick={handleSubmit} disabled={isLoading || !isFormValid} className="llm-chat-send-button" aria-label="Send message">
                      {isLoading ? <IconLoadingSpinner size={16} strokeColor="#000" strokeWidth="2.5" strokeLinecap="round" className="llm-chat-send-spinner" /> : <IconSend size={16} fillColor="#000" className="llm-chat-send-icon" />}
                    </button>}
                </div>
              </> : <>
                {}
                <div className="llm-playground-advanced-input-area" style={{
    flex: hasSubmitted || response && response.trim() ? '0 0 auto' : '1 1 100%',
    borderBottom: hasSubmitted || response && response.trim() ? '1px solid var(--llm-bg-secondary)' : 'none'
  }}>
                  {renderAdvancedInput()}
                  {!apiKey && renderApiKeyForm()}
                  {error && <div className="llm-error-message llm-error-message-top">
                      <IconError size={14} />
                      {renderErrorContent()}
                    </div>}
                </div>

                {(hasSubmitted || response && response.trim()) && <div ref={responseAreaRef} className="llm-playground-response-area">
                    {!apiKey && !(response && response.trim()) ? <>
                        <label className="llm-section-label">Prompt</label>
                        <textarea value={input} onChange={e => setInput(e.target.value)} placeholder="Enter your prompt here... (Configure API key to enable)" disabled={true} className="llm-textarea-base llm-textarea-disabled" />
                      </> : <>
                        {output ? <div className="llm-response-box">{renderMarkdown(output)}</div> : isLoading ? <div className="llm-tab-loading">
                            <span className="llm-tab-loading-content">
                              <IconLoadingSpinner size={14} className="llm-chat-loading-spinner" />
                              Generating response...
                            </span>
                          </div> : response ? <div className="llm-response-section">
                            <label className="llm-section-label">Response (Expected response, press submit to get actual response)</label>
                            <div className="llm-response-box llm-response-box-placeholder">
                              {response}
                            </div>
                          </div> : <div className="llm-tab-empty">
                            Response will appear here
                          </div>}
                      </>}
                  </div>}

                {(apiKey || response && response.trim()) && <div className="llm-playground-actions-area">
                    {apiKey ? <button onClick={handleSubmit} disabled={isLoading || !isFormValid} className="llm-submit-button">
                        {isLoading ? <span className="llm-submit-button-loading">
                            <IconLoadingSpinner size={12} className="llm-submit-button-loading-spinner" />
                            Processing...
                          </span> : 'Submit'}
                      </button> : <div className="llm-config-message">
                        Configure API key above to submit and get actual response
                      </div>}
                  </div>}
              </>}
          </div>

          {isSettingsOpen && <div className="llm-settings-panel" style={{
    width: `${SETTINGS_PANEL_WIDTH}px`
  }}>
              <h4 className="llm-settings-title">Settings</h4>

              <div>
                <label className="llm-settings-label">Provider</label>
                <div className="llm-settings-row-with-action">
                  <select value={provider} onChange={e => {
    const newProvider = e.target.value;
    const storageKey = PROVIDERS[newProvider].storageKey;
    const storedKey = localStorage.getItem(storageKey);
    if (apiKey) {
      previousProviderRef.current = provider;
    }
    setProvider(newProvider);
    setModel(PROVIDERS[newProvider].models[0].value);
    localStorage.setItem(PROVIDER_STORAGE_KEY, newProvider);
    if (storedKey) {
      setApiKey(storedKey);
    } else {
      setApiKey('');
    }
  }} disabled={isLoading} className="llm-settings-select">
                    {Object.entries(PROVIDERS).map(([key, prov]) => {
    const hasKey = typeof window !== 'undefined' && localStorage.getItem(prov.storageKey);
    return <option key={key} value={key} className="llm-settings-option">
                          {prov.label}{!hasKey ? ' (no key)' : ''}
                        </option>;
  })}
                  </select>
                  <button type="button" onClick={handleRemoveProvider} disabled={isLoading} className="llm-settings-remove-button" title="Remove Provider">
                    <IconTrash size={14} />
                  </button>
                </div>
              </div>

              <div>
                <label className="llm-settings-label">
                  Model
                </label>
                <select value={model} onChange={e => setModel(e.target.value)} disabled={isLoading} className="llm-settings-select">
                  {PROVIDERS[provider].models.map(m => <option key={m.value} value={m.value} className="llm-settings-option">
                      {m.label}
                    </option>)}
                </select>
              </div>

              <div>
                <div className="llm-settings-range-container">
                  <label className="llm-settings-label">
                    Temperature
                    <span className="llm-settings-info" title="Controls randomness. Lower values make output more focused and deterministic. Higher values make it more creative and varied.">
                      <IconInfo size={11} />
                    </span>
                  </label>
                  <span className="llm-settings-range-value">
                    {temperature.toFixed(1)}
                  </span>
                </div>
                <input type="range" min="0" max="2" step="0.1" value={temperature} onChange={e => setTemperature(parseFloat(e.target.value))} disabled={isLoading} className="llm-settings-range" />
                <div className="llm-settings-range-labels">
                  <span>0.0</span>
                  <span>1.0</span>
                  <span>2.0</span>
                </div>
              </div>

              <div>
                <div className="llm-settings-range-container">
                  <label className="llm-settings-label">
                    Top P
                    <span className="llm-settings-info" title="Nucleus sampling. Controls the cumulative probability cutoff. Lower values consider fewer tokens, making output more focused. Usually adjusted as an alternative to temperature.">
                      <IconInfo size={11} />
                    </span>
                  </label>
                  <span className="llm-settings-range-value">
                    {topP.toFixed(2)}
                  </span>
                </div>
                <input type="range" min="0" max="1" step="0.05" value={topP} onChange={e => setTopP(parseFloat(e.target.value))} disabled={isLoading} className="llm-settings-range" />
                <div className="llm-settings-range-labels">
                  <span>0.0</span>
                  <span>0.5</span>
                  <span>1.0</span>
                </div>
              </div>

            </div>}
        </div>

        {isApiCallsOpen && (lastSentJson || lastResponseJson) && <div className="llm-api-calls-content">
            <div className="llm-api-calls-tabs">
              {lastSentJson && <button type="button" className={`llm-api-calls-tab${apiCallTab === 'request' ? ' llm-api-calls-tab-active' : ''}`} onClick={() => setApiCallTab('request')}>
                  Request
                </button>}
              {lastResponseJson && <button type="button" className={`llm-api-calls-tab${apiCallTab === 'response' ? ' llm-api-calls-tab-active' : ''}`} onClick={() => setApiCallTab('response')}>
                  Response
                </button>}
            </div>
            {apiCallTab === 'request' && lastSentJson && <div className="llm-api-calls-block">
                <div className="llm-textarea-base llm-textarea-enabled llm-json-viewer">
                  {JSON.stringify(lastSentJson, null, 2)}
                </div>
              </div>}
            {apiCallTab === 'response' && lastResponseJson && <div className="llm-api-calls-block">
                <div className="llm-textarea-base llm-textarea-enabled llm-json-viewer">
                  {JSON.stringify(lastResponseJson, null, 2)}
                </div>
              </div>}
          </div>}

        <div className="llm-footer">
          <div className="llm-footer-status-container">
            <span>{PROVIDERS[provider].label} key:</span>
            <div className="llm-footer-status-icon" title={apiKey ? 'API Key configured' : 'API Key not configured'}>
              {apiKey ? <IconCheckmark size={14} strokeColor="#22c55e" /> : <IconXStatus size={14} strokeColor="#ef4444" />}
            </div>
          </div>
          <div className="llm-footer-actions">
            {hasSubmitted && (lastSentJson || lastResponseJson) && <button type="button" onClick={() => setIsApiCallsOpen(!isApiCallsOpen)} className={isApiCallsOpen ? 'llm-footer-toggle-button llm-footer-toggle-button-active' : 'llm-footer-toggle-button'} aria-label="Toggle API Calls">
                <IconChevron size={12} isOpen={isApiCallsOpen} className={isApiCallsOpen ? 'llm-footer-toggle-icon llm-footer-toggle-icon-open' : 'llm-footer-toggle-icon'} />
                <span>API Calls</span>
              </button>}
            <button onClick={() => {
    if (!forceSettingsOpen) {
      setIsSettingsOpen(!isSettingsOpen);
    }
  }} disabled={forceSettingsOpen} className={isSettingsOpen ? 'llm-footer-toggle-button llm-footer-toggle-button-active' : 'llm-footer-toggle-button'} aria-label="Toggle settings">
              <IconChevron size={12} isOpen={isSettingsOpen} className={isSettingsOpen ? 'llm-footer-toggle-icon llm-footer-toggle-icon-open' : 'llm-footer-toggle-icon'} />
              <span>Settings</span>
            </button>
          </div>
        </div>
      </div>}
    </div>;
};

When a single prompt isn't enough, you need techniques that improve reasoning and reliability. This page covers Chain-of-Thought, self-consistency, extended thinking, and prompt chaining.

## Chain-of-Thought (CoT): Making Reasoning Visible

<Note>
  Chain-of-Thought (CoT) is less common with reasoning models, since they already perform an explicit reasoning step. With SLMs and other non-reasoning models, however, CoT can still make a meaningful difference.

  That said, it’s still valuable to learn CoT techniques—they help you understand how these models think and how to effectively influence their behavior.
</Note>

**The Problem:**

<LLMPlayground title="Playground: Let's do some math" defaultMode="chat" defaultInput={`What's 15% tip on a $47.83 bill?`} response={`$7.17`} height="400px" keepInput={true} />

But what if you need to debug a wrong answer? You can't see the reasoning. The expected response would be something like (note: the response shown below is a placeholder example, not a real API response):

<LLMPlayground
  title="Playground: Chain-of-Thought"
  defaultMode="advanced"
  defaultMessages={[
{"role": "system", "content": "Show your reasoning steps briefly before the final answer."},
{"role": "user", "content": "What's a 15% tip on a $47.83 bill? Think step by step."},
]}
  height="400px"
/>

**In Production:**

* Use CoT for complex reasoning; avoid for deterministic extraction/classification at temperature=0.
* Consider privacy/compliance: avoid logging sensitive intermediate reasoning.
* Cost/latency rise with longer outputs—use selectively.

**Why It Works:**

* Often improves performance on reasoning tasks (magnitude varies by task/model)
* Creates "intermediate tokens" that guide the model
* Makes errors debuggable

**Production Pattern:**

<LLMPlayground
  title="Playground: CoT Production Pattern"
  defaultMode="advanced"
  defaultMessages={[
{"role": "system", "content": "You are a helpful assistant that solves problems step by step."},
{"role": "user", "content": "<question>What's 15% tip on a $47.83 bill?</question>\n\n<instructions>\nSolve this step by step:\n1. Identify what information you need\n2. Break down the problem into sub-steps\n3. Solve each sub-step\n4. Combine into final answer\n5. Verify your answer makes sense\n</instructions>\n\n<thinking>\n[Your step-by-step reasoning here]\n</thinking>\n\n<final_answer>\n[Your final answer here]\n</final_answer>"}
]}
  height="600px"
/>

**Real-World Impact:**

* Code generation: 35% fewer bugs with CoT
* Math problems: 50-70% accuracy improvement
* Medical diagnosis: More reliable clinical reasoning

## Self-Consistency: Voting for Reliability

**The Problem:** One response might be wrong due to non-determinism, ambiguous tasks, and/or valid solution paths.

**The Solution:** Generate multiple responses and vote.

<CodeEditor file="src/prompting/advance_self_consistency.ts" functionName="main" lines="24-36" title="Advanced Self-Consistency" />

**When to Use:**

* High-stakes decisions (medical, financial, legal)
* Complex reasoning where errors are costly
* Classification tasks where confidence matters

**Cost Consideration:**

* 5x Agent tasks = 5x cost
* Use only when accuracy justifies expense

**Performance Data:**

* CoT often improves performance on reasoning benchmarks; magnitude varies by task/model (see Wei et al., 2022)
* Combining CoT + Self-Consistency can yield additional gains; magnitude varies by task/model (see Wang et al., 2022)
* Always validate on your evaluation set; do not assume universal gains

## Extended Thinking: Anthropic's Secret Weapon

**Claude-Specific Feature:** Claude can expose its "thinking" before answering using special tags.

```python Prompt theme={null}
<thinking>
Let me analyze this complex legal document...
- First, I'll identify the key clauses
- Then, I'll look for any conflicting terms
- Finally, I'll assess risk level
</thinking>

[Your actual task here]
```

**Why This Matters:**

1. **Debugging:** See where reasoning went wrong
2. **Quality:** Forces model to think before answering
3. **Transparency:** Clients can audit AI decisions

**Thinking tags can also be used to guide Claude steps:**

<CodeGroup>
  ```ts TypeScript theme={null}
  async function analyzeContract(contractText: string): Promise<{
      analysis: any;
      reasoning: string;
  }> {
      const prompt = `
  <document>
  ${contractText}
  </document>

  <thinking>
  I need to analyze this contract for:
  1. Key obligations
  2. Termination clauses
  3. Liability limits
  4. Red flags

  Let me work through each section...
  </thinking>

  Provide a JSON response with:
  - obligations: list of key obligations
  - risks: list of potential risks
  - recommendations: list of recommended actions
  `;
      
      const response = await claude.generate(prompt);
      
      // Parse thinking section for audit trail
      const thinking = extractBetweenTags(response, "thinking");
      const result = extractJson(response);
      
      return {
          analysis: result,
          reasoning: thinking  // Store for compliance/review
      };
  }
  ```
</CodeGroup>

## Prompt Chaining: Breaking Complex Tasks

**Single Prompt Limitations:**

* Context window fills up
* Errors compound
* Hard to debug
* Expensive to retry

**Chaining Solution:** Break one complex task into sequential simple tasks.

```mermaid theme={null}
flowchart LR
    A[Ticket Received] --> B[Classify Urgency]
    B -->|high| C[Extract Details]
    C --> D[Search KB]
    D --> E[Generate Response]
    B -->|low| E
    style B fill:#c8e6c9
    style D fill:#c8e6c9
```

<CodeEditor file="src/prompting/prompt_chaining.ts" functionName="main" lines="127-139" title="Prompt Chaining" />

**Benefits:**

* Each step is simple → fewer errors
* Failed steps can retry independently
* Cheaper: Only call expensive steps when needed
* Easier to evaluate and improve

**Trade-off:**

* More latency (sequential calls)
* More complex code
* Multiple LLM calls (but often cheaper overall)

***

<Quiz>
  <QuizQuestion question="You add 'think step by step' to a simple factual lookup ('What's the capital of France?'). What happens?" options={["Accuracy improves significantly", "You waste tokens — CoT helps complex reasoning, not simple recall", "The model refuses to answer"]} answer={1} explanation="CoT adds value for multi-step reasoning (math, logic, analysis). For simple factual retrieval, it just burns tokens without improving accuracy." />

  <QuizQuestion question="Your prompt chain has 4 steps. Step 3 fails 20% of the time. What's the best fix?" options={["Add retry logic for step 3 only, without re-running the entire chain", "Combine all 4 steps into one prompt to avoid the failure point", "Lower the temperature globally across all steps"]} answer={0} explanation="Prompt chaining's key advantage: you can retry or fix individual steps. Combining everything into one prompt makes the whole thing fragile and harder to debug." />

  <QuizQuestion question="You run self-consistency with 5 samples and get: 'Paris, Paris, Lyon, Paris, Marseille'. The majority answer is Paris. Should you trust it?" options={["Yes — 3/5 agreement is strong enough for production", "It depends — 60% agreement is moderate; check if the dissenting answers reveal ambiguity in the question itself", "No — you need at least 80% agreement"]} answer={1} explanation="Low agreement (60%) can signal an ambiguous question or insufficient context. Before trusting majority vote, investigate why the model produced different answers." />
</Quiz>
