{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "prompt-playground",
  "title": "Prompt Playground",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/prompt-playground/PromptPlayground.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, ref, watch } from 'vue'\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-vue-next'\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'\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\nconst selectedModel = ref('claude-3-5-sonnet')\nconst selectedPreset = ref('code-reviewer')\n\nconst systemPrompt = ref(presets[0].systemPrompt)\nconst userMessage = ref(presets[0].userMessage)\nconst temperature = ref(presets[0].temperature)\nconst maxTokens = ref(presets[0].maxTokens)\nconst topP = ref(presets[0].topP)\nconst jsonMode = ref(presets[0].jsonMode)\nconst streamResponses = ref(presets[0].stream)\n\nconst displayedResponse = ref(presets[0].mockResponse)\nconst responseStatus = ref('200 OK · 1.4s · 428 tokens')\nconst isGenerating = ref(false)\nconst showRawJson = ref(false)\nconst isCopied = ref(false)\nconst isPresetSaved = ref(false)\n\nlet streamTimer: ReturnType<typeof setInterval> | null = null\n\nconst activeModel = computed(() => {\n  return models.find((m) => m.id === selectedModel.value) ?? models[0]\n})\n\nconst estimatedPromptTokens = computed(() => {\n  const combined = (systemPrompt.value || '') + (userMessage.value || '')\n  return Math.max(1, Math.round(combined.length / 3.8))\n})\n\nconst estimatedTotalTokens = computed(() => {\n  const outputEstimate = isGenerating.value ? Math.round(maxTokens.value / 4) : 428\n  return estimatedPromptTokens.value + outputEstimate\n})\n\nconst estimatedCost = computed(() => {\n  const m = activeModel.value\n  const inputCost = (estimatedPromptTokens.value / 1_000_000) * m.inputCostPer1M\n  const outputCost = (428 / 1_000_000) * m.outputCostPer1M\n  return (inputCost + outputCost).toFixed(4)\n})\n\nconst rawJsonPayload = computed(() => {\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.value,\n      system_fingerprint: 'fp_uipkge_49a',\n      choices: [\n        {\n          index: 0,\n          message: {\n            role: 'assistant',\n            content: displayedResponse.value,\n          },\n          finish_reason: 'stop',\n        },\n      ],\n      usage: {\n        prompt_tokens: estimatedPromptTokens.value,\n        completion_tokens: 428,\n        total_tokens: estimatedPromptTokens.value + 428,\n      },\n      configuration: {\n        temperature: temperature.value,\n        max_tokens: maxTokens.value,\n        top_p: topP.value,\n        response_format: jsonMode.value ? { type: 'json_object' } : { type: 'text' },\n        stream: streamResponses.value,\n      },\n    },\n    null,\n    2,\n  )\n})\n\nfunction applyPreset(presetId: string) {\n  const preset = presets.find((p) => p.id === presetId)\n  if (!preset) return\n\n  systemPrompt.value = preset.systemPrompt\n  userMessage.value = preset.userMessage\n  selectedModel.value = preset.model\n  temperature.value = preset.temperature\n  maxTokens.value = preset.maxTokens\n  topP.value = preset.topP\n  jsonMode.value = preset.jsonMode\n  streamResponses.value = preset.stream\n  displayedResponse.value = preset.mockResponse\n  responseStatus.value = `200 OK · ${preset.mockLatency} · ${preset.mockTokens} tokens`\n}\n\nwatch(selectedPreset, (newPreset) => {\n  applyPreset(newPreset)\n})\n\nfunction runPrompt() {\n  if (isGenerating.value) return\n  if (streamTimer) clearInterval(streamTimer)\n\n  const activePresetObj = presets.find((p) => p.id === selectedPreset.value) ?? presets[0]\n  const targetResponse =\n    jsonMode.value && !activePresetObj.jsonMode ? presets[2].mockResponse : activePresetObj.mockResponse\n  const targetLatency = activePresetObj.mockLatency\n  const targetTokens = activePresetObj.mockTokens\n\n  isGenerating.value = true\n  responseStatus.value = 'Generating...'\n\n  if (streamResponses.value) {\n    displayedResponse.value = ''\n    let charIndex = 0\n    const chunkSize = 8\n    const totalChars = targetResponse.length\n\n    streamTimer = setInterval(() => {\n      charIndex += chunkSize\n      if (charIndex >= totalChars) {\n        displayedResponse.value = targetResponse\n        if (streamTimer) clearInterval(streamTimer)\n        streamTimer = null\n        isGenerating.value = false\n        responseStatus.value = `200 OK · ${targetLatency} · ${targetTokens} tokens`\n      } else {\n        displayedResponse.value = targetResponse.slice(0, charIndex)\n      }\n    }, 25)\n  } else {\n    setTimeout(() => {\n      displayedResponse.value = targetResponse\n      isGenerating.value = false\n      responseStatus.value = `200 OK · ${targetLatency} · ${targetTokens} tokens`\n    }, 600)\n  }\n}\n\nfunction handleCopyResponse() {\n  if (!displayedResponse.value) return\n  navigator.clipboard?.writeText(displayedResponse.value)\n  isCopied.value = true\n  setTimeout(() => {\n    isCopied.value = false\n  }, 2000)\n}\n\nfunction handleSavePreset() {\n  isPresetSaved.value = true\n  setTimeout(() => {\n    isPresetSaved.value = false\n  }, 2000)\n}\n\nfunction handleResetDefaults() {\n  temperature.value = 0.7\n  maxTokens.value = 2048\n  topP.value = 0.9\n  jsonMode.value = false\n  streamResponses.value = true\n}\n\nfunction handleKeydown(e: KeyboardEvent) {\n  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {\n    e.preventDefault()\n    runPrompt()\n  }\n}\n\nonMounted(() => {\n  window.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n  window.removeEventListener('keydown', handleKeydown)\n  if (streamTimer) clearInterval(streamTimer)\n})\n</script>\n\n<template>\n  <div data-slot=\"prompt-playground\" class=\"w-full space-y-6\">\n    <!-- Top Toolbar -->\n    <div\n      class=\"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    >\n      <div class=\"flex flex-wrap items-center gap-2.5\">\n        <!-- Model Selector -->\n        <div class=\"w-48\">\n          <Select v-model=\"selectedModel\">\n            <SelectTrigger class=\"h-9 w-full text-xs font-medium\" aria-label=\"Select AI Model\">\n              <SelectValue placeholder=\"Select model\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem v-for=\"m in models\" :key=\"m.id\" :value=\"m.id\">\n                <div class=\"flex items-center gap-2\">\n                  <Sparkles class=\"text-primary size-3.5\" />\n                  <span>{{ m.name }}</span>\n                </div>\n              </SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n\n        <!-- Preset Template Dropdown -->\n        <div class=\"w-52\">\n          <Select v-model=\"selectedPreset\">\n            <SelectTrigger class=\"h-9 w-full text-xs\" aria-label=\"Select Preset Template\">\n              <SelectValue placeholder=\"Preset template\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem v-for=\"p in presets\" :key=\"p.id\" :value=\"p.id\">\n                <span>{{ p.name }}</span>\n              </SelectItem>\n            </SelectContent>\n          </Select>\n        </div>\n\n        <!-- Token Counter Pill -->\n        <div\n          class=\"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        >\n          <Cpu class=\"text-primary size-3.5\" />\n          <span>{{ estimatedTotalTokens.toLocaleString() }} tokens</span>\n          <span class=\"text-foreground font-medium\">${{ estimatedCost }}</span>\n        </div>\n      </div>\n\n      <div class=\"flex items-center gap-2\">\n        <!-- Save Preset Outline Button -->\n        <Button variant=\"outline\" size=\"sm\" class=\"h-9 gap-1.5 text-xs font-medium\" @click=\"handleSavePreset\">\n          <Check v-if=\"isPresetSaved\" class=\"text-success size-3.5\" />\n          <Save v-else class=\"size-3.5\" />\n          <span>{{ isPresetSaved ? 'Saved!' : 'Save Preset' }}</span>\n        </Button>\n\n        <!-- Run Prompt Primary Button -->\n        <Button\n          size=\"sm\"\n          class=\"bg-primary text-primary-foreground h-9 gap-1.5 text-xs font-medium shadow-xs\"\n          :disabled=\"isGenerating\"\n          @click=\"runPrompt\"\n        >\n          <span\n            v-if=\"isGenerating\"\n            class=\"border-primary-foreground size-3.5 animate-spin rounded-full border-2 border-t-transparent\"\n          />\n          <Zap v-else class=\"size-3.5 fill-current\" />\n          <span>{{ isGenerating ? 'Running...' : 'Run Prompt' }}</span>\n          <kbd\n            class=\"bg-primary-foreground/20 hidden items-center rounded px-1 py-0.5 font-mono text-xs sm:inline-flex\"\n          >\n            ⌘↵\n          </kbd>\n        </Button>\n      </div>\n    </div>\n\n    <!-- 2-Column Studio Layout -->\n    <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n      <!-- Left Panel: Prompt Configuration & Test Input -->\n      <div class=\"space-y-6 lg:col-span-7\">\n        <!-- System Instructions Card -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader class=\"pb-3\">\n            <div class=\"flex flex-wrap items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                  <Bot class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">System Instructions</CardTitle>\n                  <CardDescription class=\"text-xs\"\n                    >Behavior, role personality, and operational constraints</CardDescription\n                  >\n                </div>\n              </div>\n              <Badge variant=\"secondary\" class=\"font-mono text-xs\">role: system</Badge>\n            </div>\n          </CardHeader>\n          <CardContent class=\"pt-0\">\n            <Textarea\n              v-model=\"systemPrompt\"\n              placeholder=\"You are an expert AI assistant...\"\n              :rows=\"4\"\n              no-resize\n              class=\"border-border/70 focus:border-primary font-mono text-xs leading-relaxed\"\n            />\n          </CardContent>\n          <CardFooter\n            class=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\"\n          >\n            <span class=\"flex flex-wrap items-center gap-1.5\">\n              <FileCode class=\"size-3.5\" />\n              <span>Context: {{ activeModel.contextWindow }}</span>\n            </span>\n            <span class=\"font-mono\">{{ systemPrompt.length }} chars</span>\n          </CardFooter>\n        </Card>\n\n        <!-- User Message Input Card -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader class=\"pb-3\">\n            <div class=\"flex flex-wrap items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-secondary text-secondary-foreground flex size-7 items-center justify-center rounded-md\">\n                  <MessageSquare class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">User Message</CardTitle>\n                  <CardDescription class=\"text-xs\">Prompt input payload and test variables</CardDescription>\n                </div>\n              </div>\n              <Badge variant=\"outline\" class=\"font-mono text-xs\">role: user</Badge>\n            </div>\n          </CardHeader>\n          <CardContent class=\"pt-0\">\n            <Textarea\n              v-model=\"userMessage\"\n              placeholder=\"Enter user test prompt...\"\n              :rows=\"7\"\n              no-resize\n              class=\"border-border/70 focus:border-primary font-mono text-xs leading-relaxed\"\n            />\n          </CardContent>\n          <CardFooter\n            class=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\"\n          >\n            <div class=\"flex items-center gap-2\">\n              <span class=\"text-muted-foreground text-xs\">Quick inject:</span>\n              <button\n                type=\"button\"\n                class=\"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                @click=\"userMessage += '\\n\\nEnsure strict backward compatibility.'\"\n              >\n                +compatibility\n              </button>\n              <button\n                type=\"button\"\n                class=\"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                @click=\"userMessage += '\\n\\nOutput in concise bullet points.'\"\n              >\n                +bullets\n              </button>\n            </div>\n            <span class=\"font-mono\">~{{ estimatedPromptTokens }} tokens</span>\n          </CardFooter>\n        </Card>\n      </div>\n\n      <!-- Right Panel: Parameters Sidebar + Output Card -->\n      <div class=\"space-y-6 lg:col-span-5\">\n        <!-- Right Parameter Sidebar Card -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader class=\"pb-3\">\n            <div class=\"flex flex-wrap items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-muted text-muted-foreground flex size-7 items-center justify-center rounded-md\">\n                  <Sliders class=\"size-4\" />\n                </div>\n                <CardTitle class=\"text-sm font-semibold\">Parameters</CardTitle>\n              </div>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                @click=\"handleResetDefaults\"\n              >\n                <RotateCcw class=\"mr-1 size-3\" />\n                <span>Reset</span>\n              </Button>\n            </div>\n          </CardHeader>\n          <CardContent class=\"space-y-4 pt-0\">\n            <!-- Temperature Slider -->\n            <div class=\"space-y-2\">\n              <div class=\"flex flex-wrap items-center justify-between text-xs\">\n                <label class=\"text-foreground font-medium\">Temperature</label>\n                <span class=\"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 v-model=\"temperature\" :min=\"0\" :max=\"1\" :step=\"0.05\" />\n              <div class=\"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 class=\"space-y-2\">\n              <div class=\"flex flex-wrap items-center justify-between text-xs\">\n                <label class=\"text-foreground font-medium\">Max Tokens</label>\n                <span class=\"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 v-model=\"maxTokens\" :min=\"256\" :max=\"4096\" :step=\"128\" />\n              <div class=\"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 class=\"space-y-2\">\n              <div class=\"flex flex-wrap items-center justify-between text-xs\">\n                <label class=\"text-foreground font-medium\">Top P</label>\n                <span class=\"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 v-model=\"topP\" :min=\"0\" :max=\"1\" :step=\"0.05\" />\n              <div class=\"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 class=\"space-y-3\">\n              <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                <div class=\"space-y-0.5\">\n                  <label for=\"vue-json-mode\" class=\"text-foreground cursor-pointer text-xs font-medium\">\n                    JSON Mode\n                  </label>\n                  <p class=\"text-muted-foreground text-xs\">Enforce structured JSON output</p>\n                </div>\n                <Switch id=\"vue-json-mode\" v-model=\"jsonMode\" />\n              </div>\n\n              <div class=\"flex flex-wrap items-center justify-between gap-2\">\n                <div class=\"space-y-0.5\">\n                  <label for=\"vue-stream-responses\" class=\"text-foreground cursor-pointer text-xs font-medium\">\n                    Stream Responses\n                  </label>\n                  <p class=\"text-muted-foreground text-xs\">Simulate real-time token streaming</p>\n                </div>\n                <Switch id=\"vue-stream-responses\" v-model=\"streamResponses\" />\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n\n        <!-- Response Output Card -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader class=\"pb-3\">\n            <div class=\"flex flex-wrap items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                  <Terminal class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">Response Output</CardTitle>\n                </div>\n              </div>\n              <div class=\"flex flex-wrap items-center gap-1.5\">\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"h-7 px-2 text-xs\"\n                  :class=\"showRawJson ? 'text-primary bg-primary/10' : 'text-muted-foreground'\"\n                  @click=\"showRawJson = !showRawJson\"\n                >\n                  <Braces class=\"mr-1 size-3.5\" />\n                  <span>{{ showRawJson ? 'Formatted' : 'Raw JSON' }}</span>\n                </Button>\n                <Button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                  @click=\"handleCopyResponse\"\n                >\n                  <Check v-if=\"isCopied\" class=\"text-success mr-1 size-3.5\" />\n                  <Copy v-else class=\"mr-1 size-3.5\" />\n                  <span>{{ isCopied ? 'Copied' : 'Copy' }}</span>\n                </Button>\n              </div>\n            </div>\n            <div class=\"mt-2 flex items-center gap-2\">\n              <Badge\n                variant=\"outline\"\n                class=\"font-mono text-xs\"\n                :class=\"\n                  isGenerating ? 'border-primary/40 text-primary animate-pulse' : 'border-success/30 text-success'\n                \"\n              >\n                <span\n                  class=\"mr-1.5 inline-block size-1.5 rounded-full\"\n                  :class=\"isGenerating ? 'bg-primary' : 'bg-success'\"\n                />\n                {{ responseStatus }}\n              </Badge>\n            </div>\n          </CardHeader>\n          <CardContent class=\"pt-0\">\n            <!-- Raw JSON View -->\n            <div v-if=\"showRawJson\" class=\"relative\">\n              <pre\n                class=\"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></pre>\n            </div>\n\n            <!-- Prose / Markdown View -->\n            <div\n              v-else\n              class=\"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            >\n              {{ displayedResponse\n              }}<span v-if=\"isGenerating\" class=\"text-primary ml-0.5 inline-block animate-pulse\">▋</span>\n            </div>\n          </CardContent>\n          <CardFooter\n            class=\"border-border text-muted-foreground flex flex-wrap items-center justify-between border-t pt-3 text-xs\"\n          >\n            <span class=\"flex items-center gap-1\">\n              <Clock class=\"size-3.5\" />\n              <span>Finish: stop</span>\n            </span>\n            <span class=\"font-mono\">Throughput: ~305 tok/s</span>\n          </CardFooter>\n        </Card>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/PromptPlayground.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/select.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/slider.json",
    "https://uipkge.dev/r/vue/switch.json",
    "https://uipkge.dev/r/vue/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"
  ]
}