CTA / frontend /src /app /graph /page.tsx
TheQuantEd's picture
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>
);
}