# Project export: RepoRx

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: AI-native research engine to discover FDA-approved drug repurposing candidates for cancer treatment. Runs GPU-accelerated diffusion simulations to surface high-potential treatments in minutes.
- Devpost: https://devpost.com/software/molecule-mcbob
- GitHub: https://github.com/ezou1/tree26
- Demo: https://reporx.vercel.app/
- Video: https://www.youtube.com/embed/rBBftD6zOhM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([Fetch.ai] Build, launch and monetise AI Agents on Agentverse that turn user intent into real outcomes. (Best Overall AI Agent Solution - $2500 Best Multi-Agent Workflow - $1500 Best Monetised Agent - $1000); [Runpod] Best use of Flash (1st Place: 200 Runpod credits per team member (up to 4). 2nd Place: 100 Runpod credits per team member (up to 4). 3rd Place: 50 Runpod credits per team member (up to 4).))
- Team: 3 GitHub contributor(s) — cherylwu834 (10 commits), h4nkyn (6 commits), ezou1 (2 commits)

## Devpost submission (written by the team)

### Overview

The Cure That Was Already There A potential cancer treatment sat in medicine cabinets for 50 years before anyone noticed. Metformin, one of the most common diabetes drugs in the world, wasn't investigated for pancreatic cancer until 2005 — half a century after FDA approval. We’re making sure that never happens again. We’re focusing on repurposing already FDA-approved drugs. Developing a new cancer drug through the FDA approval process typically takes 10–15 years and over $1 billion, creating a major bottleneck for patients who urgently need better treatments. Repurposing already FDA-approved drugs offers an easier path. Recently, there are new tools to simulate drugs and their effectiveness in targeting cancer, but the research space is simply too large to try all of them. So we built, RepoRx, a research platform for discovering potential cancer treatments in drugs that were never designed for cancer. See more about the why here.

### What it does

Our agents Compose a full literature review that synthesizes current research Identify proteins to target, and relevant drugs Run experiments to simulate the interaction between the proteins and compounds Output: Full research report to share Simulation results that show the most promising drugs to take to wet labs Confidence scores assigned to each drug candidate Once the outputs are received, researchers can take them to the wet lab or continue searching for compounds with similar molecular structures in order to find more potential drugs – right on our platform.

### How we built it

We built a modular, multi-agent pipeline in TypeScript with a Next.js + Tailwind frontend. The app is hosted on Vercel. Each agent handles a distinct phase of the workflow and communicates via structured JSON, allowing independent development, debugging, and resumable execution. Orchestrated agent flows using Fetch.ai – registered an agent on AgentVerse where it can be used in ASI:One, an alternative UI to our platform. Using the arXiv API and Perplexity AI (Sonar Pro Deep Research), the system synthesizes current research for a selected cancer type and extracts high-impact protein targets (e.g., EGFR, KRAS, p53), along with known drugs, mechanisms of action, and FDA status. The agent retrieves high-resolution protein structures from the RCSB Protein Data Bank and queries PubChem to collect and expand candidate compounds. RDKit canonicalizes SMILES - strings for docking compatibility. We run DiffDock on RunPod serverless GPUs (Nvidia Blackwells) to predict protein–ligand binding. Ligands are processed in parallel batches, generating multiple poses per pair and ranking candidates by confidence score (0–1). Docking results are classified into cancer-approved drugs, repurposing candidates, and novel compounds. The system generates a ranked repurposing leaderboard and automatically produces structured research summaries using Perplexity AI. A final assistant agent explains the results and future recommendations interactively.

### Challenges we ran into

Running DiffDock locally wasn’t feasible due to GPU and memory limitations. We had to provision cloud infrastructure on RunPod and deploy to virtual NVIDIA Blackwell GPUs to handle large-scale docking simulations. This required setting up remote job orchestration, batching, and polling logic under tight time constraints. DiffDock requires clean, consistent structural inputs. Converting compounds from PubChem into canonicalized SMILES and compatible 3D formats introduced edge cases (naming conflicts, malformed structures, conversion mismatches). Ensuring filesystem-safe ligand naming while preserving metadata required additional preprocessing and validation layers. We initially built the backend in Python (Flask), but migrated to a full TypeScript stack mid-hackathon for better compatibility with Vercel deployment and frontend integration. This required refactoring API orchestration, async workflows, and agent communication under time pressure. Because our system surfaces drug recommendations in a clinical domain, we had to think carefully about disclaimers, scope limitation, and responsible framing. Determining where human oversight is required—and ensuring the system is positioned as a research aid rather than a clinical decision-maker—was an important design consideration.

### Accomplishments we're proud of

