Spaces:
Sleeping
Sleeping
Initial deployment: ClinicalMatch AI v2.0 — FHIR R4 · MCP (9 tools) · A2A workflow · SHARP compliance · 100k synthetic patients · Neo4j graph · GraphRAG chatbot
59abb4f | "use client"; | |
| import { useState } from "react"; | |
| import { graphQuery, getGraphStats } from "@/lib/api"; | |
| import { MessageSquare, Loader2, Database } from "lucide-react"; | |
| import { useEffect } from "react"; | |
| const SAMPLE_QUESTIONS = [ | |
| "Which patients are eligible for breast cancer trials?", | |
| "What trials are in Phase II?", | |
| "List all patients with HER2 positive biomarker", | |
| "How many active trials are there for prostate cancer?", | |
| "Which study sites have the most active trials?", | |
| ]; | |
| export default function GraphPage() { | |
| const [question, setQuestion] = useState(""); | |
| const [response, setResponse] = useState(""); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(""); | |
| const [stats, setStats] = useState<any>(null); | |
| useEffect(() => { | |
| getGraphStats().then(setStats).catch(() => {}); | |
| }, []); | |
| const handleQuery = async (q = question) => { | |
| if (!q.trim()) return; | |
| setLoading(true); | |
| setError(""); | |
| setResponse(""); | |
| setQuestion(q); | |
| try { | |
| const data = await graphQuery(q); | |
| setResponse(data.response); | |
| } catch (e: any) { | |
| setError(e.message); | |
| } | |
| setLoading(false); | |
| }; | |
| return ( | |
| <div className="p-6 max-w-3xl mx-auto"> | |
| <div className="mb-6"> | |
| <h1 className="text-2xl font-bold text-slate-900 mb-1">Graph RAG</h1> | |
| <p className="text-slate-500 text-sm">Ask natural language questions about the clinical trial knowledge graph</p> | |
| </div> | |
| {stats && ( | |
| <div className="flex gap-3 mb-6"> | |
| {Object.entries(stats).map(([k, v]: any) => ( | |
| <div key={k} className="bg-white border border-slate-200 rounded-lg px-4 py-2.5 flex items-center gap-2"> | |
| <Database className="w-4 h-4 text-indigo-400" /> | |
| <span className="text-sm font-bold text-slate-800">{v}</span> | |
| <span className="text-xs text-slate-500 capitalize">{k}</span> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| <div className="mb-4"> | |
| <p className="text-xs font-semibold text-slate-500 mb-2">Sample Questions</p> | |
| <div className="flex flex-wrap gap-2"> | |
| {SAMPLE_QUESTIONS.map((q) => ( | |
| <button | |
| key={q} | |
| onClick={() => handleQuery(q)} | |
| className="text-xs bg-indigo-50 text-indigo-700 hover:bg-indigo-100 px-3 py-1.5 rounded-full transition-colors" | |
| > | |
| {q} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="flex gap-3 mb-6"> | |
| <input | |
| type="text" | |
| value={question} | |
| onChange={(e) => setQuestion(e.target.value)} | |
| onKeyDown={(e) => e.key === "Enter" && handleQuery()} | |
| placeholder="Ask anything about patients, trials, or biomarkers..." | |
| className="flex-1 border border-slate-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white" | |
| /> | |
| <button | |
| onClick={() => handleQuery()} | |
| disabled={loading || !question.trim()} | |
| className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium hover:bg-indigo-700 disabled:opacity-50 transition-colors" | |
| > | |
| {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <MessageSquare className="w-4 h-4" />} | |
| {loading ? "Querying..." : "Ask"} | |
| </button> | |
| </div> | |
| {error && ( | |
| <div className="bg-red-50 border border-red-200 text-red-700 rounded-lg px-4 py-3 text-sm mb-4">{error}</div> | |
| )} | |
| {response && ( | |
| <div className="bg-white rounded-xl border border-slate-200 p-5"> | |
| <div className="flex items-center gap-2 mb-3"> | |
| <MessageSquare className="w-4 h-4 text-indigo-500" /> | |
| <span className="text-xs font-semibold text-slate-600">Response</span> | |
| </div> | |
| <p className="text-sm text-slate-700 leading-relaxed whitespace-pre-wrap">{response}</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |