From e7e806b13548faae8ef514293e633c7e028b1c58 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 12 Feb 2026 19:01:22 -0700 Subject: [PATCH] Add chat history --- web/public/locales/en/views/chat.json | 4 + web/src/pages/Chat.tsx | 117 +++++++++++++++++++++++--- web/src/utils/i18n.ts | 1 + 3 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 web/public/locales/en/views/chat.json diff --git a/web/public/locales/en/views/chat.json b/web/public/locales/en/views/chat.json new file mode 100644 index 000000000..d85fdb4ac --- /dev/null +++ b/web/public/locales/en/views/chat.json @@ -0,0 +1,4 @@ +{ + "placeholder": "Ask anything...", + "error": "Something went wrong. Please try again." +} diff --git a/web/src/pages/Chat.tsx b/web/src/pages/Chat.tsx index a642668c6..f8802da45 100644 --- a/web/src/pages/Chat.tsx +++ b/web/src/pages/Chat.tsx @@ -1,26 +1,121 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { FaArrowUpLong } from "react-icons/fa6"; +import { useTranslation } from "react-i18next"; +import { useState, useCallback } from "react"; +import axios from "axios"; + +type ChatMessage = { role: "user" | "assistant"; content: string }; export default function ChatPage() { + const { t } = useTranslation(["views/chat"]); + const [input, setInput] = useState(""); + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const sendMessage = useCallback(async () => { + const text = input.trim(); + if (!text || isLoading) return; + + const userMessage: ChatMessage = { role: "user", content: text }; + setInput(""); + setError(null); + setMessages((prev) => [...prev, userMessage]); + setIsLoading(true); + + try { + const apiMessages = [...messages, userMessage].map((m) => ({ + role: m.role, + content: m.content, + })); + const { data } = await axios.post<{ + message: { role: string; content: string | null }; + }>("chat/completion", { messages: apiMessages }); + + const content = data.message?.content ?? ""; + setMessages((prev) => [ + ...prev, + { role: "assistant", content: content || " " }, + ]); + } catch { + setError(t("error")); + } finally { + setIsLoading(false); + } + }, [input, isLoading, messages, t]); + return (
-
- +
+ {messages.map((msg, i) => ( +
+ {msg.content} +
+ ))} + {error && ( +

+ {error} +

+ )} +
+
); } -function ChatEntry() { +type ChatEntryProps = { + input: string; + setInput: (value: string) => void; + sendMessage: () => void; + isLoading: boolean; + placeholder: string; +}; + +function ChatEntry({ + input, + setInput, + sendMessage, + isLoading, + placeholder, +}: ChatEntryProps) { + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }; + return ( -
- -
-
-
diff --git a/web/src/utils/i18n.ts b/web/src/utils/i18n.ts index f149f467e..e987cef4d 100644 --- a/web/src/utils/i18n.ts +++ b/web/src/utils/i18n.ts @@ -46,6 +46,7 @@ i18n "components/icons", "components/player", "views/events", + "views/chat", "views/explore", "views/live", "views/settings",