diff --git a/frontend/src/App.js b/frontend/src/App.js index 4e3bc1d..e3e04b7 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -11,6 +11,7 @@ function App() { const [apiKey, setApiKey] = useState(''); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); + const [loadingPhase, setLoadingPhase] = useState(null); const [result, setResult] = useState(null); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [darkMode, setDarkMode] = useState(() => { @@ -35,16 +36,17 @@ function App() { const handleSubmit = async (fhirData = {}) => { if ((!query.trim() && !fhirData.has_fhir) || loading) return; - // Close the sidebar automatically on mobile when a query is sent closeSidebar(); const userMessage = { role: 'user', text: query || `FHIR ${fhirData.fhir_resource_type}/${fhirData.fhir_resource_id}` }; + setMessages(prev => [...prev, userMessage]); setQuery(''); setLoading(true); + setLoadingPhase(null); setResult(null); try { @@ -55,12 +57,42 @@ function App() { }); if (!response.ok) throw new Error('Query failed'); - const data = await response.json(); - console.log('full response:', data); - console.log('final_response:', data.final_response); - const final = data.final_response; - setResult(final); - setMessages(prev => [...prev, { role: 'assistant', data: final }]); + + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + + let buffer = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const parts = buffer.split('\n\n'); + + buffer = parts.pop(); + + for (const part of parts) { + const line = part.trim(); + if (line.startsWith('data: ')) { + try { + const data = JSON.parse(line.substring(6)); + + if (data.status) { + setLoadingPhase(data.status); + } + + if (data.final_response) { + setResult(data.final_response); + setMessages(prev => [...prev, { role: 'assistant', data: data.final_response }]); + } + } catch (parseError) { + console.error("JSON Parse Error on complete chunk:", line, parseError); + } + } + } + } } catch (err) { let errorText = 'Failed to connect to the analysis server. Ensure the backend is running.'; @@ -78,6 +110,7 @@ function App() { }]); } finally { setLoading(false); + setLoadingPhase(null); } }; @@ -99,8 +132,8 @@ function App() {
-
@@ -120,10 +153,11 @@ function App() { onQueryChange={setQuery} onSubmit={handleSubmit} loading={loading} + loadingPhase={loadingPhase} /> ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/ChatWindow.js b/frontend/src/components/ChatWindow.js index 1325512..092d149 100644 --- a/frontend/src/components/ChatWindow.js +++ b/frontend/src/components/ChatWindow.js @@ -7,24 +7,33 @@ const FHIR_RESOURCE_TYPES = ['Condition', 'MedicationRequest', 'DiagnosticReport const SAMPLE_PLACEHOLDERS = [ 'e.g. What are the evidence-based treatments for heart failure?', - 'e.g. Are there any contraindications for prescribing Tylenol?', + 'e.g. Are there any contraindications for prescribing Metoprolol?', 'e.g. Summarize the latest clinical guidelines for managing Type 2 Diabetes.', - 'e.g. What is the NNT for statins in primary prevention?' + 'e.g. What is the primary reason to prescribe statins?' ]; -function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) { +const NODE_MESSAGES = { + fhir_input: "Connecting to HAPI FHIR server...", + preprocess_query: "Analyzing query intent...", + pubmed_retrieval: "Querying PubMed abstracts...", + detect_medications: "Scanning for medication entities...", + fda_enrichment: "Retrieving openFDA medication data...", + llm_generation: "Drafting initial clinical response...", + parse_claims: "Extracting testable claims...", + nli_scoring: "Running NLI verification model...", + confidence_scoring: "Calculating overall reliability...", + assembly: "Finalizing evidence-based response..." +}; + +function ChatWindow({ messages, query, onQueryChange, onSubmit, loading, loadingPhase }) { const bottomRef = useRef(null); const textareaRef = useRef(null); const [showFhir, setShowFhir] = useState(false); const [fhirType, setFhirType] = useState('Condition'); const [fhirId, setFhirId] = useState(''); - - // Track which placeholder is currently active const [placeholderIndex, setPlaceholderIndex] = useState(0); - // Swap the placeholder every 5 seconds useEffect(() => { - // Stop the carousel if a message has already been sent if (messages.length > 0) return; const timer = setInterval(() => { @@ -71,7 +80,6 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) { const canSubmit = query.trim() || (showFhir && fhirId.trim()); - // Determine what the current placeholder should be const currentPlaceholder = showFhir ? 'Optional: add a specific question about this resource…' : messages.length > 0 @@ -137,10 +145,21 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) { {loading && (
-
-
-
-
+
+
+
+
+
+
+
+ {NODE_MESSAGES[loadingPhase] || "Initializing analysis server..."} +
)} @@ -159,8 +178,6 @@ function ChatWindow({ messages, query, onQueryChange, onSubmit, loading }) { · HAPI R4 Server
- - {/* Added flexWrap: 'wrap' here so the inputs stack cleanly on small mobile screens */}