"use client"; import { format } from "date-fns"; import { TagInput, type Tag as TagType } from "emblor"; import { useAtomValue, useSetAtom } from "jotai"; import { CalendarIcon, XIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom"; import { closeHitlEditPanelAtom, hitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom"; import { PlateEditor } from "@/components/editor/plate-editor"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; 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); const onChangeRef = useRef(onChange); onChangeRef.current = onChange; useEffect(() => { if (isInitialMount.current) { isInitialMount.current = false; return; } onChangeRef.current(tagsToEmailString(tags)); }, [tags]); 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 ( ); } function parseDateTimeValue(value: string): { date: Date | undefined; time: string } { if (!value) return { date: undefined, time: "09:00" }; try { const d = new Date(value); if (Number.isNaN(d.getTime())) return { date: undefined, time: "09:00" }; return { date: d, time: format(d, "HH:mm"), }; } catch { return { date: undefined, time: "09:00" }; } } function buildLocalDateTimeString(date: Date | undefined, time: string): string { if (!date) return ""; const [hours, minutes] = time.split(":").map(Number); const combined = new Date(date); combined.setHours(hours ?? 9, minutes ?? 0, 0, 0); const y = combined.getFullYear(); const m = String(combined.getMonth() + 1).padStart(2, "0"); const d = String(combined.getDate()).padStart(2, "0"); const h = String(combined.getHours()).padStart(2, "0"); const min = String(combined.getMinutes()).padStart(2, "0"); return `${y}-${m}-${d}T${h}:${min}:00`; } function DateTimePickerField({ id, value, onChange, }: { id: string; value: string; onChange: (value: string) => void; }) { const parsed = useMemo(() => parseDateTimeValue(value), [value]); const [selectedDate, setSelectedDate] = useState(parsed.date); const [time, setTime] = useState(parsed.time); const [open, setOpen] = useState(false); const handleDateSelect = useCallback( (day: Date | undefined) => { setSelectedDate(day); onChange(buildLocalDateTimeString(day, time)); setOpen(false); }, [time, onChange] ); const handleTimeChange = useCallback( (e: React.ChangeEvent) => { const newTime = e.target.value; setTime(newTime); onChange(buildLocalDateTimeString(selectedDate, newTime)); }, [selectedDate, onChange] ); const displayLabel = selectedDate ? `${format(selectedDate, "MMM d, yyyy")} at ${time}` : "Pick date & time"; return (
); } export function HitlEditPanelContent({ title: initialTitle, content: initialContent, contentFormat, extraFields, onSave, onClose, showCloseButton = true, }: { title: string; content: string; toolName: string; contentFormat?: "markdown" | "html"; extraFields?: ExtraField[]; onSave: (title: string, content: string, extraFieldValues?: Record) => void; onClose?: () => void; showCloseButton?: boolean; }) { const [editedTitle, setEditedTitle] = useState(initialTitle); const contentRef = useRef(initialContent); const [isSaving, setIsSaving] = useState(false); const [extraFieldValues, setExtraFieldValues] = useState>(() => { if (!extraFields) return {}; const initial: Record = {}; for (const field of extraFields) { initial[field.key] = field.value; } return initial; }); const handleContentChange = useCallback((content: string) => { contentRef.current = content; }, []); const handleExtraFieldChange = useCallback((key: string, value: string) => { setExtraFieldValues((prev) => ({ ...prev, [key]: value })); }, []); const handleSave = useCallback(() => { if (!editedTitle.trim()) return; setIsSaving(true); const extras = extraFields && extraFields.length > 0 ? extraFieldValues : undefined; onSave(editedTitle, contentRef.current, extras); onClose?.(); }, [editedTitle, onSave, onClose, extraFields, extraFieldValues]); return ( <>
setEditedTitle(e.target.value)} placeholder="Untitled" className="flex-1 min-w-0 bg-transparent text-sm font-semibold text-foreground outline-none placeholder:text-muted-foreground" aria-label="Page title" /> {onClose && showCloseButton && ( )}
{extraFields && extraFields.length > 0 && (
{extraFields.map((field) => (
{field.type === "emails" ? ( handleExtraFieldChange(field.key, v)} placeholder={`Add ${field.label.toLowerCase()}`} /> ) : field.type === "datetime-local" ? ( handleExtraFieldChange(field.key, v)} /> ) : field.type === "textarea" ? (