We built a system that takes a single cancer-type input and autonomously performs literature synthesis, target identification, structure retrieval, compound discovery, molecular docking with DiffDock, classification, and report generation—compressing what typically a lab takes weeks or months to research into one automated workflow that completes in several minutes. We deployed real large-scale docking simulations on cloud GPUs via RunPod, using a physics-informed diffusion model, generating ranked confidence scores across multiple protein–drug pairs in parallel. Despite migrating tech stacks mid-hackathon and provisioning GPU infrastructure from scratch, we delivered a production-style TypeScript backend with a deployable Next.js frontend.

### What we learned

Building in the medical domain forced us to think beyond technical performance. Even though our system is research-focused, surfacing drug recommendations carries real-world implications. We learned the importance of positioning the platform as a decision-support tool and not an authority, clearly defining its scope. In healthcare, outputs can influence high-stakes decisions. We had to consider disclaimers, human-in-the-loop checkpoints, and how recommendations are communicated.

### What's next

We plan to incorporate patient-specific safety constraints, including pre-existing conditions, contraindications, and known adverse interaction profiles. This will allow the system to filter or down-rank compounds that may pose elevated clinical risk, improving real-world viability. We want to make the workflow fully iterative: allowing users to move forward or backward between stages (e.g., refining target selection after docking results, expanding compound libraries, or re-running simulations with adjusted constraints). For top-ranked candidates, we aim to integrate more rigorous physics-based methods beyond DiffDock, such as molecular dynamics simulations and free-energy calculations, to improve binding accuracy and reduce false positives. Once computational confidence is strengthened, the next step is structured recommendation for wet-lab validation prioritizing compounds with strong predicted binding, established safety profiles, and mechanistic plausibility for in vitro testing.

## README (from the GitHub repository)

# tree26
for the love of the game
Treehacks 2026

## Detected evidence (automated analysis)

Indexed codebase: 97 recognized source files, 831 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 120)

```
.gitignore
agent1q2fa4qyx2l_data.json
agent2_output.json
agent2.py
agent3_output.json
agent3.py
app/api/analyze/route.ts
app/api/chat/route.ts
app/api/dock/route.ts
app/api/expand/route.ts
app/api/paper/route.ts
app/api/report/route.ts
app/api/review/route.ts
app/api/structures/route.ts
app/deck/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
components.json
components/app-header.tsx
components/data-panel.tsx
components/drug-detail-panel.tsx
components/molecule-viewer.tsx
components/report-panel.tsx
components/research-panel.tsx
components/theme-provider.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
final_paper.md
final.py
hooks/use-mobile.tsx
hooks/use-toast.ts
lib/mock-data.ts
lib/pdb.ts
lib/perplexity.ts
lib/pubchem.ts
lib/runpod.ts
lib/types.ts
lib/utils.ts
LICENSE
next-env.d.ts
next.config.mjs
old-outputs/agent2_output.json
old-outputs/agent3_output.json
package.json
pipeline_state.json
pipeline.py
postcss.config.mjs
README.md
requirements.txt
results.md
results.py
review copy.json
review copy.md
review.json
review.md
review.py
router_agent.py
structures/2FMA.pdb
structures/3QL9.pdb
structures/4PZ3.pdb
structures/6XUN.pdb
structures/8A27.pdb
structures/9E3S.pdb
structures/9IAY.pdb
styles/globals.css
tailwind.config.ts
test_agent2.py
test_agents/hello_agent.py
test_agents/two_agents.py
tsconfig.json
tsconfig.tsbuildinfo
```

### Dependencies

- package.json: @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@1.1.1, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @tailwindcss/postcss@^4.1.13, @tailwindcss/typography@^0.5.19, @types/node@^22, @types/react@19.2.7, @types/react-dom@19.2.3, 3dmol@^2.5.4, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.1.1, date-fns@4.1.0, embla-carousel-react@8.5.1, html2pdf.js@^0.14.0, input-otp@1.4.1, lucide-react@^0.544.0, next@16.1.6, next-themes@^0.4.6, postcss@^8.5, react@19.2.3, react-day-picker@8.10.1, react-dom@19.2.3, react-hook-form@^7.54.1, react-markdown@^10.1.0, react-resizable-panels@^2.1.7, recharts@2.15.0, remark-gfm@^4.0.1, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@5.7.3, vaul@^1.1.2, zod@^3.24.1
- requirements.txt: gemmi@>=0.7.0, pytest@>=8.0.0, python-dotenv@>=1.0.0, rdkit@>=2024.3.1, requests@>=2.31.0, runpod@>=1.7.0, uagents@==0.23.6, uagents-core@==0.4.0

### Recent commits (newest first)

