From 638b15fb40bddd7e03224bc5b9328b5f5df19d81 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 15 Apr 2026 01:23:15 -0600 Subject: [PATCH] fix web chat features --- web/app/ai-chat/page.js | 16 ++ web/app/chat/page.js | 392 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 378 insertions(+), 30 deletions(-) diff --git a/web/app/ai-chat/page.js b/web/app/ai-chat/page.js index dc892c74..32bb38e9 100644 --- a/web/app/ai-chat/page.js +++ b/web/app/ai-chat/page.js @@ -123,6 +123,22 @@ function AiChatPage() { headers: { Authorization: `Bearer ${token}` }, }); + try { + for (const msg of messages) { + const prefix = msg.role === "assistant" ? "[AI Assistant] " : "[Customer] "; + await fetch(`${API_BASE}/api/v1/chat/conversations/${conv.id}/messages`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ content: prefix + msg.content }), + }); + } + } catch { + // Navigate anyway — agent has partial context + } + setHumanRequested(true); router.push(`/chat?id=${conv.id}`); } catch { diff --git a/web/app/chat/page.js b/web/app/chat/page.js index 3a75f41c..0bc1bf26 100644 --- a/web/app/chat/page.js +++ b/web/app/chat/page.js @@ -20,12 +20,17 @@ function ChatPage() { const [sending, setSending] = useState(false); const [loadingConv, setLoadingConv] = useState(true); const [error, setError] = useState(null); + const [conversations, setConversations] = useState([]); + const [convsLoading, setConvsLoading] = useState(false); + const [showSidebar, setShowSidebar] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); const messagesEndRef = useRef(null); const messagesAreaRef = useRef(null); const inputRef = useRef(null); const pollRef = useRef(null); const lastMessageIdRef = useRef(null); + const fileInputRef = useRef(null); useEffect(() => { if (!authLoading && !user) { @@ -88,6 +93,23 @@ function ChatPage() { } }, [token]); + const fetchConversations = useCallback(async () => { + if (!token) return; + setConvsLoading(true); + try { + const res = await fetch(`${API_BASE}/api/v1/chat/conversations`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) return; + const data = await res.json(); + setConversations(Array.isArray(data) ? data : (data.content ?? [])); + } catch { + // silent + } finally { + setConvsLoading(false); + } + }, [token]); + const startPolling = useCallback((convId) => { if (pollRef.current) clearInterval(pollRef.current); pollRef.current = setInterval(async () => { @@ -168,11 +190,22 @@ function ChatPage() { }; }, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, startPolling]); + useEffect(() => { + if (showSidebar && token) fetchConversations(); + }, [showSidebar, token, fetchConversations]); + async function handleSend(e) { e?.preventDefault(); const text = input.trim(); - if (!text || sending || !conversation) return; + if ((!text && !selectedFile) || sending || !conversation) return; + if (selectedFile) { + await handleSendAttachment(text); + } else { + await handleSendText(text); + } + } + async function handleSendText(text) { setInput(""); setSending(true); setError(null); @@ -214,6 +247,59 @@ function ChatPage() { } } + async function handleSendAttachment(optionalText) { + setSending(true); + setError(null); + const file = selectedFile; + setSelectedFile(null); + setInput(""); + + try { + const formData = new FormData(); + formData.append("file", file); + if (optionalText) formData.append("content", optionalText); + + const res = await fetch( + `${API_BASE}/api/v1/chat/conversations/${conversation.id}/attachments`, + { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + body: formData, + } + ); + + if (res.status === 401) { + router.push("/login?next=" + encodeURIComponent("/chat")); + return; + } + + if (!res.ok) { + const data = await res.json().catch(() => null); + setError(data?.message || "Failed to send attachment."); + setSelectedFile(file); + setInput(optionalText); + return; + } + + const msg = await res.json(); + setMessages((prev) => prev.some((m) => m.id === msg.id) ? prev : [...prev, msg]); + lastMessageIdRef.current = msg.id; + } catch { + setError("Network error. Please try again."); + setSelectedFile(file); + setInput(optionalText); + } finally { + setSending(false); + inputRef.current?.focus(); + } + } + + function handleFileChange(e) { + const file = e.target.files?.[0]; + if (file) setSelectedFile(file); + e.target.value = ""; + } + function handleKeyDown(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); @@ -258,6 +344,19 @@ function ChatPage() { } } + async function switchConversation(convId) { + if (pollRef.current) clearInterval(pollRef.current); + setMessages([]); + setError(null); + setLoadingConv(true); + await fetchConversation(convId); + await fetchMessages(convId); + setLoadingConv(false); + startPolling(convId); + router.replace(`/chat?id=${convId}`, { scroll: false }); + setShowSidebar(false); + } + if (authLoading || loadingConv) { return (
@@ -292,6 +391,43 @@ function ChatPage() {
+
+ {showSidebar && ( +
+
+ All Conversations + +
+ {convsLoading &&

Loading...

} + {!convsLoading && conversations.length === 0 && ( +

No conversations yet.

+ )} +
+ {conversations.map((conv) => ( + + ))} +
+ +
+ )} +
{!conversation ? (
💬
@@ -301,6 +437,9 @@ function ChatPage() { + @@ -320,13 +459,21 @@ function ChatPage() {
- +
+ + +
{!hasStaff && !hasStaffMessage && !isClosed && ( @@ -413,31 +560,62 @@ function ChatPage() { ) : (
-