diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx new file mode 100644 index 000000000..4b7f1d9a7 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/PromptsContent.tsx @@ -0,0 +1,225 @@ +"use client"; + +import { PenLine, Plus, Sparkles, Trash2 } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import { toast } from "sonner"; +import type { PromptRead } from "@/contracts/types/prompts.types"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Spinner } from "@/components/ui/spinner"; +import { promptsApiService } from "@/lib/apis/prompts-api.service"; + +interface PromptFormData { + name: string; + prompt: string; + mode: "transform" | "explore"; +} + +const EMPTY_FORM: PromptFormData = { name: "", prompt: "", mode: "transform" }; + +export function PromptsContent() { + const [prompts, setPrompts] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [showForm, setShowForm] = useState(false); + const [editingId, setEditingId] = useState(null); + const [formData, setFormData] = useState(EMPTY_FORM); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + promptsApiService + .list() + .then(setPrompts) + .catch(() => toast.error("Failed to load prompts")) + .finally(() => setIsLoading(false)); + }, []); + + const handleSave = useCallback(async () => { + if (!formData.name.trim() || !formData.prompt.trim()) { + toast.error("Name and prompt are required"); + return; + } + + setIsSaving(true); + try { + if (editingId) { + const updated = await promptsApiService.update(editingId, formData); + setPrompts((prev) => prev.map((p) => (p.id === editingId ? updated : p))); + toast.success("Prompt updated"); + } else { + const created = await promptsApiService.create(formData); + setPrompts((prev) => [created, ...prev]); + toast.success("Prompt created"); + } + setShowForm(false); + setFormData(EMPTY_FORM); + setEditingId(null); + } catch { + toast.error("Failed to save prompt"); + } finally { + setIsSaving(false); + } + }, [formData, editingId]); + + const handleEdit = useCallback((prompt: PromptRead) => { + setFormData({ + name: prompt.name, + prompt: prompt.prompt, + mode: prompt.mode as "transform" | "explore", + }); + setEditingId(prompt.id); + setShowForm(true); + }, []); + + const handleDelete = useCallback(async (id: number) => { + try { + await promptsApiService.delete(id); + setPrompts((prev) => prev.filter((p) => p.id !== id)); + toast.success("Prompt deleted"); + } catch { + toast.error("Failed to delete prompt"); + } + }, []); + + const handleCancel = useCallback(() => { + setShowForm(false); + setFormData(EMPTY_FORM); + setEditingId(null); + }, []); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+

+ Create prompt templates triggered with / in the chat composer. +

+ {!showForm && ( + + )} +
+ + {showForm && ( +
+

+ {editingId ? "Edit prompt" : "New prompt"} +

+ +
+ + setFormData((p) => ({ ...p, name: e.target.value }))} + placeholder="e.g. Fix grammar" + /> +
+ +
+ +