- move images
- Image for deck
- Update deck slides
- Merge pull request #4 from ezou1/dev
- Merge branch 'main' into dev
- move deck location
- Slide Deck
- Improve PDF export and update panel styles
- Add PDF export and Markdown rendering
- Merge branch 'main' into dev
- Add user-confirmable AI expansion flow
- Add CID_xxx direct PubChem lookup
- Refactor pipeline APIs and enhance frontend UX - IT'S WORKING THANK THE LORD
- Semi-working typescript-side
- add next lib folder
- Re-organized UI files
- Front end code from v0
- Merge branch 'dev' of https://github.com/ezou1/tree26 into dev
- Started on router_agent
- stuff

## Key source files (fetched from GitHub, selected and truncated for size)

### results.md

```markdown
# Molecular Docking Analysis: Pancreatic Ductal Adenocarcinoma

*Auto-generated on February 14, 2026*

---

## Methodology

## Molecular Docking Engine

All molecular docking simulations were performed using DiffDock, a diffusion-based deep learning model designed for structure-based drug discovery[1]. DiffDock predicts ligand binding poses by iteratively refining ligand coordinates through a learned diffusion process, generating multiple binding poses per ligand–protein pair and ranking each pose by a confidence score ranging from 0 to 1, with higher scores indicating greater predicted binding reliability. For each ligand–protein complex, ten binding poses were generated, and the pose with the highest confidence score was retained as the representative result for subsequent ranking and analysis.

## Compute Infrastructure

All docking simulations were executed on RunPod serverless GPU instances accessed via the RunPod Python SDK[1]. This cloud-based infrastructure enabled parallel GPU-accelerated inference without requiring local hardware management or maintenance. Each ligand–protein docking job was submitted to a dedicated serverless endpoint, allowing efficient batch processing and rapid turnaround of large-scale virtual screening campaigns.

## Protein Target Selection

Target proteins were selected based on their established roles in pancreatic ductal adenocarcinoma (PDAC) pathogenesis. Two primary targets were investigated: KRAS (Protein Data Bank identifier: 9IAY) and extracellular signal-regulated kinase 2 (ERK2; PDB: 2FMA). These proteins were selected because KRAS mutations occur in approximately 90% of PDAC cases and represent a critical node in oncogenic signaling, while ERK2 functions as a key effector in the mitogen-activated protein kinase (MAPK) cascade downstream of KRAS activation. Three-dimensional protein structures were retrieved from the RCSB Protein Data Bank in standard .pdb format.

## Ligand Library Construction

Candidate compounds were retrieved from PubChem and curated drug databases, encompassing both FDA-approved pharmaceuticals and bioactive research compounds identified through target-based searches. The ligand library included direct KRAS inhibitors (sotorasib, adagrasib, divarasib, MRTX1133, RMC-4630, RMC-6236, RMC-9805, BI-3406, BI-1701963, TH-Z835), MEK/ERK pathway inhibitors (trametinib, LY3214996), compounds with indirect anti-KRAS activity (metformin, simvastatin, disulfiram, itraconazole), repurposed drugs with reported off-target MAPK inhibition (doxycycline, aspirin, thalidomide, propranolol, verapamil, cimetidine), and bioactive natural product derivatives identified through PubChem target searches.

## Docking Protocol and Execution

For the KRAS target (PDB: 9IAY), 64 ligands were submitted for docking, yielding 71 successfully docked poses. For the ERK2 target (PDB: 2FMA), 6 ligands were submitted, yielding 13 successfully docked poses. The discrepancy between submitted and successfully docked ligan
[truncated — 11205 more characters]
```

### review.md

