diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5ef6a520780202a1d6addd833d800ccb1ecac0bb --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/ui/.vscode/mcp.json b/ui/.vscode/mcp.json new file mode 100644 index 0000000000000000000000000000000000000000..6716ff9e7ca89525b68d64af82f23a44c402dccb --- /dev/null +++ b/ui/.vscode/mcp.json @@ -0,0 +1,11 @@ +{ + "servers": { + "shadcn": { + "command": "npx", + "args": [ + "shadcn@latest", + "mcp" + ] + } + } +} diff --git a/ui/.vscode/tasks.json b/ui/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..18ecb9bd54cd3168a3508d32de67d10ce5f2754f --- /dev/null +++ b/ui/.vscode/tasks.json @@ -0,0 +1,44 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Type Check", + "type": "shell", + "command": "pnpm", + "args": [ + "run", + "type-check" + ], + "problemMatcher": [ + "$tsc" + ], + "group": "test" + }, + { + "label": "Lint", + "type": "shell", + "command": "pnpm", + "args": [ + "run", + "lint" + ], + "problemMatcher": [ + "$eslint-stylish" + ], + "group": "test" + }, + { + "label": "Lint", + "type": "shell", + "command": "npm", + "args": [ + "run", + "lint" + ], + "problemMatcher": [ + "$eslint-stylish" + ], + "group": "test" + } + ] +} \ No newline at end of file diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f31218f54501cc4e17de5cdcdc5ea0ebee3097f7 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,64 @@ +# BioFlow UI (Next.js) + +This project is a migration of the Streamlit "old_ui" to Next.js 16, Shadcn UI, Framer Motion, and Tailwind CSS v4. + +## Getting Started + +1. **Install dependencies:** + + ```bash + cd ui + pnpm install + # or + npm install + ``` + +2. **Run the development server:** + + ```bash + pnpm dev + # or + npm run dev + ``` + +3. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +## Structure + +- `app/`: App Router pages and layout. + - `page.tsx`: Home Dashboard. + - `discovery/`: Drug Discovery Pipeline interface. + - `explorer/`: Data Explorer with Charts. + - `data/`: Data Management. + - `settings/`: Configuration. + - `api/`: API Route handlers. +- `components/`: Reusable UI components. + - `ui/`: Shadcn-like primitive components (Button, Card, etc.). + - `sidebar.tsx`: Main Navigation. + - `page-header.tsx`: Standard page headers. + +## Tech Stack + +- **Framework:** Next.js 16 (App Router) +- **Styling:** Tailwind CSS v4 +- **UI Library:** Custom components inspired by Shadcn UI +- **Icons:** Lucide React +- **Charts:** Recharts +- **Animations:** Framer Motion (basic animations included, Framer Motion ready) + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/ui/app/api/data/route.ts b/ui/app/api/data/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..a913faf41a10aa10f0b1f0fcd8c1dbf840c68a5e --- /dev/null +++ b/ui/app/api/data/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; +import { getData } from "@/lib/data-service"; + +export async function GET() { + const data = await getData(); + return NextResponse.json(data); +} diff --git a/ui/app/api/discovery/route.ts b/ui/app/api/discovery/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..b339cf36adfd8d6f6156d90c765d878592498725 --- /dev/null +++ b/ui/app/api/discovery/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server'; + +export async function POST(request: Request) { + const body = await request.json(); + const { query } = body; + + console.log("Starting discovery for:", query); + + // Here you would typically call your Python backend + // e.g., await fetch('http://localhost:8000/api/discovery', { ... }) + + // Mock response + return NextResponse.json({ + success: true, + jobId: "job_" + Date.now(), + status: "processing", + message: "Pipeline started successfully" + }); +} diff --git a/ui/app/api/explorer/route.ts b/ui/app/api/explorer/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ed25e57cf179466ac88f5693ba332b2c080a4e1 --- /dev/null +++ b/ui/app/api/explorer/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server"; +import { getExplorerPoints } from "@/lib/explorer-service"; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + + try { + const data = await getExplorerPoints( + searchParams.get("dataset") || undefined, + searchParams.get("view") || undefined, + searchParams.get("colorBy") || undefined + ); + return NextResponse.json(data); + } catch (e) { + return NextResponse.json({ error: "Invalid parameters" }, { status: 400 }); + } +} diff --git a/ui/app/data/data-view.tsx b/ui/app/data/data-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..45c7aaad0d845f269f2cdb658a972a0644c56d41 --- /dev/null +++ b/ui/app/data/data-view.tsx @@ -0,0 +1,117 @@ +"use client" + +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Folder, FileText, Database, HardDrive, Upload, CloudUpload, Eye, Download, Trash2 } from "lucide-react" +import { SectionHeader } from "@/components/page-header" +import { Dataset, Statistics } from "@/types/data" + +interface DataViewProps { + datasets: Dataset[]; + stats: Statistics | null; +} + +export function DataView({ datasets, stats }: DataViewProps) { + const statCards = [ + { label: "Datasets", value: stats?.datasets?.toString() ?? "—", icon: Folder, color: "text-blue-500" }, + { label: "Molecules", value: stats?.molecules ?? "—", icon: FileText, color: "text-cyan-500" }, + { label: "Proteins", value: stats?.proteins ?? "—", icon: Database, color: "text-emerald-500" }, + { label: "Storage Used", value: stats?.storage ?? "—", icon: HardDrive, color: "text-amber-500" }, + ] + + return ( +
+
+ {statCards.map((stat, i) => ( + + +
+
{stat.label}
+
+
+
{stat.value}
+
+
+ ))} +
+ + + + Datasets + Upload + Processing + + + } /> + + + + + Name + Type + Items + Size + Updated + Actions + + + + {datasets.length === 0 && ( + + No datasets found. + + )} + {datasets.map((ds, i) => ( + + {ds.name} + +
+ {ds.type} +
+
+ {ds.count} + {ds.size} + {ds.updated} + +
+ + + +
+
+
+ ))} +
+
+
+
+ + } /> + + +
+
+ +
+
+
Click to upload or drag and drop
+
CSV, SDF, FASTA, or JSON (max 50MB)
+
+
+
+
+
+ + + + No active processing tasks. + + + +
+
+ ) +} diff --git a/ui/app/data/page.tsx b/ui/app/data/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c1a8379955f4586366b150bb5f016571b4e253ea --- /dev/null +++ b/ui/app/data/page.tsx @@ -0,0 +1,27 @@ +import { Suspense } from 'react' +import { getData } from "@/lib/data-service" +import { DataView } from "./data-view" +import { PageHeader } from "@/components/page-header" +import { Database, Loader2 } from "lucide-react" + +export default async function DataPage() { + const { datasets, stats } = await getData() + + return ( +
+ } + /> + + + +
+ }> + + + + ) +} diff --git a/ui/app/discovery/page.tsx b/ui/app/discovery/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..857f56ae139e74700d3e881a3cb788c34be283d1 --- /dev/null +++ b/ui/app/discovery/page.tsx @@ -0,0 +1,182 @@ +"use client" + +import * as React from "react" +import { PageHeader, SectionHeader } from "@/components/page-header" +import { Card, CardContent } from "@/components/ui/card" +import { Textarea } from "@/components/ui/textarea" +import { Button } from "@/components/ui/button" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Label } from "@/components/ui/label" +import { Microscope, Search, CheckCircle2, Circle, Loader2, ArrowRight } from "lucide-react" + +export default function DiscoveryPage() { + const [query, setQuery] = React.useState("") + const [isSearching, setIsSearching] = React.useState(false) + const [step, setStep] = React.useState(0) + + const handleSearch = () => { + setIsSearching(true) + setStep(1) + + // Simulate pipeline + setTimeout(() => setStep(2), 1500) + setTimeout(() => setStep(3), 3000) + setTimeout(() => { + setStep(4) + setIsSearching(false) + }, 4500) + } + + const steps = [ + { name: "Input", status: step > 0 ? "done" : "active" }, + { name: "Encode", status: step > 1 ? "done" : (step === 1 ? "active" : "pending") }, + { name: "Search", status: step > 2 ? "done" : (step === 2 ? "active" : "pending") }, + { name: "Predict", status: step > 3 ? "done" : (step === 3 ? "active" : "pending") }, + { name: "Results", status: step === 4 ? "active" : "pending" }, + ] + + return ( +
+ } + /> + + +
Search Query
+ +
+
+