File size: 4,142 Bytes
b2c1c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48954a5
b2c1c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48954a5
b2c1c67
 
 
48954a5
b2c1c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { useCallback, useEffect, useState } from "react";
import AuthPage from "./components/AuthPage.jsx";
import Sidebar from "./components/Sidebar.jsx";
import ChatView from "./components/ChatView.jsx";
import AnalyticsView from "./components/AnalyticsView.jsx";
import DocumentsPanel from "./components/DocumentsPanel.jsx";
import DemoBanner from "./components/DemoBanner.jsx";
import { api, getTokens, setTokens, setUnauthorizedHandler } from "./lib/api.js";

export default function App() {
  const [user, setUser] = useState(null);
  const [booting, setBooting] = useState(true);
  const [appInfo, setAppInfo] = useState(null);
  const [sessions, setSessions] = useState([]);
  const [activeSessionId, setActiveSessionId] = useState(null);
  const [view, setView] = useState("chat");
  const [docsOpen, setDocsOpen] = useState(false);

  const logout = useCallback(() => {
    setTokens(null);
    setUser(null);
    setSessions([]);
    setActiveSessionId(null);
    setView("chat");
  }, []);

  useEffect(() => {
    setUnauthorizedHandler(() => logout());
    (async () => {
      try {
        const info = await fetch("/api/info").then((r) => r.json());
        setAppInfo(info);
      } catch {}
      if (getTokens()) {
        try {
          setUser(await api("/api/auth/me"));
        } catch {}
      }
      setBooting(false);
    })();
  }, [logout]);

  const refreshSessions = useCallback(async () => {
    const list = await api("/api/sessions");
    setSessions(list);
    return list;
  }, []);

  useEffect(() => {
    if (user) refreshSessions().catch(() => {});
  }, [user, refreshSessions]);

  const newChat = useCallback(async () => {
    const session = await api("/api/sessions", { method: "POST", body: {} });
    setSessions((prev) => [session, ...prev]);
    setActiveSessionId(session.id);
    setView("chat");
    return session;
  }, []);

  const deleteSession = useCallback(
    async (id) => {
      await api(`/api/sessions/${id}`, { method: "DELETE" });
      setSessions((prev) => prev.filter((s) => s.id !== id));
      if (activeSessionId === id) setActiveSessionId(null);
    },
    [activeSessionId]
  );

  const renameSession = useCallback(async (id, title) => {
    const updated = await api(`/api/sessions/${id}`, {
      method: "PATCH",
      body: { title },
    });
    setSessions((prev) => prev.map((s) => (s.id === id ? updated : s)));
  }, []);

  const patchSessionLocal = useCallback((id, patch) => {
    setSessions((prev) =>
      prev.map((s) => (s.id === id ? { ...s, ...patch } : s))
    );
  }, []);

  if (booting) {
    return (
      <div className="app-shell" style={{ alignItems: "center", justifyContent: "center" }}>
        <div className="spinner" />
      </div>
    );
  }

  if (!user) {
    return <AuthPage onAuthed={setUser} appInfo={appInfo} />;
  }

  return (
    <div className="app-shell">
      <Sidebar
        user={user}
        sessions={sessions}
        activeSessionId={activeSessionId}
        view={view}
        onSelectSession={(id) => {
          setActiveSessionId(id);
          setView("chat");
        }}
        onNewChat={() => newChat().catch(() => {})}
        onDeleteSession={deleteSession}
        onRenameSession={renameSession}
        onOpenDocs={() => setDocsOpen(true)}
        onShowAnalytics={() => setView("analytics")}
        onLogout={logout}
      />
      <main className="main-column">
        {appInfo?.demo_mode && <DemoBanner appInfo={appInfo} />}
        {view === "analytics" ? (
          <AnalyticsView />
        ) : (
          <ChatView
            appInfo={appInfo}
            sessionId={activeSessionId}
            session={sessions.find((s) => s.id === activeSessionId) || null}
            ensureSession={async () => {
              if (activeSessionId) return activeSessionId;
              const session = await newChat();
              return session.id;
            }}
            onSessionMeta={patchSessionLocal}
            onOpenDocs={() => setDocsOpen(true)}
          />
        )}
      </main>
      {docsOpen && <DocumentsPanel onClose={() => setDocsOpen(false)} />}
    </div>
  );
}