{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "prompt-playground",
  "title": "Prompt Playground",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/prompt-playground/PromptPlayground.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  Bot,\n  Braces,\n  Check,\n  Clock,\n  Copy,\n  Cpu,\n  FileCode,\n  MessageSquare,\n  RotateCcw,\n  Save,\n  Sliders,\n  Sparkles,\n  Terminal,\n  Zap,\n} from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\nimport { Slider } from '@/components/ui/slider'\nimport { Switch } from '@/components/ui/switch'\nimport { Textarea } from '@/components/ui/textarea'\nimport { cn } from '@/lib/utils'\n\ninterface ModelOption {\n  id: string\n  name: string\n  provider: string\n  contextWindow: string\n  inputCostPer1M: number\n  outputCostPer1M: number\n}\n\ninterface Preset {\n  id: string\n  name: string\n  description: string\n  model: string\n  temperature: number\n  maxTokens: number\n  topP: number\n  jsonMode: boolean\n  stream: boolean\n  systemPrompt: string\n  userMessage: string\n  mockResponse: string\n  mockTokens: number\n  mockLatency: string\n}\n\nconst models: ModelOption[] = [\n  {\n    id: 'claude-3-5-sonnet',\n    name: 'Claude 3.5 Sonnet',\n    provider: 'Anthropic',\n    contextWindow: '200k',\n    inputCostPer1M: 3.0,\n    outputCostPer1M: 15.0,\n  },\n  {\n    id: 'gpt-4o',\n    name: 'GPT-4o',\n    provider: 'OpenAI',\n    contextWindow: '128k',\n    inputCostPer1M: 2.5,\n    outputCostPer1M: 10.0,\n  },\n  {\n    id: 'llama-3-3-70b',\n    name: 'Llama 3.3 70B',\n    provider: 'Meta',\n    contextWindow: '128k',\n    inputCostPer1M: 0.6,\n    outputCostPer1M: 0.8,\n  },\n]\n\nconst presets: Preset[] = [\n  {\n    id: 'code-reviewer',\n    name: 'Code Reviewer',\n    description: 'Security & vulnerability audit for backend code',\n    model: 'claude-3-5-sonnet',\n    temperature: 0.2,\n    maxTokens: 2048,\n    topP: 0.9,\n    jsonMode: false,\n    stream: true,\n    systemPrompt:\n      'You are a world-class senior developer assistant specializing in application security, code reviews, and high-performance software architecture. Analyze code with precision, highlight vulnerabilities (OWASP Top 10), and provide clean refactored snippets.',\n    userMessage:\n      'Review the following authentication middleware for security vulnerabilities and race conditions:\\n\\n```typescript\\nexport async function authMiddleware(req: Request) {\\n  const token = req.headers.get(\"authorization\")?.split(\" \")[1];\\n  if (!token) throw new Error(\"Unauthorized\");\\n  const session = await db.session.findUnique({ where: { token } });\\n  if (session.expiresAt < new Date()) {\\n    await db.session.delete({ where: { token } });\\n    throw new Error(\"Expired\");\\n  }\\n  return session.user;\\n}\\n```',\n    mockResponse:\n      '### Security Review & Vulnerability Analysis\\n\\nI identified 3 key security & stability issues in the provided authentication middleware:\\n\\n1. **Unhandled Exceptions / Missing Error Envelope:**\\n   Throwing raw `Error` instances inside middleware can cause unhandled 500 crashes instead of structured 401/403 HTTP responses.\\n2. **Missing Token Existence Check on DB lookup:**\\n   If `session` is not found, `session.expiresAt` throws a `TypeError` (cannot read properties of null).\\n3. **Non-atomic Cleanup & Database Mutation:**\\n   Deleting expired tokens in the read path introduces write amplification on every expired request and isn\\'t atomic with the expiration verification.\\n\\n---\\n\\n### Recommended Refactoring\\n\\n```typescript\\nimport { NextResponse } from \"next/server\";\\n\\nexport async function authMiddleware(req: Request) {\\n  const authHeader = req.headers.get(\"authorization\");\\n  if (!authHeader?.startsWith(\"Bearer \")) {\\n    return new Response(JSON.stringify({ error: \"Missing or malformed Authorization header\" }), {\\n      status: 401,\\n      headers: { \"Content-Type\": \"application/json\" },\\n    });\\n  }\\n\\n  const token = authHeader.slice(7).trim();\\n  const session = await db.session.findUnique({\\n    where: { token },\\n    include: { user: true },\\n  });\\n\\n  if (!session || session.expiresAt.getTime() <= Date.now()) {\\n    return new Response(JSON.stringify({ error: \"Invalid or expired session\" }), {\\n      status: 401,\\n      headers: { \"Content-Type\": \"application/json\" },\\n    });\\n  }\\n\\n  return session.user;\\n}\\n```\\n\\n**Key Improvements:**\\n- Constant-time prefix check avoiding unexpected array indexing errors.\\n- Null-safe session expiration check without throwing uncaught exceptions.\\n- Immediate 401 JSON error responses matching standard API conventions.',\n    mockTokens: 428,\n    mockLatency: '1.4s',\n  },\n  {\n    id: 'customer-support',\n    name: 'Customer Support Agent',\n    description: 'Empathetic, brand-aligned troubleshooting for customer inquiries',\n    model: 'gpt-4o',\n    temperature: 0.7,\n    maxTokens: 1024,\n    topP: 0.95,\n    jsonMode: false,\n    stream: true,\n    systemPrompt:\n      'You are an empathetic, concise, and professional Tier-2 support specialist for CloudFlow SaaS. Your goal is to troubleshoot customer issues efficiently while maintaining a helpful, brand-aligned tone.',\n    userMessage:\n      'A customer wrote: \"Our webhooks stopped delivering payload events since 14:00 UTC today. We are seeing intermittent 504 gateway timeout errors on endpoint https://api.client.com/events. Can you check our account ID #ACC-9821?\"',\n    mockResponse:\n      'Hello Team,\\n\\nThank you for reaching out, and I apologize for the disruption to your event pipeline.\\n\\nI investigated account **#ACC-9821** and observed the following:\\n\\n- **Root Cause:** Our webhook dispatch worker encountered downstream 504 Gateway Timeouts from `https://api.client.com/events` when response times exceeded the 5,000ms threshold.\\n- **Retry Mechanism:** Failed webhook events have been automatically enrolled into exponential backoff (attempt 3 of 5).\\n- **Action Taken:** We temporarily increased the worker handshake timeout to 8,000ms for your tenant while your ingress recovers.\\n\\n**Recommended Next Steps:**\\n1. Check your upstream server logs around 14:00 UTC for concurrent connection saturation or database connection pool starvation.\\n2. If you need all 14:00–15:00 UTC events replayed immediately, reply with **\"CONFIRM REPLAY #ACC-9821\"** and I will trigger a zero-loss backfill.\\n\\nBest regards,\\n**CloudFlow Tier-2 Engineering Support**',\n    mockTokens: 286,\n    mockLatency: '0.9s',\n  },\n  {\n    id: 'json-extractor',\n    name: 'JSON Extractor',\n    description: 'Strict schema extraction from unstructured invoices or logs',\n    model: 'gpt-4o',\n    temperature: 0.0,\n    maxTokens: 1024,\n    topP: 0.1,\n    jsonMode: true,\n    stream: false,\n    systemPrompt:\n      'You are an infallible data extraction engine. Extract structured entities from unstructured receipts, invoices, or logs and return ONLY valid RFC-8259 JSON matching the requested schema. Do not include markdown formatting, explanations, or prologue.',\n    userMessage:\n      'Extract invoice metadata:\\nInvoice Number: INV-2026-8891\\nDate: August 21, 2026\\nVendor: Acme Cloud Services LLC (Tax ID: US-99182312)\\nClient: Cyberdyne Systems\\nItems:\\n1. GPU Cluster Instance H100 x 48 hrs @ $3.20/hr = $153.60\\n2. NVMe High-Speed Storage 2TB x 1 mo @ $80.00/mo = $80.00\\nTax: $18.69 (8%)\\nTotal Due: $252.29',\n    mockResponse:\n      '{\\n  \"invoiceNumber\": \"INV-2026-8891\",\\n  \"issueDate\": \"2026-08-21\",\\n  \"vendor\": {\\n    \"name\": \"Acme Cloud Services LLC\",\\n    \"taxId\": \"US-99182312\"\\n  },\\n  \"client\": {\\n    \"name\": \"Cyberdyne Systems\"\\n  },\\n  \"lineItems\": [\\n    {\\n      \"description\": \"GPU Cluster Instance H100\",\\n      \"quantity\": 48,\\n      \"unit\": \"hours\",\\n      \"unitPrice\": 3.20,\\n      \"totalPrice\": 153.60\\n    },\\n    {\\n      \"description\": \"NVMe High-Speed Storage 2TB\",\\n      \"quantity\": 1,\\n      \"unit\": \"month\",\\n      \"unitPrice\": 80.00,\\n      \"totalPrice\": 80.00\\n    }\\n  ],\\n  \"subtotal\": 233.60,\\n  \"taxRate\": 0.08,\\n  \"taxAmount\": 18.69,\\n  \"totalAmount\": 252.29,\\n  \"currency\": \"USD\"\\n}',\n    mockTokens: 215,\n    mockLatency: '0.6s',\n  },\n]\n\nexport function PromptPlayground({ className }: { className?: string }) {\n  const [selectedModel, setSelectedModel] = React.useState('claude-3-5-sonnet')\n  const [selectedPreset, setSelectedPreset] = React.useState('code-reviewer')\n\n  const [systemPrompt, setSystemPrompt] = React.useState(presets[0].systemPrompt)\n  const [userMessage, setUserMessage] = React.useState(presets[0].userMessage)\n  const [temperature, setTemperature] = React.useState(presets[0].temperature)\n  const [maxTokens, setMaxTokens] = React.useState(presets[0].maxTokens)\n  const [topP, setTopP] = React.useState(presets[0].topP)\n  const [jsonMode, setJsonMode] = React.useState(presets[0].jsonMode)\n  const [streamResponses, setStreamResponses] = React.useState(presets[0].stream)\n\n  const [displayedResponse, setDisplayedResponse] = React.useState(presets[0].mockResponse)\n  const [responseStatus, setResponseStatus] = React.useState('200 OK · 1.4s · 428 tokens')\n  const [isGenerating, setIsGenerating] = React.useState(false)\n  const [showRawJson, setShowRawJson] = React.useState(false)\n  const [isCopied, setIsCopied] = React.useState(false)\n  const [isPresetSaved, setIsPresetSaved] = React.useState(false)\n\n  const streamTimerRef = React.useRef<ReturnType<typeof setInterval> | null>(null)\n\n  const activeModel = React.useMemo(() => {\n    return models.find((m) => m.id === selectedModel) ?? models[0]\n  }, [selectedModel])\n\n  const estimatedPromptTokens = React.useMemo(() => {\n    const combined = (systemPrompt || '') + (userMessage || '')\n    return Math.max(1, Math.round(combined.length / 3.8))\n  }, [systemPrompt, userMessage])\n\n  const estimatedTotalTokens = React.useMemo(() => {\n    const outputEstimate = isGenerating ? Math.round(maxTokens / 4) : 428\n    return estimatedPromptTokens + outputEstimate\n  }, [estimatedPromptTokens, isGenerating, maxTokens])\n\n  const estimatedCost = React.useMemo(() => {\n    const inputCost = (estimatedPromptTokens / 1_000_000) * activeModel.inputCostPer1M\n    const outputCost = (428 / 1_000_000) * activeModel.outputCostPer1M\n    return (inputCost + outputCost).toFixed(4)\n  }, [estimatedPromptTokens, activeModel])\n\n  const rawJsonPayload = React.useMemo(() => {\n    return JSON.stringify(\n      {\n        id: `chatcmpl-${Math.random().toString(36).substring(2, 10)}`,\n        object: 'chat.completion',\n        created: 1787313600,\n        model: selectedModel,\n        system_fingerprint: 'fp_uipkge_49a',\n        choices: [\n          {\n            index: 0,\n            message: {\n              role: 'assistant',\n              content: displayedResponse,\n            },\n            finish_reason: 'stop',\n          },\n        ],\n        usage: {\n          prompt_tokens: estimatedPromptTokens,\n          completion_tokens: 428,\n          total_tokens: estimatedPromptTokens + 428,\n        },\n        configuration: {\n          temperature,\n          max_tokens: maxTokens,\n          top_p: topP,\n          response_format: jsonMode ? { type: 'json_object' } : { type: 'text' },\n          stream: streamResponses,\n        },\n      },\n      null,\n      2,\n    )\n  }, [selectedModel, displayedResponse, estimatedPromptTokens, temperature, maxTokens, topP, jsonMode, streamResponses])\n\n  const handlePresetChange = React.useCallback((presetId: string) => {\n    setSelectedPreset(presetId)\n    const preset = presets.find((p) => p.id === presetId)\n    if (!preset) return\n\n    setSystemPrompt(preset.systemPrompt)\n    setUserMessage(preset.userMessage)\n    setSelectedModel(preset.model)\n    setTemperature(preset.temperature)\n    setMaxTokens(preset.maxTokens)\n    setTopP(preset.topP)\n    setJsonMode(preset.jsonMode)\n    setStreamResponses(preset.stream)\n    setDisplayedResponse(preset.mockResponse)\n    setResponseStatus(`200 OK · ${preset.mockLatency} · ${preset.mockTokens} tokens`)\n  }, [])\n\n  const runPrompt = React.useCallback(() => {\n    if (isGenerating) return\n    if (streamTimerRef.current) clearInterval(streamTimerRef.current)\n\n    const activePresetObj = presets.find((p) => p.id === selectedPreset) ?? presets[0]\n    const targetResponse =\n      jsonMode && !activePresetObj.jsonMode ? presets[2].mockResponse : activePresetObj.mockResponse\n    const targetLatency = activePresetObj.mockLatency\n    const targetTokens = activePresetObj.mockTokens\n\n    setIsGenerating(true)\n    setResponseStatus('Generating...')\n\n    if (streamResponses) {\n      setDisplayedResponse('')\n      let charIndex = 0\n      const chunkSize = 8\n      const totalChars = targetResponse.length\n\n      streamTimerRef.current = setInterval(() => {\n        charIndex += chunkSize\n        if (charIndex >= totalChars) {\n          setDisplayedResponse(targetResponse)\n          if (streamTimerRef.current) clearInterval(streamTimerRef.current)\n          streamTimerRef.current = null\n          setIsGenerating(false)\n          setResponseStatus(`200 OK · ${targetLatency} · ${targetTokens} tokens`)\n        } else {\n          setDisplayedResponse(targetResponse.slice(0, charIndex))\n        }\n      }, 25)\n    } else {\n      setTimeout(() => {\n        setDisplayedResponse(targetResponse)\n        setIsGenerating(false)\n        setResponseStatus(`200 OK · ${targetLatency} · ${targetTokens} tokens`)\n      }, 600)\n    }\n  }, [isGenerating, selectedPreset, jsonMode, streamResponses])\n\n  const handleCopyResponse = React.useCallback(() => {\n    if (!displayedResponse) return\n    navigator.clipboard?.writeText(displayedResponse)\n    setIsCopied(true)\n    setTimeout(() => setIsCopied(false), 2000)\n  }, [displayedResponse])\n\n  const handleSavePreset = React.useCallback(() => {\n    setIsPresetSaved(true)\n    setTimeout(() => setIsPresetSaved(false), 2000)\n  }, [])\n\n  const handleResetDefaults = React.useCallback(() => {\n    setTemperature(0.7)\n    setMaxTokens(2048)\n    setTopP(0.9)\n    setJsonMode(false)\n    setStreamResponses(true)\n  }, [])\n\n  React.useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {\n        e.preventDefault()\n        runPrompt()\n      }\n    }\n    window.addEventListener('keydown', handleKeyDown)\n    return () => {\n      window.removeEventListener('keydown', handleKeyDown)\n      if (streamTimerRef.current) clearInterval(streamTimerRef.current)\n    }\n  }, [runPrompt])\n\n  return (\n    <div data-slot=\"prompt-playground\" className={cn('w-full space-y-6', className)}>\n      {/* Top Toolbar */}\n      <div className=\"border-border bg-card/70 flex flex-wrap items-center justify-between gap-3 rounded-lg border p-3.5 shadow-xs backdrop-blur-xs\">\n        <div className=\"flex flex-wrap items-center gap-2.5\">\n          {/* Model Selector */}\n          <div className=\"w-48\">\n            <Select value={selectedModel} onValueChange={setSelectedModel}>\n              <SelectTrigger className=\"h-9 w-full text-xs font-medium\" aria-label=\"Select AI Model\">\n                <SelectValue placeholder=\"Select model\" />\n              </SelectTrigger>\n              <SelectContent>\n                {models.map((m) => (\n                  <SelectItem key={m.id} value={m.id}>\n                    <div className=\"flex items-center gap-2\">\n                      <Sparkles className=\"text-primary size-3.5\" />\n                      <span>{m.name}</span>\n                    </div>\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n\n          {/* Preset Template Dropdown */}\n          <div className=\"w-52\">\n            <Select value={selectedPreset} onValueChange={handlePresetChange}>\n              <SelectTrigger className=\"h-9 w-full text-xs\" aria-label=\"Select Preset Template\">\n                <SelectValue placeholder=\"Preset template\" />\n              </SelectTrigger>\n              <SelectContent>\n                {presets.map((p) => (\n                  <SelectItem key={p.id} value={p.id}>\n                    <span>{p.name}</span>\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n\n          {/* Token Counter Pill */}\n          <div className=\"border-border bg-muted/60 text-muted-foreground hidden items-center gap-1.5 rounded-full border px-3 py-1 font-mono text-xs md:inline-flex\">\n            <Cpu className=\"text-primary size-3.5\" />\n            <span>{estimatedTotalTokens.toLocaleString()} tokens</span>\n            <span className=\"text-foreground font-medium\">${estimatedCost}</span>\n          </div>\n        </div>\n\n        <div className=\"flex items-center gap-2\">\n          {/* Save Preset Outline Button */}\n          <Button variant=\"outline\" size=\"sm\" className=\"h-9 gap-1.5 text-xs font-medium\" onClick={handleSavePreset}>\n            {isPresetSaved ? <Check className=\"text-success size-3.5\" /> : <Save className=\"size-3.5\" />}\n            <span>{isPresetSaved ? 'Saved!' : 'Save Preset'}</span>\n          </Button>\n\n          {/* Run Prompt Primary Button */}\n          <Button\n            size=\"sm\"\n            className=\"bg-primary text-primary-foreground h-9 gap-1.5 text-xs font-medium shadow-xs\"\n            disabled={isGenerating}\n            onClick={runPrompt}\n          >\n            {isGenerating ? (\n              <span className=\"border-primary-foreground size-3.5 animate-spin rounded-full border-2 border-t-transparent\" />\n            ) : (\n              <Zap className=\"size-3.5 fill-current\" />\n            )}\n            <span>{isGenerating ? 'Running...' : 'Run Prompt'}</span>\n            <kbd className=\"bg-primary-foreground/20 hidden items-center rounded px-1 py-0.5 font-mono text-xs sm:inline-flex\">\n              ⌘↵\n            </kbd>\n          </Button>\n        </div>\n      </div>\n\n      {/* 2-Column Studio Layout */}\n      <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        {/* Left Panel: Prompt Configuration & Test Input */}\n        <div className=\"space-y-6 lg:col-span-7\">\n          {/* System Instructions Card */}\n          <Card className=\"border-border bg-card shadow-xs\">\n            <CardHeader className=\"pb-3\">\n              <div className=\"flex flex-wrap items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <Bot className=\"size-4\" />\n                  </div>\n                  <div>\n                    <CardTitle className=\"text-sm font-semibold\">System Instructions</CardTitle>\n                    <CardDescription className=\"text-xs\">\n                      Behavior, role personality, and operational constraints\n                    </CardDescription>\n                  </div>\n                </div>\n                <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                  role: system\n                </Badge>\n              </div>\n            </CardHeader>\n            <CardContent className=\"pt-0\">\n              <Textarea\n                value={systemPrompt}\n                onValueChange={setSystemPrompt}\n                placeholder=\"You are an expert AI assistant...\"\n                rows={4}\n                noResize\n                className=\"border-border/70 focus:border-primary font-mono text-xs leading-relaxed\"\n              />\n            </CardContent>\n            <CardFooter className=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\">\n              <span className=\"flex flex-wrap items-center gap-1.5\">\n                <FileCode className=\"size-3.5\" />\n                <span>Context: {activeModel.contextWindow}</span>\n              </span>\n              <span className=\"font-mono\">{systemPrompt.length} chars</span>\n            </CardFooter>\n          </Card>\n\n          {/* User Message Input Card */}\n          <Card className=\"border-border bg-card shadow-xs\">\n            <CardHeader className=\"pb-3\">\n              <div className=\"flex flex-wrap items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"bg-secondary text-secondary-foreground flex size-7 items-center justify-center rounded-md\">\n                    <MessageSquare className=\"size-4\" />\n                  </div>\n                  <div>\n                    <CardTitle className=\"text-sm font-semibold\">User Message</CardTitle>\n                    <CardDescription className=\"text-xs\">Prompt input payload and test variables</CardDescription>\n                  </div>\n                </div>\n                <Badge variant=\"outline\" className=\"font-mono text-xs\">\n                  role: user\n                </Badge>\n              </div>\n            </CardHeader>\n            <CardContent className=\"pt-0\">\n              <Textarea\n                value={userMessage}\n                onValueChange={setUserMessage}\n                placeholder=\"Enter user test prompt...\"\n                rows={7}\n                noResize\n                className=\"border-border/70 focus:border-primary font-mono text-xs leading-relaxed\"\n              />\n            </CardContent>\n            <CardFooter className=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\">\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-muted-foreground text-xs\">Quick inject:</span>\n                <button\n                  type=\"button\"\n                  className=\"hover:border-primary/60 hover:text-foreground min-h-6 rounded border border-dashed px-1.5 py-0.5 font-mono text-xs transition-colors\"\n                  onClick={() => setUserMessage((prev) => prev + '\\n\\nEnsure strict backward compatibility.')}\n                >\n                  +compatibility\n                </button>\n                <button\n                  type=\"button\"\n                  className=\"hover:border-primary/60 hover:text-foreground min-h-6 rounded border border-dashed px-1.5 py-0.5 font-mono text-xs transition-colors\"\n                  onClick={() => setUserMessage((prev) => prev + '\\n\\nOutput in concise bullet points.')}\n                >\n                  +bullets\n                </button>\n              </div>\n              <span className=\"font-mono\">~{estimatedPromptTokens} tokens</span>\n            </CardFooter>\n          </Card>\n        </div>\n\n        {/* Right Panel: Parameters Sidebar + Output Card */}\n        <div className=\"space-y-6 lg:col-span-5\">\n          {/* Right Parameter Sidebar Card */}\n          <Card className=\"border-border bg-card shadow-xs\">\n            <CardHeader className=\"pb-3\">\n              <div className=\"flex flex-wrap items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                    <Sliders className=\"size-4\" />\n                  </div>\n                  <CardTitle className=\"text-sm font-semibold\">Parameters</CardTitle>\n                </div>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  className=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                  onClick={handleResetDefaults}\n                >\n                  <RotateCcw className=\"mr-1 size-3\" />\n                  <span>Reset</span>\n                </Button>\n              </div>\n            </CardHeader>\n            <CardContent className=\"space-y-4 pt-0\">\n              {/* Temperature Slider */}\n              <div className=\"space-y-2\">\n                <div className=\"flex flex-wrap items-center justify-between text-xs\">\n                  <label className=\"text-foreground font-medium\">Temperature</label>\n                  <span className=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono font-medium\">\n                    {Number(temperature).toFixed(2)}\n                  </span>\n                </div>\n                <Slider\n                  value={[temperature]}\n                  onValueChange={(val) => setTemperature(val[0])}\n                  min={0}\n                  max={1}\n                  step={0.05}\n                />\n                <div className=\"text-muted-foreground flex justify-between text-xs\">\n                  <span>0.0 (Deterministic)</span>\n                  <span>1.0 (Creative)</span>\n                </div>\n              </div>\n\n              {/* Max Tokens Slider */}\n              <div className=\"space-y-2\">\n                <div className=\"flex flex-wrap items-center justify-between text-xs\">\n                  <label className=\"text-foreground font-medium\">Max Tokens</label>\n                  <span className=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono font-medium\">\n                    {Number(maxTokens).toLocaleString()}\n                  </span>\n                </div>\n                <Slider\n                  value={[maxTokens]}\n                  onValueChange={(val) => setMaxTokens(val[0])}\n                  min={256}\n                  max={4096}\n                  step={128}\n                />\n                <div className=\"text-muted-foreground flex justify-between text-xs\">\n                  <span>256</span>\n                  <span>4,096</span>\n                </div>\n              </div>\n\n              {/* Top P Slider */}\n              <div className=\"space-y-2\">\n                <div className=\"flex flex-wrap items-center justify-between text-xs\">\n                  <label className=\"text-foreground font-medium\">Top P</label>\n                  <span className=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono font-medium\">\n                    {Number(topP).toFixed(2)}\n                  </span>\n                </div>\n                <Slider value={[topP]} onValueChange={(val) => setTopP(val[0])} min={0} max={1} step={0.05} />\n                <div className=\"text-muted-foreground flex justify-between text-xs\">\n                  <span>0.0 (Focused)</span>\n                  <span>1.0 (Diverse)</span>\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Switches */}\n              <div className=\"space-y-3\">\n                <div className=\"flex flex-wrap items-center justify-between gap-2\">\n                  <div className=\"space-y-0.5\">\n                    <label htmlFor=\"react-json-mode\" className=\"text-foreground cursor-pointer text-xs font-medium\">\n                      JSON Mode\n                    </label>\n                    <p className=\"text-muted-foreground text-xs\">Enforce structured JSON output</p>\n                  </div>\n                  <Switch id=\"react-json-mode\" checked={jsonMode} onCheckedChange={setJsonMode} />\n                </div>\n\n                <div className=\"flex flex-wrap items-center justify-between gap-2\">\n                  <div className=\"space-y-0.5\">\n                    <label\n                      htmlFor=\"react-stream-responses\"\n                      className=\"text-foreground cursor-pointer text-xs font-medium\"\n                    >\n                      Stream Responses\n                    </label>\n                    <p className=\"text-muted-foreground text-xs\">Simulate real-time token streaming</p>\n                  </div>\n                  <Switch id=\"react-stream-responses\" checked={streamResponses} onCheckedChange={setStreamResponses} />\n                </div>\n              </div>\n            </CardContent>\n          </Card>\n\n          {/* Response Output Card */}\n          <Card className=\"border-border bg-card shadow-xs\">\n            <CardHeader className=\"pb-3\">\n              <div className=\"flex flex-wrap items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <Terminal className=\"size-4\" />\n                  </div>\n                  <div>\n                    <CardTitle className=\"text-sm font-semibold\">Response Output</CardTitle>\n                  </div>\n                </div>\n                <div className=\"flex flex-wrap items-center gap-1.5\">\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    className={cn(\n                      'h-7 px-2 text-xs',\n                      showRawJson ? 'text-primary bg-primary/10' : 'text-muted-foreground',\n                    )}\n                    onClick={() => setShowRawJson(!showRawJson)}\n                  >\n                    <Braces className=\"mr-1 size-3.5\" />\n                    <span>{showRawJson ? 'Formatted' : 'Raw JSON'}</span>\n                  </Button>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    className=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                    onClick={handleCopyResponse}\n                  >\n                    {isCopied ? <Check className=\"text-success mr-1 size-3.5\" /> : <Copy className=\"mr-1 size-3.5\" />}\n                    <span>{isCopied ? 'Copied' : 'Copy'}</span>\n                  </Button>\n                </div>\n              </div>\n              <div className=\"mt-2 flex items-center gap-2\">\n                <Badge\n                  variant=\"outline\"\n                  className={cn(\n                    'font-mono text-xs',\n                    isGenerating ? 'border-primary/40 text-primary animate-pulse' : 'border-success/30 text-success',\n                  )}\n                >\n                  <span\n                    className={cn(\n                      'mr-1.5 inline-block size-1.5 rounded-full',\n                      isGenerating ? 'bg-primary' : 'bg-success',\n                    )}\n                  />\n                  {responseStatus}\n                </Badge>\n              </div>\n            </CardHeader>\n            <CardContent className=\"pt-0\">\n              {/* Raw JSON View */}\n              {showRawJson ? (\n                <div className=\"relative\">\n                  <pre className=\"border-border bg-muted/40 text-foreground max-h-[380px] overflow-x-auto overflow-y-auto rounded-md border p-3.5 font-mono text-xs leading-relaxed\">\n                    <code>{rawJsonPayload}</code>\n                  </pre>\n                </div>\n              ) : (\n                /* Prose / Markdown View */\n                <div className=\"border-border bg-muted/20 text-foreground max-h-[380px] min-h-[220px] overflow-y-auto rounded-md border p-3.5 font-mono text-xs leading-relaxed whitespace-pre-wrap\">\n                  {displayedResponse}\n                  {isGenerating && <span className=\"text-primary ml-0.5 inline-block animate-pulse\">▋</span>}\n                </div>\n              )}\n            </CardContent>\n            <CardFooter className=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\">\n              <span className=\"flex items-center gap-1\">\n                <Clock className=\"size-3.5\" />\n                <span>Finish: stop</span>\n              </span>\n              <span className=\"font-mono\">Throughput: ~305 tok/s</span>\n            </CardFooter>\n          </Card>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/PromptPlayground.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/separator.json",
    "https://uipkge.dev/r/react/slider.json",
    "https://uipkge.dev/r/react/switch.json",
    "https://uipkge.dev/r/react/textarea.json"
  ],
  "description": "AI model prompt engineering studio & parameter tuner with system instructions, user input, temperature, max tokens, top-p sliders, JSON/stream modes, and interactive response evaluation.",
  "categories": [
    "ai",
    "app"
  ]
}