mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-03 04:47:29 +08:00
webui chat: restore history loading and add channel/session-scoped conversation selector
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ChatItem[]>([]);
|
||||
const [msg, setMsg] = useState('');
|
||||
const [fileSelected, setFileSelected] = useState(false);
|
||||
const [sessionKey, setSessionKey] = useState('main');
|
||||
const chatEndRef = useRef<HTMLDivElement>(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 (
|
||||
<div className="flex h-full">
|
||||
<div className="flex-1 flex flex-col bg-zinc-950/50">
|
||||
<div className="px-4 py-3 border-b border-zinc-800 flex items-center justify-between">
|
||||
<h2 className="text-sm text-zinc-300 font-medium">Main Agent</h2>
|
||||
<div className="px-4 py-3 border-b border-zinc-800 flex items-center justify-between gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-sm text-zinc-300 font-medium">Session</h2>
|
||||
<select value={sessionKey} onChange={(e)=>setSessionKey(e.target.value)} className="bg-zinc-900 border border-zinc-700 rounded px-2 py-1 text-xs text-zinc-200">
|
||||
{(sessions || []).map((s:any)=> <option key={s.key} value={s.key}>{s.key}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<button onClick={loadHistory} className="flex items-center gap-1 px-2 py-1 text-xs rounded bg-zinc-800 hover:bg-zinc-700"><RefreshCw className="w-3 h-3"/>Reload History</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user