From c080fc66cf886f57c21e07a4ed4c0b8dec183429 Mon Sep 17 00:00:00 2001 From: DBT Date: Fri, 27 Feb 2026 05:39:31 +0000 Subject: [PATCH] webui chat: restore history loading and add channel/session-scoped conversation selector --- pkg/nodes/registry_server.go | 59 ++++++++++++++++++++++++++++---- webui/src/context/AppContext.tsx | 2 +- webui/src/pages/Chat.tsx | 26 ++++++++++---- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/pkg/nodes/registry_server.go b/pkg/nodes/registry_server.go index e7f96fa..378cdb4 100644 --- a/pkg/nodes/registry_server.go +++ b/pkg/nodes/registry_server.go @@ -420,7 +420,13 @@ func (s *RegistryServer) handleWebUIChat(w http.ResponseWriter, r *http.Request) http.Error(w, "invalid json", http.StatusBadRequest) return } - session := "main" + session := strings.TrimSpace(body.Session) + if session == "" { + session = strings.TrimSpace(r.URL.Query().Get("session")) + } + if session == "" { + session = "main" + } prompt := strings.TrimSpace(body.Message) if strings.TrimSpace(body.Media) != "" { if prompt != "" { @@ -445,7 +451,10 @@ func (s *RegistryServer) handleWebUIChatHistory(w http.ResponseWriter, r *http.R http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - session := "main" + session := strings.TrimSpace(r.URL.Query().Get("session")) + if session == "" { + session = "main" + } if s.onChatHistory == nil { _ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "session": session, "messages": []interface{}{}}) return @@ -480,7 +489,13 @@ func (s *RegistryServer) handleWebUIChatStream(w http.ResponseWriter, r *http.Re http.Error(w, "invalid json", http.StatusBadRequest) return } - session := "main" + session := strings.TrimSpace(body.Session) + if session == "" { + session = strings.TrimSpace(r.URL.Query().Get("session")) + } + if session == "" { + session = "main" + } prompt := strings.TrimSpace(body.Message) if strings.TrimSpace(body.Media) != "" { if prompt != "" { @@ -1210,11 +1225,43 @@ func (s *RegistryServer) handleWebUISessions(w http.ResponseWriter, r *http.Requ http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - _ = os.MkdirAll(filepath.Join(filepath.Dir(strings.TrimSpace(s.workspacePath)), "agents", "main", "sessions"), 0755) + sessionsDir := filepath.Join(filepath.Dir(strings.TrimSpace(s.workspacePath)), "agents", "main", "sessions") + _ = os.MkdirAll(sessionsDir, 0755) type item struct { - Key string `json:"key"` + Key string `json:"key"` + Channel string `json:"channel,omitempty"` } - _ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "sessions": []item{{Key: "main"}}}) + out := make([]item, 0, 16) + entries, err := os.ReadDir(sessionsDir) + if err == nil { + seen := map[string]struct{}{} + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasSuffix(name, ".jsonl") || strings.Contains(name, ".deleted.") { + continue + } + key := strings.TrimSuffix(name, ".jsonl") + if strings.TrimSpace(key) == "" { + continue + } + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + channel := "" + if i := strings.Index(key, ":"); i > 0 { + channel = key[:i] + } + out = append(out, item{Key: key, Channel: channel}) + } + } + if len(out) == 0 { + out = append(out, item{Key: "main", Channel: "main"}) + } + _ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "sessions": out}) } func (s *RegistryServer) handleWebUIMemory(w http.ResponseWriter, r *http.Request) { diff --git a/webui/src/context/AppContext.tsx b/webui/src/context/AppContext.tsx index c841a5a..087301d 100644 --- a/webui/src/context/AppContext.tsx +++ b/webui/src/context/AppContext.tsx @@ -134,7 +134,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children if (!r.ok) throw new Error('Failed to load sessions'); const j = await r.json(); const arr = Array.isArray(j.sessions) ? j.sessions : []; - setSessions(arr.map((s: any) => ({ key: s.key, title: s.key }))); + setSessions(arr.map((s: any) => ({ key: s.key, title: s.title || s.key }))); setIsGatewayOnline(true); } catch (e) { setIsGatewayOnline(false); diff --git a/webui/src/pages/Chat.tsx b/webui/src/pages/Chat.tsx index 526cdd0..f92ab6a 100644 --- a/webui/src/pages/Chat.tsx +++ b/webui/src/pages/Chat.tsx @@ -7,10 +7,11 @@ import { ChatItem } from '../types'; const Chat: React.FC = () => { const { t } = useTranslation(); - const { q } = useAppContext(); + const { q, sessions } = useAppContext(); const [chat, setChat] = useState([]); const [msg, setMsg] = useState(''); const [fileSelected, setFileSelected] = useState(false); + const [sessionKey, setSessionKey] = useState('main'); const chatEndRef = useRef(null); useEffect(() => { @@ -19,7 +20,8 @@ const Chat: React.FC = () => { const loadHistory = async () => { try { - const r = await fetch(`/webui/api/chat/history${q}`); + const qs = q ? `${q}&session=${encodeURIComponent(sessionKey)}` : `?session=${encodeURIComponent(sessionKey)}`; + const r = await fetch(`/webui/api/chat/history${qs}`); if (!r.ok) return; const j = await r.json(); const arr = Array.isArray(j.messages) ? j.messages : []; @@ -78,7 +80,7 @@ const Chat: React.FC = () => { const response = await fetch(`/webui/api/chat/stream${q}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ session: 'main', message: currentMsg, media }), + body: JSON.stringify({ session: sessionKey, message: currentMsg, media }), }); if (!response.ok || !response.body) throw new Error('Chat request failed'); @@ -110,13 +112,25 @@ const Chat: React.FC = () => { useEffect(() => { loadHistory(); - }, [q]); + }, [q, sessionKey]); + + useEffect(() => { + if (!sessions || sessions.length === 0) return; + if (!sessions.some(s => s.key === sessionKey)) { + setSessionKey(sessions[0].key); + } + }, [sessions]); return (
-
-

Main Agent

+
+
+

Session

+ +