```markdown
# Literature Review: Pancreatic Ductal Adenocarcinoma

*Auto-generated on February 14, 2026*

---

# Literature Review: Pancreatic Ductal Adenocarcinoma

## 1. Introduction (Epidemiology, Significance)

**Pancreatic ductal adenocarcinoma (PDAC)** represents the predominant histological subtype of pancreatic cancer, characterized by its aggressive biology, late-stage diagnosis, and dismal prognosis. PDAC accounts for the majority of pancreatic malignancies, with global incidence rates rising steadily; for instance, age-adjusted incidence increased from 6.72 to 7.92 per 100,000 person-years between 2000 and 2020 for PDAC specifically[4]. Worldwide, PDAC is the 12th most common cancer, with an age-standardized incidence of approximately 4.2 per 100,000, translating to around 338,000 new cases in 2012, disproportionately affecting more developed regions (7.2 per 100,000) compared to less developed ones (2.8 per 100,000)[2 from search]. In the United States, recent data indicate 13.8 new cases per 100,000 annually (2018–2022), with a death rate of 11.3 per 100,000 (2019–2023), reflecting mortality rates closely mirroring incidence due to advanced presentation at diagnosis[3 from search]. Early-stage PDAC incidence has notably risen from 1.1 to 2.8 per 100,000 between 2001 and 2020, with an annual percent change (APC) of 5.7%, suggesting improved detection capabilities, alongside 5-year survival improvements for stages IA (47% to 75%) and IB (38% to 68%) from 2004 to 2015[1 from search].

The clinical significance of PDAC stems from its lethality: only 15% of cases are localized at diagnosis (5-year survival 43.6%), 28% regional (16.7%), and 51% distant (3.2%), yielding an overall 5-year survival of ~7.7–10%[3 from search][2 from search]. By 2030, PDAC is projected to become the second leading cause of cancer-related deaths in the US, driven by its dense desmoplastic stroma, tumor microenvironment (TME) heterogeneity, and challenges in early detection. Risk factors include advanced age (incidence peaks at 55.7 per 100,000 for ≥75 years), male sex, and regional disparities, with highest rates in Northern America (7.4 per 100,000) and Europe (6.8 per 100,000)[2 from search]. These trends underscore the urgent need for enhanced screening, precise imaging for resectability, and novel therapies to address the TME's role in invasion and therapy resistance[2].

## 2. Molecular and Genetic Landscape

The molecular underpinnings of PDAC are dominated by **KRAS mutations**, present in over 90% of cases, particularly KRAS G12D, which locks the protein in an active state, driving uncontrolled cell proliferation[5]. Bioinformatics analyses of GEO datasets (e.g., GSE28735, GSE15471, GSE62452) have identified differentially expressed genes (DEGs) via Gene Ontology (GO) and KEGG pathways, revealing hub genes involved in PDAC pathogenesis, including those in cell proliferation, adhesion, and stromal remodeling[1]. Gene regulatory networks further highlight transcriptio
[truncated — 23351 more characters]
```

### requirements.txt

```
requests>=2.31.0
python-dotenv>=1.0.0
runpod>=1.7.0
rdkit>=2024.3.1
gemmi>=0.7.0
pytest>=8.0.0
uagents==0.23.6
uagents-core==0.4.0

```

### package.json

```
{
  "name": "my-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "3dmol": "^2.5.4",
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "1.1.1",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "@tailwindcss/typography": "^0.5.19",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.1.1",
    "date-fns": "4.1.0",
    "embla-carousel-react": "8.5.1",
    "html2pdf.js": "^0.14.0",
    "input-otp": "1.4.1",
    "lucide-react": "^0.544.0",
    "next": "16.1.6",
    "next-themes": "^0.4.6",
    "react": "19.2.3",
    "react-day-picker": "8.10.1",
    "react-dom": "19.2.3",
    "react-hook-form": "^7.54.1",
    "react-markdown": "^10.1.0",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "remark-gfm": "^4.0.1",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.13",
    "@types/node": "^22",
    "@types/react": "19.2.7",
    "@types/react-dom": "19.2.3",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "5.7.3"
  },
  "pnpm": {
    "overrides": {
      "@types/react": "19.2.7",
      "@types/react-dom": "19.2.3"
    }
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata, Viewport } from 'next'
import { Inter, JetBrains_Mono } from 'next/font/google'

import './globals.css'

const _inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
const _jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  variable: '--font-jetbrains-mono',
})

export const metadata: Metadata = {
  title: 'RepoRx - AI Drug Repurposing Research',
  description:
    'AI-powered literature review and drug repurposing research platform for identifying protein targets and candidate therapeutics.',
}

export const viewport: Viewport = {
  themeColor: '#181c24',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body
        className={`${_inter.variable} ${_jetbrainsMono.variable} font-sans antialiased`}
      >
        {children}
      </body>
    </html>
  )
}

```

### app/deck/page.tsx

