diff --git a/src/app/(dashboard)/dashboard/playground/components/ParamSliders.tsx b/src/app/(dashboard)/dashboard/playground/components/ParamSliders.tsx new file mode 100644 index 0000000000..ebbd48883a --- /dev/null +++ b/src/app/(dashboard)/dashboard/playground/components/ParamSliders.tsx @@ -0,0 +1,176 @@ +"use client"; + +// src/app/(dashboard)/dashboard/playground/components/ParamSliders.tsx + +export interface PlaygroundParams { + temperature: number; + max_tokens: number; + top_p: number; + presence_penalty: number; + frequency_penalty: number; + seed: number | null; + stop: string; + jsonMode: boolean; +} + +export const DEFAULT_PARAMS: PlaygroundParams = { + temperature: 1.0, + max_tokens: 1024, + top_p: 1.0, + presence_penalty: 0, + frequency_penalty: 0, + seed: null, + stop: "", + jsonMode: false, +}; + +interface ParamSlidersProps { + params: PlaygroundParams; + setParams: (params: PlaygroundParams) => void; +} + +interface SliderRowProps { + label: string; + value: number; + min: number; + max: number; + step: number; + onChange: (v: number) => void; +} + +function SliderRow({ label, value, min, max, step, onChange }: SliderRowProps) { + return ( +
+
+ + { + const v = parseFloat(e.target.value); + if (!isNaN(v)) onChange(Math.min(max, Math.max(min, v))); + }} + className="w-16 text-xs text-right bg-surface border border-border rounded px-1.5 py-0.5 focus:outline-none focus:ring-1 focus:ring-primary" + /> +
+ onChange(parseFloat(e.target.value))} + className="w-full accent-primary h-1.5" + /> +
+ ); +} + +/** + * Sliders for temperature, max_tokens, top_p, penalties, seed, stop, JSON mode. + * Props: { params, setParams } + */ +export default function ParamSliders({ params, setParams }: ParamSlidersProps) { + function update(key: K, value: PlaygroundParams[K]) { + setParams({ ...params, [key]: value }); + } + + return ( +
+ update("temperature", v)} + /> + + update("max_tokens", Math.round(v))} + /> + + update("top_p", v)} + /> + + update("presence_penalty", v)} + /> + + update("frequency_penalty", v)} + /> + + {/* Seed input */} +
+ + { + const raw = e.target.value; + update("seed", raw === "" ? null : parseInt(raw, 10)); + }} + className="w-full text-xs bg-surface border border-border rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary" + /> +
+ + {/* Stop sequences */} +
+ + update("stop", e.target.value)} + className="w-full text-xs bg-surface border border-border rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary" + /> +
+ + {/* JSON mode toggle */} +
+ + +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/playground/components/TokenCostCounter.tsx b/src/app/(dashboard)/dashboard/playground/components/TokenCostCounter.tsx new file mode 100644 index 0000000000..5975cd7696 --- /dev/null +++ b/src/app/(dashboard)/dashboard/playground/components/TokenCostCounter.tsx @@ -0,0 +1,29 @@ +"use client"; + +// src/app/(dashboard)/dashboard/playground/components/TokenCostCounter.tsx + +interface TokenCostCounterProps { + tokensIn: number; + tokensOut: number; + costUsd: number | null; +} + +/** + * Displays token counts and estimated cost. + * D13: Cost is labeled "(estimated)" to reflect client-side calculation. + */ +export default function TokenCostCounter({ tokensIn, tokensOut, costUsd }: TokenCostCounterProps) { + if (tokensIn === 0 && tokensOut === 0 && costUsd === null) { + return null; + } + + return ( + + {tokensIn > 0 && {tokensIn}↑} + {tokensOut > 0 && {tokensOut}↓} + {costUsd !== null && costUsd > 0 && ( + · ${costUsd.toFixed(4)} (estimated) + )} + + ); +}