diff --git a/frontend/app/search/[bmid]/page.tsx b/frontend/app/search/[bmid]/page.tsx index bd0a7ee..6fdb183 100644 --- a/frontend/app/search/[bmid]/page.tsx +++ b/frontend/app/search/[bmid]/page.tsx @@ -3,6 +3,13 @@ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { ChatBox } from "@/components/ChatBox"; import { User, Lock, @@ -13,6 +20,13 @@ import { FlaskConical, Users, FileText, + ChevronsUpDown, + Search, + Dna, + Gauge, + Atom, + Briefcase, + Cog, } from "lucide-react"; interface Simulation { @@ -70,6 +84,48 @@ export default function BiomodelDetailPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); + const [activeTab, setActiveTab] = useState("overview"); + const [diagramAnalysis, setDiagramAnalysis] = useState(""); + const [analysisError, setAnalysisError] = useState(""); + const [combinedMessages, setCombinedMessages] = useState([]); + + const quickActions = [ + { + label: "Describe biology of the model", + value: "Describe biology of the model", + icon: , + }, + { + label: "Describe parameters", + value: "Describe parameters", + icon: , + }, + { + label: "Describe species", + value: "Describe species", + icon: , + }, + { + label: "Describe reactions", + value: "Describe reactions", + icon: , + }, + { + label: "What Applications are used?", + value: "What Applications are used?", + icon: , + }, + { + label: "What solvers are used?", + value: "What solvers are used?", + icon: , + }, + { + label: "Analyze VCML", + value: "Analyze VCML", + icon: , + }, + ]; useEffect(() => { if (!bmid) return; @@ -91,8 +147,42 @@ export default function BiomodelDetailPage() { .finally(() => setLoading(false)); }, [bmid]); - if (loading) - return
Loading biomodel...
; + useEffect(() => { + if (!data?.bmKey) return; + + const fetchDiagramAnalysis = async () => { + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + const res = await fetch(`${apiUrl}/analyse/${data.bmKey}/diagram`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + if (res.ok) { + const responseData = await res.json(); + setDiagramAnalysis(responseData.response || ""); + } else { + const errorData = await res.json(); + setAnalysisError(errorData.detail || "Failed to analyze diagram."); + } + } catch (err) { + setAnalysisError("Failed to fetch diagram analysis."); + } + }; + + fetchDiagramAnalysis(); + }, [data?.bmKey]); + + // Create combined messages when diagram analysis is ready + useEffect(() => { + if (diagramAnalysis) { + const diagramMessage = `# Diagram Analysis \n ${diagramAnalysis}`; + setCombinedMessages([diagramMessage]); + } + }, [diagramAnalysis]); + if (error) return
{error}
; if (!data) return null; @@ -101,21 +191,21 @@ export default function BiomodelDetailPage() { return (
- - -
+ + +
- - + + {data.name} -
+
@@ -126,13 +216,13 @@ export default function BiomodelDetailPage() { "_blank", ); }} - className="inline-flex items-center gap-2 px-4 py-2 rounded border border-yellow-500 text-yellow-700 bg-white font-semibold shadow-sm transition-colors hover:bg-yellow-50" + className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded border border-yellow-500 text-yellow-700 bg-white font-semibold shadow-sm transition-colors hover:bg-yellow-50 text-sm" > AI Analysis
-
+
{" "} {data.bmKey} @@ -152,7 +242,7 @@ export default function BiomodelDetailPage() { )} {data.privacy === 1 ? "Private" : "Public"} @@ -167,143 +257,194 @@ export default function BiomodelDetailPage() {
- - {/* Biomodel Diagram block */} -
-
- - - Biomodel Diagram - -
- Biomodel Diagram setError("Failed to load diagram image.")} - onLoad={() => setError("")} - /> -
-
-
- - - Description - -
-
- {data.annot && data.annot.trim() !== "" - ? data.annot - : "No description is available for this biomodel"} -
-
-
-
- - - Applications - -
-
    - {data.applications?.map((app) => { - const encodedAppName = encodeURIComponent(app.name || ""); - const bnglUrl = `https://vcell.cam.uchc.edu/api/v0/biomodel/${data.bmKey}/biomodel.bngl?appname=${encodedAppName}`; - const sbmlUrl = `https://vcell.cam.uchc.edu/api/v0/biomodel/${data.bmKey}/biomodel.sbml?appname=${encodedAppName}`; - return ( -
  • - - - {app.name} - - - App Key:{" "} - - {app.key} - - MathKey:{" "} - - {app.mathKey} - + + + + + + Overview + + + + AI Analysis + + + + + {/* Biomodel Diagram block */} +
    + Biomodel Diagram setError("Failed to load diagram image.")} + onLoad={() => setError("")} + /> +
    + + {/* Description Section */} + + +
    + + + Description -
    - - -
    -
  • - ); - })} -