```typescript
"use client"

import { useState, useEffect, useCallback } from "react"
import Image from "next/image"
import {
  FlaskConical,
  ChevronLeft,
  ChevronRight,
  BookOpen,
  Dna,
  Pill,
  Clock,
  Zap,
  Search,
  BarChart3,
  Target,
  ArrowRight,
  ArrowDown,
  CheckCircle2,
  AlertTriangle,
  Eye,
  EyeOff,
  Microscope,
} from "lucide-react"

const TOTAL_SLIDES = 7

export default function DeckPage() {
  const [current, setCurrent] = useState(0)
  const [isAnimating, setIsAnimating] = useState(false)

  const goTo = useCallback(
    (index: number) => {
      if (isAnimating) return
      if (index < 0 || index >= TOTAL_SLIDES) return
      setIsAnimating(true)
      setCurrent(index)
      setTimeout(() => setIsAnimating(false), 500)
    },
    [isAnimating]
  )

  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.key === "ArrowRight" || e.key === " ") {
        e.preventDefault()
        goTo(current + 1)
      }
      if (e.key === "ArrowLeft") {
        e.preventDefault()
        goTo(current - 1)
      }
    }
    window.addEventListener("keydown", handler)
    return () => window.removeEventListener("keydown", handler)
  }, [current, goTo])

  return (
    <div className="relative h-screen w-screen overflow-hidden bg-background">
      <div
        className="flex h-full transition-transform duration-500 ease-in-out"
        style={{ transform: `translateX(-${current * 100}vw)` }}
      >
        <Slide01Title />
        <Slide02Metformin />
        <Slide03Problem />
        <Slide04Invisible />
        <Slide05SearchSpace />
        <Slide06NewTools />
        <Slide09Platform />
      </div>

      {/* Navigation */}
      <div className="absolute bottom-6 left-1/2 z-20 flex -translate-x-1/2 items-center gap-4">
        <button
          onClick={() => goTo(current - 1)}
          disabled={current === 0}
          className="flex h-9 w-9 items-center justify-center rounded-full border border-border bg-card text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground disabled:opacity-30 disabled:hover:bg-card"
          aria-label="Previous slide"
        >
          <ChevronLeft className="h-4 w-4" />
        </button>

        <div className="flex items-center gap-1.5">
          {Array.from({ length: TOTAL_SLIDES }).map((_, i) => (
            <button
              key={i}
              onClick={() => goTo(i)}
              aria-label={`Go to slide ${i + 1}`}
              className={`h-1.5 rounded-full transition-all duration-300 ${i === current
                ? "w-6 bg-primary"
                : "w-1.5 bg-muted-foreground/30 hover:bg-muted-foreground/60"
                }`}
            />
          ))}
        </div>

        <button
          onClick={() => goTo(current + 1)}
          disabled={current === TOTAL_SLIDES - 1}
          className="flex h-9 w-9 items-center justify-center rounded-full border border-border bg-card text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground disabled:opacity-30 disabled:hover:bg-card"
          aria-label="Next slide"
        >
          <ChevronRight className="h-4 w-4" />
        </button>
      </div>

      <div className="absolute bottom-7 right-8 z-20 font-mono text-xs text-muted-foreground">
        {current + 1} / {TOTAL_SLIDES}
      </div>
    </div>
  )
}

/* ─── Shared slide wrapper ─── */
function Slide({ children, className = "" }: { children: React.ReactNode; className?: string }) {
  return (
    <div className={`relative flex h-screen w-screen flex-none items-center justify-center overflow-hidden ${className}`}>
      {children}
    </div>
  )
}

/* ─── 1. Title — RepoRx Brand Slide ─── */
function Slide01Title() {
  return (
    <Slide>
      {/* Background image */}
      <div className="absolute inset-0">
        <Image
          src="/images/deck-molecule-network.jpg"
          alt="Molecular network visualization"
          fill
          className="object-cover opacity-25"
        />
        <div className="absolute inset-0 bg-gradient-to-t from-background via-background/70 to-background/40" />
      </div>

      <div className="relative z-10 flex flex-col items-center gap-6 text-center">
        {/* Logo mark */}
        <div className="flex h-20 w-20 items-center justify-center rounded-2xl border border-primary/20 bg-primary/10">
          <FlaskConical className="h-10 w-10 text-primary" />
        </div>

        {/* Brand name */}
        <h1 className="text-8xl font-bold tracking-tight text-foreground lg:text-9xl">
          Repo<span className="text-primary">Rx</span>
        </h1>

        {/* Accent divider */}
        <div className="h-1 w-20 rounded-full bg-primary" />

        {/* Tagline */}
        <p className="text-xl text-secondary-foreground lg:text-2xl">
          Research Platform for AI Drug Rediscovery
        </p>
      </div>
    </Slide>
  )
}

/* ─── 2. Metformin Case Study ─── */
function Slide02Metformin() {
  return (
    <Slide>
      <div className="flex w-full max-w-6xl items-center gap-16 px-16">
        {/* Left — Image */}
        <div className="relative flex-none">
          <div className="relative h-[420px] w-[360px] overflow-hidden rounded-2xl border border-border">
            <Image
              src="/images/deck-metformin.jpg"
              alt="Metformin pill"
              fill
              className="object-cover"
            />
            <div className="absolute inset-0 bg-gradient-to-t from-background/90 via-transparent to-transparent" />
            <div className="absolute bottom-0 left-0 right-0 p-6">
              <p className="font-mono text-sm text-primary">Case Study</p>
              <p className="text-2xl font-bold text-foreground">Metformin</p>
            </div>
          </div>
        </div>

        {/* Right — Timeline */}
        <div className="flex flex-1 flex-col gap-6">
          <div>
            <p className="mb-3 font-mono text-xs uppercase tracking-[0.2em] text-pr
[truncated — 15481 more characters]
```

