From 49725362c5a84736602b17e71db28fce606ac7ea Mon Sep 17 00:00:00 2001 From: techjarves Date: Mon, 15 Jun 2026 06:46:46 +0530 Subject: [PATCH] feat: initialize frontend application structure with TextChat and Sidebar components --- app/frontend/src/App.jsx | 67 ++++++++- app/frontend/src/components/Sidebar.jsx | 135 ++++++++++++++++- app/frontend/src/components/TextChat.jsx | 182 ++++++----------------- 3 files changed, 235 insertions(+), 149 deletions(-) diff --git a/app/frontend/src/App.jsx b/app/frontend/src/App.jsx index d354dc9..8c2fa2b 100644 --- a/app/frontend/src/App.jsx +++ b/app/frontend/src/App.jsx @@ -133,6 +133,58 @@ function App() { localStorage.setItem("textSettings", JSON.stringify(textSettings)); }, [textSettings]); + // Lifted Chat History States + const [conversations, setConversations] = useState([]); + const [activeConversationId, setActiveConversationId] = useState(null); + const [showHistory, setShowHistory] = useState(false); // Default hide + + useEffect(() => { + const saved = localStorage.getItem("chat_conversations"); + if (saved) { + try { + setConversations(JSON.parse(saved)); + } catch (_) {} + } + }, []); + + const saveConversationState = useCallback((id, msgs, modelName, newTitle = null) => { + setConversations((prev) => { + const list = [...prev]; + const idx = list.findIndex(c => c.id === id); + if (idx !== -1) { + list[idx] = { + ...list[idx], + messages: msgs, + timestamp: Date.now(), + model: modelName, + ...(newTitle ? { title: newTitle } : {}) + }; + } else { + list.unshift({ + id, + title: newTitle || "Chat Session", + model: modelName, + messages: msgs, + timestamp: Date.now() + }); + } + localStorage.setItem("chat_conversations", JSON.stringify(list)); + return list; + }); + }, []); + + const handleDeleteConversation = useCallback((id, e) => { + if (e) e.stopPropagation(); + setConversations((prev) => { + const filtered = prev.filter((c) => c.id !== id); + localStorage.setItem("chat_conversations", JSON.stringify(filtered)); + return filtered; + }); + if (activeConversationId === id) { + setActiveConversationId(null); + } + }, [activeConversationId]); + // Load hardware specifications on mount useEffect(() => { async function loadSpecs() { @@ -371,8 +423,14 @@ function App() { activeTab={activeTab} setActiveTab={setActiveTab} specs={specs} + conversations={conversations} + activeConversationId={activeConversationId} + setActiveConversationId={setActiveConversationId} + showHistory={showHistory} + setShowHistory={setShowHistory} + onDeleteConversation={handleDeleteConversation} /> - ), [activeTab, specs]); + ), [activeTab, specs, conversations, activeConversationId, showHistory, handleDeleteConversation]); const handleStopServer = useCallback(async () => { if (!serverRunning || isStoppingServer) return; @@ -513,6 +571,13 @@ function App() { setTextSettings={setTextSettings} setActiveModel={setActiveModel} setServerRunning={setServerRunning} + conversations={conversations} + setConversations={setConversations} + activeConversationId={activeConversationId} + setActiveConversationId={setActiveConversationId} + showHistory={showHistory} + setShowHistory={setShowHistory} + saveConversationState={saveConversationState} /> diff --git a/app/frontend/src/components/Sidebar.jsx b/app/frontend/src/components/Sidebar.jsx index b9249ff..db86544 100644 --- a/app/frontend/src/components/Sidebar.jsx +++ b/app/frontend/src/components/Sidebar.jsx @@ -1,7 +1,17 @@ import React, { memo } from "react"; -import { Image, FolderDown, MessageSquare, Settings, Shield, Terminal } from "lucide-react"; +import { Image, FolderDown, MessageSquare, Settings, Shield, Terminal, ChevronDown, ChevronUp, Trash2 } from "lucide-react"; -function Sidebar({ activeTab, setActiveTab, specs }) { +function Sidebar({ + activeTab, + setActiveTab, + specs, + conversations = [], + activeConversationId, + setActiveConversationId, + showHistory, + setShowHistory, + onDeleteConversation +}) { return (
@@ -21,12 +31,121 @@ function Sidebar({ activeTab, setActiveTab, specs }) { Image Generator
-
setActiveTab("chat")} - > - - Text Chat +
+
setActiveTab("chat")} + style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%", boxSizing: "border-box" }} + > +
+ + Text Chat +
+ +
+ + {/* Sidebar Chat History List */} + {showHistory && ( +
+ {conversations.length === 0 ? ( +
+ No saved chats +
+ ) : ( + conversations.map((conv) => { + const isActive = activeConversationId === conv.id; + return ( +
{ + setActiveConversationId(conv.id); + setActiveTab("chat"); + }} + style={{ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + padding: "6px 8px 6px 10px", + borderRadius: "var(--md-shape-corner-small)", + fontSize: "0.78rem", + cursor: "pointer", + background: isActive ? "var(--md-sys-color-secondary-container)" : "transparent", + color: isActive ? "var(--md-sys-color-on-secondary-container)" : "var(--md-sys-color-on-surface-variant)", + border: isActive ? "1px solid var(--md-sys-color-outline-variant)" : "1px solid transparent", + transition: "background 0.2s" + }} + className="sidebar-history-item" + title={conv.title} + > + + {conv.title} + + +
+ ); + }) + )} +
+ )}
{ - const saved = localStorage.getItem("chat_conversations"); - if (saved) { - try { - setConversations(JSON.parse(saved)); - } catch (_) {} + if (activeConversationId) { + const conv = conversations.find(c => c.id === activeConversationId); + if (conv) { + setMessages(conv.messages); + if (conv.model && models.some(m => m.filename === conv.model)) { + setSelectedModel(conv.model); + } + const total = conv.messages.reduce((sum, m) => { + const text = Array.isArray(m.content) + ? m.content.map(c => c.text || "").join(" ") + : (m.content || ""); + return sum + text.split(/\s+/).length; + }, 0); + setTokenUsage({ + prompt_tokens: Math.round(total * 0.7), + completion_tokens: Math.round(total * 0.3), + total_tokens: total + }); + } + } else { + setMessages([]); + setTokenUsage({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }); } - }, []); + }, [activeConversationId, conversations, models]); const refresh = useCallback(async () => { const [nextModels, nextStatus] = await Promise.all([listLlmModels(), getLlmStatus()]); @@ -197,72 +226,8 @@ function TextChat({ specs, showAlert, showConfirm, textSettings, setTextSettings setSelectedModel(""); }; - const saveConversationState = (id, msgs, modelName, newTitle = null) => { - const saved = localStorage.getItem("chat_conversations"); - let list = []; - if (saved) { - try { - list = JSON.parse(saved); - } catch (_) {} - } - const idx = list.findIndex(c => c.id === id); - if (idx !== -1) { - list[idx].messages = msgs; - list[idx].timestamp = Date.now(); - list[idx].model = modelName; - if (newTitle) list[idx].title = newTitle; - } else { - list.unshift({ - id, - title: newTitle || "Chat Session", - model: modelName, - messages: msgs, - timestamp: Date.now() - }); - } - localStorage.setItem("chat_conversations", JSON.stringify(list)); - setConversations(list); - }; - const handleNewChat = () => { setActiveConversationId(null); - setMessages([]); - setTokenUsage({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }); - }; - - const handleSelectConversation = (conv) => { - setActiveConversationId(conv.id); - setMessages(conv.messages); - if (conv.model && models.some(m => m.filename === conv.model)) { - setSelectedModel(conv.model); - } - // Try to compute approximate token usage from messages - const total = conv.messages.reduce((sum, m) => { - const text = Array.isArray(m.content) - ? m.content.map(c => c.text || "").join(" ") - : (m.content || ""); - return sum + text.split(/\s+/).length; - }, 0); - setTokenUsage({ - prompt_tokens: Math.round(total * 0.7), - completion_tokens: Math.round(total * 0.3), - total_tokens: total - }); - }; - - const handleDeleteConversation = (id, e) => { - e.stopPropagation(); - const saved = localStorage.getItem("chat_conversations"); - if (!saved) return; - try { - const list = JSON.parse(saved); - const filtered = list.filter(c => c.id !== id); - localStorage.setItem("chat_conversations", JSON.stringify(filtered)); - setConversations(filtered); - if (activeConversationId === id) { - handleNewChat(); - } - } catch (_) {} }; const sendMessage = async () => { @@ -351,70 +316,7 @@ function TextChat({ specs, showAlert, showConfirm, textSettings, setTextSettings }; return ( -
- {/* Collapsible Chat History Sidebar */} - {showHistory && ( - - )} - +