/** * LLM Playground (#144) * * Route: /playground (optionally `?prompt=` to prefill — "Open in Playground"). * * Edit a prompt, fill its {{variables}}, and run it against configured LLM * connections (#143) — side by side. Variables are compiled client-side via the * shared prompt-compile engine (#145); output shows tokens, cost, and latency. */ import React, { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { compileText, extractVariables } from '@agentkitai/agentlens-core'; import { useApi } from '../hooks/useApi'; import { listConnections, type LlmConnection } from '../api/llm-connections'; import { runPlayground, type PlaygroundRunResult } from '../api/playground'; interface PanelState { connectionId: string; model: string; temperature: string; result: PlaygroundRunResult | null; error: string | null; running: boolean; } function emptyPanel(): PanelState { return { connectionId: '', model: '', temperature: '', result: null, error: null, running: false }; } export function Playground(): React.ReactElement { const [params] = useSearchParams(); const { data } = useApi(() => listConnections(), []); const connections: LlmConnection[] = data?.connections ?? []; const [prompt, setPrompt] = useState(params.get('prompt') ?? 'Summarize this in one sentence: {{text}}'); const [varValues, setVarValues] = useState>({}); const [panels, setPanels] = useState([emptyPanel(), emptyPanel()]); const variables = useMemo(() => extractVariables(prompt), [prompt]); // With exactly one connection there's no choice to make — pre-select it so // "Open in Playground → Run" works in one click instead of erroring on an // unset connection. useEffect(() => { if (connections.length !== 1) return; const only = connections[0]!.id; setPanels((ps) => ps.map((p) => (p.connectionId ? p : { ...p, connectionId: only }))); }, [connections.length, connections[0]?.id]); function setPanel(i: number, patch: Partial): void { setPanels((ps) => ps.map((p, idx) => (idx === i ? { ...p, ...patch } : p))); } async function run(i: number): Promise { const panel = panels[i]!; if (!panel.connectionId) { setPanel(i, { error: 'Pick a connection first.' }); return; } setPanel(i, { running: true, error: null }); const { text, missing } = compileText(prompt, varValues); if (missing.length > 0) { setPanel(i, { running: false, error: `Fill in: ${missing.join(', ')}` }); return; } try { const result = await runPlayground({ connectionId: panel.connectionId, model: panel.model.trim() || undefined, temperature: panel.temperature.trim() ? Number(panel.temperature) : undefined, prompt: text, }); setPanel(i, { running: false, result }); } catch (e) { setPanel(i, { running: false, error: e instanceof Error ? e.message : String(e) }); } } function runBoth(): void { void run(0); void run(1); } return (

Playground

Run a prompt against your connections and compare side by side.

{connections.length === 0 && (
No LLM connections yet. Add one in Settings → LLM Connections to run the playground.
)} {/* Shared prompt + variables */}