### app/api/chat/route.ts

```typescript
import { NextResponse } from "next/server"
import { queryPerplexity } from "@/lib/perplexity"

export const maxDuration = 60

export async function POST(req: Request) {
  try {
    const { question, context } = (await req.json()) as {
      question: string
      context: {
        cancerType?: string
        proteins?: string[]
        dockingResults?: {
          name: string
          confidence: number
          protein: string
          mechanism: string
        }[]
        reviewSummary?: string
      }
    }

    if (!question) {
      return NextResponse.json(
        { error: "question is required" },
        { status: 400 }
      )
    }

    const system =
      "You are a knowledgeable drug discovery research assistant. " +
      "Answer the user's question concisely based on the provided context " +
      "about their drug discovery pipeline results. " +
      "Use specific drug names, protein targets, and scores when relevant. " +
      "Keep responses focused and under 300 words."

    let contextStr = ""
    if (context.cancerType) {
      contextStr += `Disease: ${context.cancerType}\n`
    }
    if (context.proteins?.length) {
      contextStr += `Protein targets: ${context.proteins.join(", ")}\n`
    }
    if (context.dockingResults?.length) {
      contextStr += `\nTop docking results:\n`
      for (const r of context.dockingResults) {
        contextStr += `- ${r.name}: ${Math.round(r.confidence * 100)}% confidence against ${r.protein} (${r.mechanism})\n`
      }
    }
    if (context.reviewSummary) {
      contextStr += `\nLiterature review excerpt:\n${context.reviewSummary}\n`
    }

    const user = contextStr
      ? `Context:\n${contextStr}\n\nQuestion: ${question}`
      : question

    const answer = await queryPerplexity(system, user)

    return NextResponse.json({ answer })
  } catch (error) {
    console.error("Chat API error:", error)
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 500 }
    )
  }
}

```

### app/api/paper/route.ts

```typescript
import { NextResponse } from "next/server"
import { queryPerplexity } from "@/lib/perplexity"

export const maxDuration = 300

export async function POST(req: Request) {
  try {
    const { reviewMd, resultsMd } = (await req.json()) as {
      reviewMd: string
      resultsMd: string
    }

    if (!reviewMd || !resultsMd) {
      return NextResponse.json(
        { error: "reviewMd and resultsMd are required" },
        { status: 400 }
      )
    }

    // Generate abstract
    const abstract = await queryPerplexity(
      "You are a scientific writer. Write a concise abstract (200-300 words, single paragraph) for an academic paper.",
      `Write an abstract combining these two sections:\n\n` +
        `LITERATURE REVIEW:\n${reviewMd.slice(0, 3000)}\n\n` +
        `RESULTS:\n${resultsMd.slice(0, 3000)}\n\n` +
        `Cover: clinical problem, computational approach, key targets, top candidates, conclusions.`
    )

    // Merge introduction
    const introduction = await queryPerplexity(
      "You are a scientific writer. Polish and merge these sections into a cohesive introduction. Preserve all citations.",
      `Merge into a unified Introduction & Background section:\n\n${reviewMd.slice(0, 5000)}`
    )

    // Merge results
    const mergedResults = await queryPerplexity(
      "You are a scientific writer. Polish this results section. Keep ALL tables and numerical data. Preserve citations.",
      `Polish this results section:\n\n${resultsMd}`
    )

    // Generate conclusion
    const conclusion = await queryPerplexity(
      "You are a scientific writer. Write a unified conclusion combining findings from both the literature review and computational results.",
      `Write a conclusion for this paper:\n\nREVIEW:\n${reviewMd.slice(0, 2000)}\n\nRESULTS:\n${resultsMd.slice(0, 2000)}`
    )

    // Assemble final paper
    const paperMd =
      `# Drug Repurposing Analysis\n\n` +
      `## Abstract\n\n${abstract}\n\n` +
      `---\n\n` +
      `## 1. Introduction and Background\n\n${introduction}\n\n` +
      `---\n\n` +
      `## 2. Results\n\n${mergedResults}\n\n` +
      `---\n\n` +
      `## 3. Conclusion\n\n${conclusion}\n`

    return NextResponse.json({ paperMd })
  } catch (error) {
    console.error("Paper API error:", error)
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 500 }
    )
  }
}

```

### app/api/analyze/route.ts

```typescript
import { NextResponse } from "next/server"
import { queryPerplexity, parseJsonResponse } from "@/lib/perplexity"
import type { DockingResult, ReasoningDecision } from "@/lib/types"

export const maxDuration = 60

const MAX_EXPANSION_ROUNDS = 2