-
-
-
- - - Simulations - -
-
    - {data.simulations?.map((sim) => ( -
  • -
    - - {sim.name} +
    -
    - - Solver:{" "} - - {sim.solverName} - - - - Scan Count:{" "} - - {sim.scanCount} - + + +
    + {data.annot && data.annot.trim() !== "" + ? data.annot + : "No description is available for this biomodel"} +
    +
    + + + {/* Applications Section */} + + +
    + + + Applications - - Sim Context:{" "} - - {sim.bioModelLink.simContextName} - + +
    +
    + +
      + {data.applications?.map((app) => { + const encodedAppName = encodeURIComponent(app.name || ""); + const bnglUrl = `https://vcell.cam.uchc.edu/api/v0/biomodel/${data.bmKey}/biomodel.bngl?appname=${encodedAppName}`; + const sbmlUrl = `https://vcell.cam.uchc.edu/api/v0/biomodel/${data.bmKey}/biomodel.sbml?appname=${encodedAppName}`; + return ( +
    • + + + {app.name} + + + App Key:{" "} + + {app.key} + + MathKey:{" "} + + {app.mathKey} + + +
      + + +
      +
    • + ); + })} +
    +
    +
    + + {/* Simulations Section */} + + +
    + + + Simulations +
    - {sim.overrides && sim.overrides.length > 0 && ( -
    - Overrides: -
      - {sim.overrides.map((ov, i) => ( -
    • - {ov.name} ({ov.type}):{" "} + + +
        + {data.simulations?.map((sim) => ( +
      • +
        + + {sim.name} +
        +
        + + Solver:{" "} - {ov.values ? ov.values.join(", ") : "No values"} - {" "} - (Cardinality: {ov.cardinality}) -
      • - ))} -
      -
    - )} -
  • - ))} -
-
+ {sim.solverName} + + + + Scan Count:{" "} + + {sim.scanCount} + + + + Sim Context:{" "} + + {sim.bioModelLink.simContextName} + + +
+ {sim.overrides && sim.overrides.length > 0 && ( +
+ Overrides: +
    + {sim.overrides.map((ov, i) => ( +
  • + {ov.name} ({ov.type}):{" "} + + {ov.values ? ov.values.join(", ") : "No values"} + {" "} + (Cardinality: {ov.cardinality}) +
  • + ))} +
+
+ )} + + ))} + + + + + + + {/* AI Analysis Section */} +
+
+ + + AI Analysis Assistant + +
+
+ +
+
+
+
diff --git a/frontend/components/ChatBox.tsx b/frontend/components/ChatBox.tsx index a0a92ae..d044907 100644 --- a/frontend/components/ChatBox.tsx +++ b/frontend/components/ChatBox.tsx @@ -103,9 +103,11 @@ export const ChatBox: React.FC = ({ bmkeys.forEach((bmId) => { const searchString = `${bmId}`; const encodedPrompt = encodeURIComponent(`Describe model`); - const ai_link = `[AI Analysis](/analyze/${bmId}?prompt=${encodedPrompt})`; + /* const ai_link = `[AI Analysis](/analyze/${bmId}?prompt=${encodedPrompt})`; const db_link = `[Database](/search/${bmId})`; - const replacementString = `**${bmId}** -- ${ai_link}  |  ${db_link}`; + const replacementString = `**${bmId}** -- ${ai_link}  |  ${db_link}`; */ + const db_link = `[Database Details](/search/${bmId})`; + const replacementString = `**${bmId}** || ${db_link}`; formattedContent = formattedContent.replaceAll( searchString, replacementString,