diff --git a/dev_server.log b/dev_server.log index b6638b8..9f3401e 100644 --- a/dev_server.log +++ b/dev_server.log @@ -1,6 +1,6 @@ -$ vite --port "8080" +$ vite --host 0.0.0.0 --port 3000 - VITE v5.4.21 ready in 310 ms + VITE v5.4.21 ready in 312 ms - ➜ Local: http://localhost:8080/ - ➜ Network: http://192.168.0.2:8080/ + ➜ Local: http://localhost:3000/ + ➜ Network: http://192.168.0.2:3000/ diff --git a/frontend/src/flow/statePresets.ts b/frontend/src/flow/statePresets.ts new file mode 100644 index 0000000..c68338f --- /dev/null +++ b/frontend/src/flow/statePresets.ts @@ -0,0 +1,57 @@ +export interface StatePreset { + id: string; + name: string; + stateStr: string; // The JSON string representing the state preset + createdAt: number; +} + +const STATE_PRESETS_PREFIX = "agent_flow.state_presets.v1"; + +/** + * Loads the list of initial state presets for a given workflow ID. + * If workflowId is null/empty, defaults to "default". + * + * @param workflowId The active workflow ID. + * @returns An array of StatePreset objects. + */ +export function loadPresets(workflowId: string | null): StatePreset[] { + const id = workflowId || "default"; + try { + const raw = localStorage.getItem(`${STATE_PRESETS_PREFIX}.${id}`); + if (raw) { + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) { + return parsed; + } + } + } catch (e) { + console.error("Failed to load state presets:", e); + } + return []; +} + +/** + * Saves the list of initial state presets for a given workflow ID to localStorage. + * If workflowId is null/empty, defaults to "default". + * + * @param workflowId The active workflow ID. + * @param presets The full array of StatePreset objects to persist. + */ +export function savePresets(workflowId: string | null, presets: StatePreset[]): void { + const id = workflowId || "default"; + try { + localStorage.setItem(`${STATE_PRESETS_PREFIX}.${id}`, JSON.stringify(presets)); + } catch (e) { + console.error("Failed to save state presets:", e); + } +} + +/** + * Generates a unique, descriptive ID for a preset. + */ +export function cryptoId(): string { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + return "preset_" + Math.random().toString(36).slice(2, 10); +} diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx index 26ab5c7..f4206f9 100644 --- a/frontend/src/pages/Index.tsx +++ b/frontend/src/pages/Index.tsx @@ -57,6 +57,12 @@ import { TEMPLATES, } from "@/flow/workflows"; import { WorkflowsManager } from "@/flow/WorkflowsManager"; +import { + loadPresets, + savePresets, + StatePreset, + cryptoId as presetCryptoId, +} from "@/flow/statePresets"; const nodeTypes = { agent: AgentNode }; @@ -210,6 +216,60 @@ function Canvas() { return JSON.stringify({ query: "hello world" }, null, 2); }); const [initialStateError, setInitialStateError] = useState(null); + + // ---- State Presets ---- + const [presets, setPresets] = useState(() => loadPresets(loadActiveWorkflowId())); + const [selectedPresetId, setSelectedPresetId] = useState(""); + const [newPresetName, setNewPresetName] = useState(""); + + useEffect(() => { + let loaded = loadPresets(activeWorkflowId); + if (loaded.length === 0) { + let defaultPreset: StatePreset | null = null; + if (activeWorkflowId === "template-react") { + defaultPreset = { + id: "seed-react", + name: "OpenAI Search Query", + stateStr: JSON.stringify({ query: "Should we search the web for OpenAI?" }, null, 2), + createdAt: Date.now(), + }; + } else if (activeWorkflowId === "template-http-router") { + defaultPreset = { + id: "seed-http", + name: "GitHub Octocat User", + stateStr: JSON.stringify({ username: "octocat" }, null, 2), + createdAt: Date.now(), + }; + } else if (activeWorkflowId === "template-translation-hitl") { + defaultPreset = { + id: "seed-translation", + name: "Lennon Quote Translation", + stateStr: JSON.stringify({ query: "Life is what happens when you're busy making other plans." }, null, 2), + createdAt: Date.now(), + }; + } else if (!activeWorkflowId) { + defaultPreset = { + id: "seed-default", + name: "Default Query", + stateStr: JSON.stringify({ query: "hello world" }, null, 2), + createdAt: Date.now(), + }; + } + + if (defaultPreset) { + loaded = [defaultPreset]; + savePresets(activeWorkflowId, loaded); + } + } + setPresets(loaded); + setSelectedPresetId(""); + setNewPresetName(""); + + if (loaded.length > 0) { + setSelectedPresetId(loaded[0].id); + setInitialStateStr(loaded[0].stateStr); + } + }, [activeWorkflowId]); const [visualSpeed, setVisualSpeed] = useState<"fast" | "visualized">("visualized"); const [pendingApproval, setPendingApproval] = useState<{ nodeId: string; @@ -1793,9 +1853,120 @@ function Canvas() { )} + + {/* State Presets Toolbar */} +
+
+ + Preset: + + + {selectedPresetId && ( + + )} +
+ + {/* Save Current as Preset Form */} +
+ setNewPresetName(e.target.value)} + placeholder="New preset name..." + className="flex-1 bg-transparent border-b border-dashed border-[hsl(var(--ink-faint))] focus:border-[hsl(var(--ink))] outline-none py-0.5 px-1 font-mono text-[10px] text-[hsl(var(--ink))]" + /> + +
+
+