export async function POST(req: Request) {
  try {
    const {
      cancerType,
      dockingResults,
      round,
      hypotheses = [],
      expansionHistory = [],
    } = (await req.json()) as {
      cancerType: string
      dockingResults: DockingResult[]
      round: number
      hypotheses?: string[]
      expansionHistory?: { round: number; action: string; rationale: string }[]
    }

    if (!cancerType || !dockingResults?.length) {
      return NextResponse.json(
        { error: "cancerType and dockingResults are required" },
        { status: 400 }
      )
    }

    // Build top 30 summary
    const topResults = [...dockingResults]
      .sort((a, b) => b.confidenceScore - a.confidenceScore)
      .slice(0, 30)
      .map((r) => ({
        name: r.name,
        score: Math.round(r.confidenceScore * 10000) / 10000,
        mechanism: (r.mechanism || "").slice(0, 100),
        fda_status: (r.fdaStatus || "").slice(0, 60),
        protein: r.proteinTarget || "",
        source: r.source || "",
        round: r.round || 1,
      }))

    const system =
      "You are a computational pharmacology researcher analyzing molecular " +
      "docking results for drug repurposing. You must return ONLY valid JSON.\n\n" +
      "Analyze the top docking hits and look for patterns:\n" +
      "- Are drugs from the same therapeutic class clustering as top hits?\n" +
      "  (e.g., multiple statins, multiple SSRIs, multiple kinase inhibitors)\n" +
      "- Do top-scoring compounds share structural features?\n" +
      "- Are there unexplored drug classes that might work?\n\n" +
      "Return a JSON object with:\n" +
      '  "action": one of "expand_3d_similar", "expand_class", or "proceed"\n' +
      '  "rationale": your analysis of the patterns (2-3 sentences)\n' +
      '  "hypothesis": a specific scientific hypothesis (1 sentence)\n' +
      '  "seed_cids": [list of CID numbers] (if action is expand_3d_similar)\n' +
      '  "drug_class": "class name" (if action is expand_class)\n' +
      '  "drug_names": ["drug1", "drug2", ...] (if action is expand_class)\n\n' +
      "Rules:\n" +
      `- This is round ${round}. Max rounds: ${MAX_EXPANSION_ROUNDS + 1}.\n` +
      `- If round >= ${MAX_EXPANSION_ROUNDS + 1}, you MUST choose "proceed".\n` +
      '- Only choose "expand" if you see a clear pattern worth investigating.\n' +
      "- If expanding, be specific about which CIDs or drug names to test."

    const user =
      `Cancer type: ${cancerType}\n` +
      `Round: ${round}\n` +
      `Previous hypotheses: ${JSON.stringify(hypotheses)}\n` +
      `Previous expansions: ${JSON.stringify(expansionHistory)}\n\n` +
      `Top 30 docking results (sorted by confidence score):\n` +
      `${JSON.stringify(topResults, null, 2)}\n\n` +
      `Analyze these results and return your decision as JSON.`

    const raw = await queryPerplexity(system, user)
    const parsed = parseJsonResponse(raw) as Record<string, unknown>

    const decision: ReasoningDecision = {
      action: "proceed",
      rationale: (parsed.rationale as string) || "",
      hypothesis: (parsed.hypothesis as string) || "",
    }

    // Validate action
    const action = parsed.action as string
    if (["expand_3d_similar", "expand_class", "proceed"].includes(action)) {
      decision.action = action as ReasoningDecision["action"]
    }

    // Force proceed if max rounds reached
    if (round >= MAX_EXPANSION_ROUNDS + 1) {
      decision.action = "proceed"
      decision.rationale += " (Max rounds reached, proceeding to synthesis.)"
    }

    if (decision.action === "expand_3d_similar") {
      decision.seedCids = (parsed.seed_cids as number[]) || []
    }
    if (decision.action === "expand_class") {
      decision.drugClass = (parsed.drug_class as string) || ""
      decision.drugNames = (parsed.drug_names as string[]) || []
    }

    return NextResponse.json(decision)
  } catch (error) {
    console.error("Analyze API error:", error)
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 500 }
    )
  }
}

```

### app/api/report/route.ts

```typescript
import { NextResponse } from "next/server"
import { queryPerplexity } from "@/lib/perplexity"
import type { DockingResult, DockingTarget } from "@/lib/types"

export const maxDuration = 180

function classifyCompound(
  fdaStatus: string,
  cancerType: string
): "cancer_purposed" | "repurposing" | "novel" {
  const s = fdaStatus.toLowerCase()
  const cancer = cancerType.toLowerCase()
  if (s.includes("approved") && s.includes(cancer.split(/\s+/)[0])) {
    return "cancer_purposed"
  }
  if (s.includes("approved") || s.includes("fda")) {
    return "repurposing"
  }
  return "novel"
}

