Playground Studio
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
Overview
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] β
ββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββTabs
Chat Tab
Evolves ChatPlayground.tsx into a multi-turn streaming workbench:
- Full markdown rendering via
MarkdownMessage.tsx(code blocks, tables, lists, links). - System prompt sourced from the shared Config pane.
- Token/cost per message (prompt + completion tokens).
- Regenerate last response.
- Sends to
POST /v1/chat/completionswith SSE streaming.
Compare Tab
The key differentiator for a proxy: run 1 prompt across up to 4 models in parallel.
- Up to 4 columns, each independently streaming from
/v1/chat/completions. + Add modelbutton (Cmd+K shortcut) to add columns.Run all βΆtriggers all streams simultaneously viaPromise.all+ per-columnAbortController.- Global Cancel all aborts every in-flight stream.
- Per-column
ProviderMetricsshows TTFT, TPS, tokens, and estimated cost in real time. - Metrics labelled "client-side estimate" (D12) β measured from first SSE chunk.
API Tab
Preserves 100% of the original Monaco editor for power users (D14):
- 10 endpoints: chat completions, completions, embeddings, images, audio, speech, transcriptions, moderations, rerank, search.
- Multimodal file upload.
- SSE streaming with real-time output.
- Wrapped as
ApiTab.tsx(lazy-loaded,ssr: false).
Build Tab
Tools/function calling and structured output UI:
ToolsBuilder.tsxβ add/edit/removetools[]with JSON schema editor per tool. Validates parameters viaToolDefinitionSchema(Zod).StructuredOutputEditor.tsxβ toggle JSON mode + JSON schema editor. Validates response against schema viaStructuredOutputSchema(Zod).- Sends request to
/v1/chat/completionswithtools[]and/orresponse_format.
Config Pane (Shared)
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.
Top Bar
StudioTopBar.tsx:
- Tab switcher (role="tablist").
TokenCostCounterβ live token (β/β) and estimated cost display.- Export code button (
</>) β opensExportCodeModal.
Export Code Modal
ExportCodeModal.tsx uses codeExport.ts to generate curl / Python / TypeScript snippets
from the current PlaygroundState. API key placeholder is always $OMNIROUTE_API_KEY (D11).
Prompt Improver
ImprovePromptButton.tsx β useImprovePrompt.ts β POST /api/playground/improve-prompt:
- Modal warns "will consume quota".
- On confirm, sends
{ system, prompt, model, tone }to the route. - Route calls
/v1/chat/completionsinternally withpromptImprover.META_SYSTEM_PROMPT. - Returns
{ improvedSystem?, improvedPrompt?, tokensIn, tokensOut }. - UI patches the Config pane system prompt and the Chat tab user prompt.
Presets
PresetPicker.tsx β usePresets.ts β /api/playground/presets/*:
- Stored in
playground_presetsSQLite table (migration076_playground_presets.sql). - Each preset stores:
name,endpoint,model,system,params_json,created_at. - CRUD:
GETlist,POSTcreate,GET /:id,PUT /:id,DELETE /:id.
Stream Metrics
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.- Pricing from static table in
src/lib/playground/types.ts(labelled "estimated" β D13).
Backend Routes
| 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).
Key Files
| 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/076_playground_presets.sql | DB migration |
Troubleshooting
| 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" |
References
- Master plan:
_tasks/features-v3.8.6/refactorpages/_orchestration/master-plan-group-C.md - Feature plan:
_tasks/features-v3.8.6/refactorpages/17-playground-studio-redesign.plan.md - Code export:
src/lib/playground/codeExport.ts - Prompt improver:
src/lib/playground/promptImprover.ts - Search Tools Studio:
docs/frameworks/SEARCH_TOOLS_STUDIO.md