Files
citation_sentinel/client/src/App.jsx

112 lines
3.5 KiB
JavaScript

import { useState } from 'react';
import SourceCorpusList from './components/SourceCorpusList.jsx';
import SourcePanel from './components/SourcePanel.jsx';
import DocumentButtons from './components/DocumentButtons.jsx';
import ChatPanel from './components/ChatPanel.jsx';
import DocumentModal from './components/DocumentModal.jsx';
import { useSourceCorpus } from './hooks/useSourceCorpus.js';
import { generateDocument } from './api/documents.js';
function App() {
const {
sourceCorpora,
activeSourceCorpus,
selectSourceCorpus,
createSourceCorpus,
deleteSourceCorpus,
} = useSourceCorpus();
const [hoverState, setHoverState] = useState(null);
const [sourceCount, setSourceCount] = useState(0);
const [generatingType, setGeneratingType] = useState(null);
const [docModal, setDocModal] = useState({ open: false, type: null, document: null, loading: false });
const [chatReady, setChatReady] = useState(false);
const handleSelectSourceCorpus = (id) => {
setChatReady(false);
selectSourceCorpus(id);
};
const handleSourceHover = (val) => {
if (val === null) {
setHoverState(null);
} else if (typeof val === 'number') {
setHoverState({ instanceId: `sidebar-${val}`, docIndex: val });
} else {
setHoverState(val);
}
};
const handleDocumentRequest = async (type) => {
setGeneratingType(type);
setDocModal({ open: true, type, document: null, loading: true });
try {
const res = await generateDocument(activeSourceCorpus.id, type);
setDocModal({ open: true, type, document: res.document, loading: false });
} catch (err) {
console.error('document generation failed', err);
setDocModal({ open: false, type: null, document: null, loading: false });
} finally {
setGeneratingType(null);
}
};
const hoveredDocIndex = hoverState?.docIndex ?? null;
const hoveredInstanceId = hoverState?.instanceId ?? null;
return (
<div className="app">
<aside className="sidebar">
<SourceCorpusList
sourceCorpora={sourceCorpora}
activeId={activeSourceCorpus?.id}
onSelect={handleSelectSourceCorpus}
onCreate={createSourceCorpus}
onDelete={deleteSourceCorpus}
/>
{activeSourceCorpus && (
<>
<SourcePanel
sourceCorpusId={activeSourceCorpus.id}
hoveredSourceIndex={hoveredDocIndex}
onSourceHover={handleSourceHover}
onSourcesChange={setSourceCount}
>
{chatReady && (
<DocumentButtons
sourceCount={sourceCount}
onRequest={handleDocumentRequest}
generatingType={generatingType}
/>
)}
</SourcePanel>
</>
)}
</aside>
<main className="main">
{activeSourceCorpus ? (
<ChatPanel
sourceCorpusId={activeSourceCorpus.id}
hoveredSource={hoveredInstanceId}
hoveredDocIndex={hoveredDocIndex}
onSourceHover={handleSourceHover}
onFirstResponse={() => setChatReady(true)}
/>
) : (
<p>Select or create a source corpus to get started.</p>
)}
</main>
<DocumentModal
open={docModal.open}
onClose={() => setDocModal({ open: false, type: null, document: null, loading: false })}
type={docModal.type}
document={docModal.document}
loading={docModal.loading}
/>
</div>
);
}
export default App;