File size: 3,266 Bytes
6b62834
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { useState, useEffect, useCallback } from 'react';
import Sidebar from './components/Sidebar';
import ChatPanel from './components/ChatPanel';
import KBPanel from './components/KBPanel';
import SettingsPanel from './components/SettingsPanel';
import { api } from './api';
import './App.css';

const PANELS = { chat: 'Chat', kb: 'Knowledge Base', settings: 'Settings' };

export default function App() {
  const [panel, setPanel] = useState('chat');
  const [connected, setConnected] = useState(false);
  const [sessionId, setSessionId] = useState(localStorage.getItem('agr_session_id') || '');
  const [chatKey, setChatKey] = useState(0);
  const [clearKey, setClearKey] = useState(0);

  useEffect(() => {
    fetch('/health').then(() => setConnected(true)).catch(() => setConnected(false));
  }, []);

  // Eagerly create/restore a session on mount so ChatPanel never sees a
  // sessionId transition during send — that transition would trigger the
  // useEffect([sessionId]) cleanup which clears messages and aborts streams.
  useEffect(() => {
    if (!sessionId) {
      fetch('/api/v1/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: 'web_ui' }) })
        .then(r => r.json())
        .then(({ session_id }) => {
          setSessionId(session_id);
          localStorage.setItem('agr_session_id', session_id);
        })
        .catch(() => {
          const sid = 'web_' + Date.now().toString(36);
          setSessionId(sid);
        });
    }
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const ensureSession = useCallback(async () => {
    if (sessionId) return sessionId;
    try {
      const res = await fetch('/api/v1/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: 'web_ui' }) });
      const { session_id } = await res.json();
      setSessionId(session_id);
      localStorage.setItem('agr_session_id', session_id);
      return session_id;
    } catch { const sid = 'web_' + Date.now().toString(36); setSessionId(sid); return sid; }
  }, [sessionId]);

  return (
    <div className="app">
      <Sidebar panel={panel} onNavigate={setPanel} connected={connected} sessionId={sessionId} onSessionChange={setSessionId} />
      <main className="main">
        <header className="topbar">
          <span className="topbar-title">{PANELS[panel] || panel}</span>
          <span className="topbar-spacer" />
          <button className="icon-btn" onClick={() => { setSessionId(''); localStorage.removeItem('agr_session_id'); setChatKey(k => k + 1); setPanel('chat'); }} title="New Chat">+</button>
          <button className="icon-btn" onClick={async () => {
            if (sessionId) await api.clearSessionMessages(sessionId);
            setClearKey(k => k + 1);
            setPanel('chat');
          }} title="Clear Messages">🗑</button>
        </header>
        <div className="content">
          {panel === 'chat' && <ChatPanel key={`${chatKey}-${clearKey}`} sessionId={sessionId} ensureSession={ensureSession} setSessionId={setSessionId} />}
          {panel === 'kb' && <KBPanel />}
          {panel === 'settings' && <SettingsPanel />}
        </div>
      </main>
    </div>
  );
}