"use client"; import React, { useCallback, useEffect, useMemo, useRef } from "react"; import { GripVertical, Loader2, MessageSquare, Minus, Pencil, Plus, Send, Sparkles, Trash2, X, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { notify } from "@/lib/notifications/store"; import { closeRefine, discardDraftChanges, openRefine, saveRefineSession, sendRefineChat, sessionHasChanges, setChatInput, setDraft, setRefineOpen, setRefineTab, } from "@/lib/refine-session/store"; import { useRefineSession } from "@/lib/refine-session/use-refine-session"; import { SEARCH_INTENTS, type ResearchMap, type SearchIntent, type SuggestedTag, } from "@/lib/types/research"; import { ResearchMapSection } from "@/components/research-map-section"; import { cn } from "@/lib/utils"; type Tab = "edit" | "chat"; interface ResearchMapRefinePanelProps { topicId: string; topicLabel: string; } export function ResearchMapRefinePanel({ topicId, topicLabel }: ResearchMapRefinePanelProps) { const session = useRefineSession(topicId); const chatEndRef = useRef(null); const chatInputRef = useRef(null); const open = session?.open ?? false; const tab = (session?.tab ?? "chat") as Tab; const draft = session?.draft ?? null; const chatting = session?.chatting ?? false; const saving = session?.saving ?? false; const chatInput = session?.chatInput ?? ""; const messages = useMemo(() => session?.messages ?? [], [session?.messages]); const hasChanges = sessionHasChanges(topicId); const clearChatInputDom = useCallback(() => { if (chatInputRef.current) { chatInputRef.current.value = ""; } }, []); useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, chatting]); useEffect(() => { if (!chatting && chatInput === "") { clearChatInputDom(); } }, [chatting, chatInput, clearChatInputDom]); function handleOpen() { if (!draft) { notify({ type: "warning", title: "請先分析主題" }); return; } openRefine(topicId); } function handleClose() { if (hasChanges && !confirm("有未套用的變更,確定關閉?")) return; closeRefine(topicId, true); } function handleDiscard() { if (!confirm("捨棄所有未套用變更?")) return; discardDraftChanges(topicId); } async function handleSave() { if (!hasChanges) return; await saveRefineSession(topicId); } function handleChatSend() { const userMsg = chatInput.trim(); if (!draft || !userMsg || chatting) return; clearChatInputDom(); sendRefineChat(topicId, userMsg); } function updateDraft(patch: Partial) { if (!draft) return; setDraft(topicId, { ...draft, ...patch }); } function updateListItem( key: "questions" | "pillars" | "exclusions", index: number, value: string ) { if (!draft) return; const list = [...draft[key]]; list[index] = value; setDraft(topicId, { ...draft, [key]: list }); } function addListItem(key: "questions" | "pillars" | "exclusions") { if (!draft) return; setDraft(topicId, { ...draft, [key]: [...draft[key], ""] }); } function removeListItem(key: "questions" | "pillars" | "exclusions", index: number) { if (!draft) return; setDraft(topicId, { ...draft, [key]: draft[key].filter((_, i) => i !== index) }); } function updateTag(index: number, patch: Partial) { if (!draft) return; const tags = [...draft.suggestedTags]; tags[index] = { ...tags[index], ...patch }; setDraft(topicId, { ...draft, suggestedTags: tags }); } function addTag() { if (!draft) return; setDraft(topicId, { ...draft, suggestedTags: [ ...draft.suggestedTags, { tag: "", reason: "", searchIntent: "知識" as SearchIntent }, ], }); } function removeTag(index: number) { if (!draft) return; setDraft(topicId, { ...draft, suggestedTags: draft.suggestedTags.filter((_, i) => i !== index), }); } const engaged = session?.engaged ?? false; const showFab = !open && draft !== null && engaged; if (!showFab && !open) return null; return ( <> {showFab && ( )} {open && draft && (

微調

{topicLabel}

{hasChanges && ( 未儲存 )} {chatting && ( 背景執行 )}
{tab === "chat" ? (
{messages.length === 0 && (

描述想改什麼,例如調整受眾、加標籤、刪問題

)} {messages.map((msg, i) => (
{msg.content}
))} {chatting && (
調整中…
)}