diff --git a/surfsense_web/atoms/chat/hitl-edit-panel.atom.ts b/surfsense_web/atoms/chat/hitl-edit-panel.atom.ts index a1c748266..ede6a0134 100644 --- a/surfsense_web/atoms/chat/hitl-edit-panel.atom.ts +++ b/surfsense_web/atoms/chat/hitl-edit-panel.atom.ts @@ -5,7 +5,7 @@ export interface ExtraField { label: string; key: string; value: string; - type: "text" | "email" | "datetime-local" | "textarea"; + type: "text" | "email" | "emails" | "datetime-local" | "textarea"; } interface HitlEditPanelState { diff --git a/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx b/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx index 8851d7821..1d7416837 100644 --- a/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx +++ b/surfsense_web/components/hitl-edit-panel/hitl-edit-panel.tsx @@ -1,8 +1,9 @@ "use client"; +import { TagInput, type Tag as TagType } from "emblor"; import { useAtomValue, useSetAtom } from "jotai"; import { XIcon } from "lucide-react"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { closeHitlEditPanelAtom, hitlEditPanelAtom, @@ -16,6 +17,86 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { useMediaQuery } from "@/hooks/use-media-query"; +function parseEmailsToTags(value: string): TagType[] { + if (!value.trim()) return []; + return value + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + .map((email, i) => ({ id: `${Date.now()}-${i}`, text: email })); +} + +function tagsToEmailString(tags: TagType[]): string { + return tags.map((t) => t.text).join(", "); +} + +function EmailsTagField({ + id, + value, + onChange, + placeholder, +}: { + id: string; + value: string; + onChange: (value: string) => void; + placeholder?: string; +}) { + const [tags, setTags] = useState(() => parseEmailsToTags(value)); + const [activeTagIndex, setActiveTagIndex] = useState(null); + const isInitialMount = useRef(true); + + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false; + return; + } + onChange(tagsToEmailString(tags)); + }, [tags, onChange]); + + const handleSetTags = useCallback( + (newTags: TagType[] | ((prev: TagType[]) => TagType[])) => { + setTags((prev) => + typeof newTags === "function" ? newTags(prev) : newTags + ); + }, + [] + ); + + const handleAddTag = useCallback( + (text: string) => { + const trimmed = text.trim(); + if (!trimmed) return; + if (tags.some((tag) => tag.text === trimmed)) return; + const newTag: TagType = { id: Date.now().toString(), text: trimmed }; + setTags((prev) => [...prev, newTag]); + }, + [tags] + ); + + return ( + + ); +} + export function HitlEditPanelContent({ title: initialTitle, content: initialContent, @@ -85,7 +166,14 @@ export function HitlEditPanelContent({ - {field.type === "textarea" ? ( + {field.type === "emails" ? ( + handleExtraFieldChange(field.key, v)} + placeholder={`Add ${field.label.toLowerCase()}`} + /> + ) : field.type === "textarea" ? (