export async function POST(req: Request) {
  try {
    const { cancerType, allResults, targets } = (await req.json()) as {
      cancerType: string
      allResults: DockingResult[]
      targets: DockingTarget[]
    }

    if (!cancerType || !allResults?.length) {
      return NextResponse.json(
        { error: "cancerType and allResults are required" },
        { status: 400 }
      )
    }

    // Build overview per target
    const resultsByProtein: Record<string, DockingResult[]> = {}
    for (const r of allResults) {
      const prot = r.proteinTarget || "unknown"
      if (!resultsByProtein[prot]) resultsByProtein[prot] = []
      resultsByProtein[prot].push(r)
    }

    // Sort each target's results
    for (const prot of Object.keys(resultsByProtein)) {
      resultsByProtein[prot].sort(
        (a, b) => b.confidenceScore - a.confidenceScore
      )
    }

    // Classify compounds
    const classified: Record<string, DockingResult[]> = {
      cancer_purposed: [],
      repurposing: [],
      novel: [],
    }
    for (const r of allResults) {
      const cat = classifyCompound(r.fdaStatus, cancerType)
      classified[cat].push(r)
    }

    // Build overview text
    let overviewText = `## Docking Overview\n\n`
    overviewText += `- Cancer type: ${cancerType}\n`
    overviewText += `- Total compounds docked: ${allResults.length}\n`
    overviewText += `- Protein targets: ${targets.map((t) => t.protein).join(", ")}\n\n`

    for (const target of targets) {
      const results = resultsByProtein[target.protein] || []
      overviewText += `### ${target.protein} (PDB: ${target.pdbId || "N/A"})\n\n`
      overviewText += `| Rank | Drug | Score | FDA Status | Mechanism |\n`
      overviewText += `|------|------|-------|------------|------------|\n`
      for (const [i, r] of results.slice(0, 15).entries()) {
        overviewText += `| ${i + 1} | ${r.name} | ${r.confidenceScore.toFixed(4)} | ${r.fdaStatus} | ${r.mechanism.slice(0, 60)} |\n`
      }
      overviewText += "\n"
    }

    const classifiedText =
      `Cancer-purposed drugs: ${classified.cancer_purposed.length}\n` +
      `Repurposing candidates: ${classified.repurposing.length}\n` +
      `Novel/research compounds: ${classified.novel.length}`

    // Generate methodology via Perplexity
    const methodologyPrompt =
      `Write a detailed methodology section for a drug repurposing paper about ${cancerType}. ` +
      `The study used:\n` +
      `- DiffDock (diffusion-based molecular docking model) on RunPod serverless GPU\n` +
      `- 10 poses per ligand-protein pair\n` +
      `- Protein structures from RCSB PDB\n` +
      `- Ligands from PubChem + curated databases\n` +
      `- Perplexity AI for literature analysis and reasoning\n\n` +
      `Overview:\n${overviewText}`
    const methodology = await queryPerplexity(
      "You are a scientific writer. Write a detailed methodology section in Markdown.",
      methodologyPrompt
    )

    // Generate results via Perplexity
    const resultsPrompt =
      `Write a detailed results section for a drug repurposing paper about ${cancerType}.\n\n` +
      `Docking results:\n${overviewText}\n\n` +
      `Classification:\n${classifiedText}\n\n` +
      `Focus on:\n` +
      `1. Overview of docking runs\n` +
      `2. Drug repurposing leaderboard (ALL FDA drugs not approved for this cancer)\n` +
      `3. Novel/research compounds\n` +
      `4. Cross-target comparison\n` +
      `5. Score distribution analysis`
    const results = await queryPerplexity(
      "You are a scientific writer. Write a detailed results section in Markdown with tables.",
      resultsPrompt
    )

    // Generate conclusion via Perplexity
    const conclusionPrompt =
      `Write a conclusion for a drug repurposing paper about ${cancerType}.\n\n` +
      `Results:\n${overviewText}\n\n` +
      `Classification:\n${classifiedText}\n\n` +
      `Cover: key findings, top repurposing candidates, clinical implications, limitations, future directions.`
    const conclusion = await queryPerplexity(
      "You are a scientific writer. Write a conclusion section in Markdown.",
      conclusionPrompt
    )

    const resultsMd =
      `# Results: Drug Repurposing Analysis for ${cancerType}\n\n` +
      `## Methodology\n\n${methodology}\n\n` +
      `## Results\n\n${results}\n\n` +
      `## Conclusion\n\n${conclusion}\n`

    return NextResponse.json({ resultsMd })
  } catch (error) {
    console.error("Report API error:", error)
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 500 }
    )
  }
}

```

[87 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]