docs/frameworks/PLAYGROUND_STUDIO.md
Feature: Playground Studio — unified AI testing workspace for
/dashboard/playground. Plans:17-playground-studio-redesign.plan.md+_orchestration/master-plan-group-C.mdStatus: Released in v3.8.6
Playground Studio transforms /dashboard/playground from a simple Monaco-based editor into
a full-featured testing workspace. It replaces the legacy page.tsx with a PlaygroundStudio
shell that renders four tabs and a shared config pane.
┌ Playground ──────────────────────────────────────────────────────────┐
│ [💬 Chat] [⚖ Compare] [{} API] [🔧 Build] 142↑ 38↓ · $0.002 </>│
├──────────────────────────────────────────┬───────────────────────────┤
│ {active tab content} │ ─ Config │
│ │ Endpoint [chat ∨] │
│ │ Model [gpt-5.4 ∨] │
│ │ System [textarea] │
│ │ Temp ▕▕▔▔ 0.7 │
│ │ Presets [▾ load][save] │
│ │ [✨ Improve prompt] │
└──────────────────────────────────────────┴───────────────────────────┘
Evolves ChatPlayground.tsx into a multi-turn streaming workbench:
MarkdownMessage.tsx (code blocks, tables, lists, links).POST /v1/chat/completions with SSE streaming.The key differentiator for a proxy: run 1 prompt across up to 4 models in parallel.
/v1/chat/completions.+ Add model button (Cmd+K shortcut) to add columns.Run all ▶ triggers all streams simultaneously via Promise.all + per-column AbortController.ProviderMetrics shows TTFT, TPS, tokens, and estimated cost in real time.Preserves 100% of the original Monaco editor for power users (D14):
ApiTab.tsx (lazy-loaded, ssr: false).Tools/function calling and structured output UI:
ToolsBuilder.tsx — add/edit/remove tools[] with JSON schema editor per tool.
Validates parameters via ToolDefinitionSchema (Zod).StructuredOutputEditor.tsx — toggle JSON mode + JSON schema editor.
Validates response against schema via StructuredOutputSchema (Zod)./v1/chat/completions with tools[] and/or response_format.StudioConfigPane.tsx — always visible, collapsible.
| Field | Component | Notes |
|---|---|---|
| Endpoint | <select> | 10 options matching PlaygroundEndpoint |
| Model | <input> | free text, e.g. openai/gpt-4o |
| System prompt | <textarea> | fed into all tabs |
| Parameters | ParamSliders | temperature, max_tokens, top_p, presence/frequency penalty, seed, stop |
| Presets | PresetPicker | load/save named config snapshots (persisted in DB) |
| Improve prompt | ImprovePromptButton | opens quota-warning modal, calls /api/playground/improve-prompt |
State is lifted to PlaygroundStudio.tsx and passed down to all tabs. Switching tabs
preserves config state.
StudioTopBar.tsx:
TokenCostCounter — live token (↑/↓) and estimated cost display.</>) — opens ExportCodeModal.ExportCodeModal.tsx uses codeExport.ts to generate curl / Python / TypeScript snippets
from the current PlaygroundState. API key placeholder is always $OMNIROUTE_API_KEY (D11).
ImprovePromptButton.tsx → useImprovePrompt.ts → POST /api/playground/improve-prompt:
{ system, prompt, model, tone } to the route./v1/chat/completions internally with promptImprover.META_SYSTEM_PROMPT.{ improvedSystem?, improvedPrompt?, tokensIn, tokensOut }.PresetPicker.tsx → usePresets.ts → /api/playground/presets/*:
playground_presets SQLite table (migration 084_playground_presets.sql).name, endpoint, model, system, params_json, created_at.GET list, POST create, GET /:id, PUT /:id, DELETE /:id.useStreamMetrics.ts + streamMetrics.ts (pure function):
start() — records request start time.onFirstChunk() — records TTFT.onChunk(n) — accumulates completion token count.finish(usage?) — computes final metrics: ttftMs, totalMs, tps, tokensIn, tokensOut, costUsd.src/lib/playground/types.ts (labelled "estimated" — D13).| Method | Path | Handler |
|---|---|---|
POST | /api/playground/improve-prompt | Zod-validates ImprovePromptRequestSchema; calls /v1/chat/completions with meta-prompt |
GET | /api/playground/presets | Returns { presets: PlaygroundPresetListItem[] } |
POST | /api/playground/presets | Creates preset; validates PlaygroundPresetCreateSchema |
GET | /api/playground/presets/:id | Returns one preset or 404 |
PUT | /api/playground/presets/:id | Partial update |
DELETE | /api/playground/presets/:id | 204 |
Auth: optional (REQUIRE_API_KEY). Errors via buildErrorBody() (Hard Rule #12).
| Path | Purpose |
|---|---|
src/app/(dashboard)/dashboard/playground/PlaygroundStudio.tsx | Shell component, tab orchestrator |
src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx | Tabs + counter + export button |
src/app/(dashboard)/dashboard/playground/components/StudioConfigPane.tsx | Shared config panel |
src/app/(dashboard)/dashboard/playground/components/tabs/ChatTab.tsx | Chat workbench |
src/app/(dashboard)/dashboard/playground/components/tabs/CompareTab.tsx | Multi-model compare |
src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx | Monaco editor (preserved) |
src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx | Tools + structured output |
src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx | Code export modal |
src/app/(dashboard)/dashboard/playground/components/CompareColumn.tsx | Single compare column |
src/app/(dashboard)/dashboard/playground/components/ProviderMetrics.tsx | TTFT/TPS display |
src/app/(dashboard)/dashboard/playground/hooks/useStreamMetrics.ts | Client-side metric hook |
src/app/(dashboard)/dashboard/playground/hooks/usePresets.ts | Presets CRUD hook |
src/app/(dashboard)/dashboard/playground/hooks/useImprovePrompt.ts | Improve-prompt hook |
src/lib/playground/codeExport.ts | curl/Python/TS generator (shared with Search Tools) |
src/lib/playground/promptImprover.ts | Meta-prompt builder |
src/lib/playground/streamMetrics.ts | Pure metrics computation |
src/lib/db/playgroundPresets.ts | DB module (CRUD) |
src/app/api/playground/improve-prompt/route.ts | Improve-prompt REST route |
src/app/api/playground/presets/route.ts | Presets list + create |
src/app/api/playground/presets/[id]/route.ts | Presets get/update/delete |
src/lib/db/migrations/084_playground_presets.sql | DB migration |
| Symptom | Cause | Fix |
|---|---|---|
| Monaco editor not rendering in API tab | SSR loaded Monaco | Verify ApiTab uses dynamic(..., { ssr: false }) |
| Compare streams fire sequentially | Wrong Promise.all usage | All stream starts must be dispatched in one Promise.all call |
Metrics show null TTFT | First chunk handler not wired | Check useStreamMetrics.onFirstChunk() is called in the SSE reader loop |
| Preset not persisting | DB migration not run | Run npm run db:migrate or restart the server (migration auto-runs on startup) |
| Improve prompt returns 502 | Model not set in Config | User must enter a model name in the Config pane before improving |
Export code shows MISSING_API_KEY | Placeholder not inserted | codeExport.ts always uses API_KEY_PLACEHOLDER = "$OMNIROUTE_API_KEY" |
_tasks/features-v3.8.6/refactorpages/_orchestration/master-plan-group-C.md_tasks/features-v3.8.6/refactorpages/17-playground-studio-redesign.plan.mdsrc/lib/playground/codeExport.tssrc/lib/playground/promptImprover.tsdocs/frameworks/SEARCH_TOOLS_STUDIO.md