diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index 98dc46d7b..bb6e304df 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -143,6 +143,11 @@ export function ChatSidebar({ const [confirmingDeleteId, setConfirmingDeleteId] = useState( null, ) + const [saveState, setSaveState] = useState<"idle" | "saving" | "saved">( + "idle", + ) + const saveStateRef = useRef<"idle" | "saving" | "saved">("idle") + saveStateRef.current = saveState const messagesContainerRef = useRef(null) const isScrolledToBottomRef = useRef(true) const userJustSentRef = useRef(false) @@ -426,8 +431,48 @@ export function ChatSidebar({ setThreadId(null) setFallbackChatId(newChatId) setInput("") + setSaveState("idle") }, [setThreadId, setMessages]) + // Reset saveState to idle when a new assistant message arrives + useEffect(() => { + if (messages.at(-1)?.role === "assistant") { + setSaveState("idle") + } + }, [messages]) + + // Reset saveState when the active space changes so saved state doesn't carry over + // biome-ignore lint/correctness/useExhaustiveDependencies: chatProject triggers the reset but isn't used inside the effect body + useEffect(() => { + setSaveState("idle") + }, [chatProject]) + + const handleSaveToSpace = useCallback(async () => { + if (saveStateRef.current !== "idle" || messages.length === 0) return + setSaveState("saving") + try { + const response = await fetch(`${chatApiBase}/chat/save-as-document`, { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + chatId: currentChatId, + projectId: chatProject, + messages, + }), + }) + if (!response.ok) throw new Error("Save failed") + setSaveState("saved") + analytics.chatSavedToSpace({ + chat_id: currentChatId, + project_id: chatProject, + }) + } catch (error) { + console.error("Failed to save chat:", error) + setSaveState("idle") + } + }, [chatApiBase, messages, currentChatId, chatProject]) + const fetchThreads = useCallback(async () => { setIsLoadingThreads(true) try { @@ -905,6 +950,20 @@ export function ChatSidebar({ const chatToolbarActions = (
+ {messages.length > 0 && !isAutoChatSpace && ( + + )}