From d516cf0506cd4a71140165647e3987727fbfc9ed Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 11:20:23 -0300 Subject: [PATCH] feat(playground): add PresetPicker + ImprovePromptButton --- .../components/ImprovePromptButton.tsx | 131 +++++++++++ .../playground/components/PresetPicker.tsx | 212 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 src/app/(dashboard)/dashboard/playground/components/ImprovePromptButton.tsx create mode 100644 src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx diff --git a/src/app/(dashboard)/dashboard/playground/components/ImprovePromptButton.tsx b/src/app/(dashboard)/dashboard/playground/components/ImprovePromptButton.tsx new file mode 100644 index 0000000000..0d11f690f7 --- /dev/null +++ b/src/app/(dashboard)/dashboard/playground/components/ImprovePromptButton.tsx @@ -0,0 +1,131 @@ +"use client"; + +// src/app/(dashboard)/dashboard/playground/components/ImprovePromptButton.tsx + +import { useState } from "react"; +import { useImprovePrompt } from "../hooks/useImprovePrompt"; +import type { ConfigState } from "./StudioConfigPane"; + +interface ImprovePromptButtonProps { + configState: ConfigState; + setConfigState: (s: ConfigState) => void; +} + +/** + * ImprovePromptButton — "✨ Improve prompt" button with quota-consumption warning. + * + * Flow: click → confirmation modal → confirm → calls useImprovePrompt.improve() + * → on success, updates configState.systemPrompt and/or configState.params. + * + * D8: uses the model configured in the Config pane (never overrides with cheap model). + */ +export default function ImprovePromptButton({ configState, setConfigState }: ImprovePromptButtonProps) { + const { loading, error, improve } = useImprovePrompt(); + const [confirmOpen, setConfirmOpen] = useState(false); + const [improveError, setImproveError] = useState(null); + + async function handleConfirm() { + setConfirmOpen(false); + setImproveError(null); + + const model = configState.model.trim(); + if (!model) { + setImproveError("Please set a model in the Config pane first."); + return; + } + + const result = await improve({ + system: configState.systemPrompt || undefined, + model, + }); + + if (result == null) { + setImproveError(error ?? "Improve prompt failed."); + return; + } + + // Apply improved versions if returned + const next = { ...configState }; + + if (result.improvedSystem != null) { + next.systemPrompt = result.improvedSystem; + } + + setConfigState(next); + } + + const isDisabled = loading || !configState.model.trim(); + + return ( + <> +
+ + + {improveError && ( +

{improveError}

+ )} +
+ + {/* Quota confirmation modal */} + {confirmOpen && ( +
setConfirmOpen(false)} + role="dialog" + aria-modal="true" + aria-label="Confirm improve prompt" + > +
e.stopPropagation()} + > +
+ +
+

+ Improve prompt +

+

+ This will send your current system prompt to{" "} + {configState.model} + {" "}to generate an improved version. +

+

+ This action will consume model quota. +

+
+
+ +
+ + +
+
+
+ )} + + ); +} diff --git a/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx b/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx new file mode 100644 index 0000000000..d2367678a8 --- /dev/null +++ b/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx @@ -0,0 +1,212 @@ +"use client"; + +// src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx + +import { useEffect, useState } from "react"; +import { usePresets } from "../hooks/usePresets"; +import type { ConfigState } from "./StudioConfigPane"; + +interface PresetPickerProps { + configState: ConfigState; + setConfigState: (s: ConfigState) => void; +} + +/** + * PresetPicker — load/save playground presets. + * + * Presets are stored in DB via /api/playground/presets (F3 backend). + * Load applies preset values to configState; Save opens a name-input modal. + */ +export default function PresetPicker({ configState, setConfigState }: PresetPickerProps) { + const { presets, loading, list, create, remove } = usePresets(); + const [saveModalOpen, setSaveModalOpen] = useState(false); + const [presetName, setPresetName] = useState(""); + const [saving, setSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + // Load presets on mount + useEffect(() => { + void list(); + }, [list]); + + function handleLoad(presetId: string) { + const preset = presets.find((p) => p.id === presetId); + if (!preset) return; + + setConfigState({ + ...configState, + endpoint: preset.endpoint as ConfigState["endpoint"], + model: preset.model, + systemPrompt: preset.system ?? configState.systemPrompt, + params: { + ...configState.params, + ...(typeof preset.params === "object" && preset.params != null ? preset.params : {}), + }, + }); + } + + async function handleSave() { + if (!presetName.trim()) { + setSaveError("Name is required"); + return; + } + + setSaving(true); + setSaveError(null); + + const result = await create({ + name: presetName.trim(), + endpoint: configState.endpoint, + model: configState.model, + system: configState.systemPrompt || null, + params: configState.params as Record, + }); + + setSaving(false); + + if (result == null) { + setSaveError("Failed to save preset"); + return; + } + + setSaveModalOpen(false); + setPresetName(""); + } + + function openSave() { + setSaveModalOpen(true); + setPresetName(""); + setSaveError(null); + } + + function closeSave() { + setSaveModalOpen(false); + setPresetName(""); + setSaveError(null); + } + + return ( + <> +
+ + Presets + + + {/* Load preset select */} +
+ + + +
+ + {/* Preset list with delete buttons (compact) */} + {presets.length > 0 && ( +
+ {presets.map((preset) => ( +
+ + +
+ ))} +
+ )} +
+ + {/* Save preset modal */} + {saveModalOpen && ( +
+
e.stopPropagation()} + > +

Save preset

+ +
+ setPresetName(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") void handleSave(); + if (e.key === "Escape") closeSave(); + }} + placeholder="Preset name" + autoFocus + className="text-xs bg-bg-alt border border-border rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary text-text-main" + /> + + {saveError && ( +

{saveError}

+ )} + +
+ + +
+
+
+
+ )} + + ); +}