diff --git a/Dockerfile b/Dockerfile index 4b523f0b487ec3c1ad6b5af2cdee33bf2e6c2017..60a2568f8643757aecfd8da08e3ce36ec69597ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,6 @@ COPY --chown=1000:1000 api/ ./api/ COPY --chown=1000:1000 ml/ ./ml/ COPY --chown=1000:1000 data/ ./data/ COPY --chown=1000:1000 scripts/ ./scripts/ -COPY --chown=1000:1000 ui/ ./ui/ COPY --chown=1000:1000 run_api.py run_all.py ./ # Copy pre-trained model artifacts and data diff --git a/Dockerfile.hf b/Dockerfile.hf index 18ee0ea167089a1de0315e00ac815ea073adf520..3be77eaf066dfdd4d1d4832258e098d75fc443ce 100644 --- a/Dockerfile.hf +++ b/Dockerfile.hf @@ -30,7 +30,6 @@ COPY --chown=1000:1000 api/ ./api/ COPY --chown=1000:1000 ml/ ./ml/ COPY --chown=1000:1000 data/ ./data/ COPY --chown=1000:1000 scripts/ ./scripts/ -COPY --chown=1000:1000 ui/ ./ui/ COPY --chown=1000:1000 run_api.py run_all.py ./ # Copy pre-trained model artifacts and data diff --git a/README.md b/README.md index 56c3338eda69f8e9b56625a9333ed2a034716f38..1aa39449aa5b3a70dc6820016e86753ef4893b31 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +--- +title: Kronector +emoji: 🏎️ +colorFrom: red +colorTo: gray +sdk: docker +app_port: 7860 +pinned: false +--- +
# Kronector: F1 Race Outcome Prediction & MLOps System @@ -58,11 +68,7 @@ To prove the model captures complex non-linear relationships rather than just pr | Pole Position Wins | ~42% | ### Feature Importance & ROC Proofs -*Mathematical proof that the model relies on logical racing factors.* -
- - -
+*Mathematical proof that the model relies on logical racing factors can be regenerated with `python -m scripts.generate_model_proofs`.* --- @@ -87,7 +93,6 @@ graph TD subgraph Serving E --> G[FastAPI Backend] G --> H[Multi-Agent LLM] - H --> I[React Frontend] end ``` @@ -125,17 +130,12 @@ python -m data.build_driver_map python -m scripts.auto_retrain_pipeline ``` -### 3. Run the API & Frontend +### 3. Run the API Create a `.env` file with `GROQ_API_KEY=your_key` and the `KRONECTOR_MODEL_RUN_ID` outputted by the training script. ```bash # Start backend python -m uvicorn api.main:app --reload - -# Start frontend (in a new terminal) -cd frontend -npm install -npm run dev ``` --- diff --git a/Screenshot 2026-06-11 123347.png b/Screenshot 2026-06-11 123347.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8a82d1545c8ab232c17c467a19b5fbf102c8cd --- /dev/null +++ b/Screenshot 2026-06-11 123347.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:108b99a2815fe1f01140d1efa380ed05ee2cc5473191b635c023a1699a1fe53c +size 216715 diff --git a/api/main.py b/api/main.py index 0bec246d8572785bc5ba2e678e8b06434b444852..859ea6da1de9e5df545311136309f6ee90948fe1 100644 --- a/api/main.py +++ b/api/main.py @@ -185,7 +185,7 @@ import os _cors_origins = os.getenv( "CORS_ORIGINS", - "http://localhost:3000,http://localhost:8501,http://localhost:5173", + "http://localhost:3000,http://localhost:8501,http://localhost:5173,http://localhost:8080,http://localhost:8000,null", ).split(",") app.add_middleware( diff --git a/docker-compose.yml b/docker-compose.yml index 1ca4d28ac0c63a56fa177b8d1828e0433bba8fa1..d2d3289f0d78efca3994102445b7210700eb4bff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,6 @@ # ============================================================================ # Services: # api — FastAPI prediction server (port 8000) -# streamlit — Drift monitoring dashboard (port 8501) # mlflow — MLflow tracking server (port 5000) # nginx — Reverse proxy (ports 80/443) # @@ -39,31 +38,6 @@ services: networks: - kronector-net - # -------------------------------------------------------------------------- - # Streamlit — Drift monitoring dashboard - # -------------------------------------------------------------------------- - streamlit: - build: - context: . - target: production - container_name: kronector-dashboard - command: > - streamlit run ui/monitoring_dashboard.py - --server.port=8501 - --server.address=0.0.0.0 - --server.headless=true - --browser.gatherUsageStats=false - ports: - - "8501:8501" - env_file: - - .env - volumes: - - model_data:/app/mlruns - - app_data:/app/data_output - restart: unless-stopped - networks: - - kronector-net - # -------------------------------------------------------------------------- # MLflow — Experiment tracking & model registry # -------------------------------------------------------------------------- diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 5ef6a520780202a1d6addd833d800ccb1ecac0bb..0000000000000000000000000000000000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# 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/frontend/AGENTS.md b/frontend/AGENTS.md deleted file mode 100644 index 8bd0e39085d5260e7f8faffcad2fdc45e10aef33..0000000000000000000000000000000000000000 --- a/frontend/AGENTS.md +++ /dev/null @@ -1,5 +0,0 @@ - -# This is NOT the Next.js you know - -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. - diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md deleted file mode 100644 index 43c994c2d3617f947bcb5adf1933e21dabe46bb5..0000000000000000000000000000000000000000 --- a/frontend/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 66bb426ffe6ec9f5d829fccff91afcf9f2d16810..0000000000000000000000000000000000000000 --- a/frontend/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. - -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/frontend/app/favicon.ico b/frontend/app/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 Binary files a/frontend/app/favicon.ico and /dev/null differ diff --git a/frontend/app/globals.css b/frontend/app/globals.css deleted file mode 100644 index 6e85aae5097138d8436c4c745607857614758357..0000000000000000000000000000000000000000 --- a/frontend/app/globals.css +++ /dev/null @@ -1,76 +0,0 @@ -@import "tailwindcss"; -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap'); - -@theme { - --color-saas-bg: #000000; - --color-saas-surface: #0A0A0A; - --color-saas-card: #111111; - --color-saas-border: #222222; - --color-saas-border-hover: #333333; - --color-saas-text-primary: #EDEDED; - --color-saas-text-secondary: #888888; - --color-saas-accent: #FFFFFF; - --color-saas-blue: #0070F3; - --color-saas-red: #E00000; - --color-saas-green: #00D084; - - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - --font-mono: 'Geist Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; -} - -@layer base { - :root { - --background: 0 0% 0%; - --foreground: 0 0% 93%; - --card: 0 0% 7%; - --card-foreground: 0 0% 93%; - --popover: 0 0% 7%; - --popover-foreground: 0 0% 93%; - --primary: 0 0% 100%; - --primary-foreground: 0 0% 0%; - --secondary: 0 0% 13%; - --secondary-foreground: 0 0% 93%; - --muted: 0 0% 13%; - --muted-foreground: 0 0% 53%; - --accent: 0 0% 13%; - --accent-foreground: 0 0% 93%; - --destructive: 0 100% 44%; - --destructive-foreground: 0 0% 100%; - --border: 0 0% 13%; - --input: 0 0% 13%; - --ring: 0 0% 100%; - --radius: 0.5rem; - } -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - background-color: var(--color-saas-bg); - color: var(--color-saas-text-primary); - font-family: var(--font-sans); - min-height: 100vh; - overflow-x: hidden; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* Elegant minimalist scrollbar */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} -::-webkit-scrollbar-track { - background: transparent; -} -::-webkit-scrollbar-thumb { - background: var(--color-saas-border); - border-radius: 4px; -} -::-webkit-scrollbar-thumb:hover { - background: var(--color-saas-border-hover); -} diff --git a/frontend/app/layout.js b/frontend/app/layout.js deleted file mode 100644 index 963fbd1b8658fc645634662fcfe897d54194b6c5..0000000000000000000000000000000000000000 --- a/frontend/app/layout.js +++ /dev/null @@ -1,14 +0,0 @@ -import "./globals.css"; - -export const metadata = { - title: "KRONECTOR F1 Intelligence", - description: "Predict F1 race outcomes using natural language queries powered by Hugging Face and MLflow.", -}; - -export default function RootLayout({ children }) { - return ( - - {children} - - ); -} diff --git a/frontend/app/page.js b/frontend/app/page.js deleted file mode 100644 index fb43045c9703354fd5bcb5f135150d4b52ee761a..0000000000000000000000000000000000000000 --- a/frontend/app/page.js +++ /dev/null @@ -1,33 +0,0 @@ -import Navbar from "@/components/saas/Navbar"; -import Hero from "@/components/saas/Hero"; -import PrimaryActions from "@/components/saas/PrimaryActions"; -import UpcomingGP from "@/components/saas/UpcomingGP"; -import ShapExplanation from "@/components/saas/ShapExplanation"; -import DriverComparison from "@/components/saas/DriverComparison"; -import Telemetry from "@/components/saas/Telemetry"; -import Metrics from "@/components/saas/Metrics"; - -export default function SaaSPage() { - return ( -
- - -
- - - - {/* Everything below the fold */} -
- - -
- -
- - - -
-
-
- ); -} diff --git a/frontend/app/page.module.css b/frontend/app/page.module.css deleted file mode 100644 index ad914ce7a06cc0ee7bb73cf3bf29ec5c4653e87e..0000000000000000000000000000000000000000 --- a/frontend/app/page.module.css +++ /dev/null @@ -1,365 +0,0 @@ -.main { - min-height: 100vh; - padding: 4rem 2rem; - display: flex; - flex-direction: column; - align-items: center; -} - -.header { - text-align: center; - margin-bottom: 4rem; -} - -.title { - font-size: 4rem; - margin-bottom: 1rem; - line-height: 1.1; - text-shadow: var(--shadow-glow); -} - -.subtitle { - font-size: 1.2rem; - color: var(--text-secondary); - max-width: 600px; - margin: 0 auto; -} - -/* Query Input Component */ -.queryContainer { - width: 100%; - max-width: 800px; - position: relative; - margin-bottom: 3rem; - z-index: 10; -} - -.inputWrapper { - position: relative; - width: 100%; - border-radius: var(--border-radius-lg); - background: var(--bg-glass); - backdrop-filter: blur(20px); - border: 1px solid rgba(0, 240, 255, 0.3); - box-shadow: var(--shadow-glow); - transition: var(--transition-smooth); -} - -.inputWrapper:focus-within { - box-shadow: 0 0 30px rgba(0, 240, 255, 0.5); - border-color: var(--neon-cyan); -} - -.input { - width: 100%; - background: transparent; - border: none; - padding: 1.5rem 4rem 1.5rem 2rem; - color: var(--text-primary); - font-size: 1.2rem; - font-family: inherit; - outline: none; -} - -.input::placeholder { - color: var(--text-muted); -} - -.submitBtn { - position: absolute; - right: 1rem; - top: 50%; - transform: translateY(-50%); - background: transparent; - border: none; - color: var(--neon-cyan); - cursor: pointer; - padding: 0.5rem; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition-fast); - border-radius: 50%; -} - -.submitBtn:hover { - background: rgba(0, 240, 255, 0.1); - box-shadow: var(--shadow-glow); -} - -.submitBtn:disabled { - color: var(--text-muted); - cursor: not-allowed; - background: transparent; - box-shadow: none; -} - -/* Loader */ -.loader { - border: 3px solid rgba(0, 240, 255, 0.1); - border-left-color: var(--neon-cyan); - border-radius: 50%; - width: 24px; - height: 24px; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Dashboard */ -.dashboardGrid { - display: grid; - grid-template-columns: 1fr 2fr; - gap: 2rem; - width: 100%; - max-width: 1200px; -} - -@media (max-width: 800px) { - .dashboardGrid { - grid-template-columns: 1fr; - } -} - -.panel { - padding: 2rem; - display: flex; - flex-direction: column; -} - -.panelTitle { - font-size: 1.2rem; - margin-bottom: 1.5rem; - color: var(--neon-cyan); - display: flex; - align-items: center; - gap: 0.5rem; - text-transform: uppercase; - letter-spacing: 2px; -} - -/* Gauge */ -.gaugeContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 2rem 0; -} - -.gaugeCircle { - position: relative; - width: 200px; - height: 200px; - border-radius: 50%; - background: conic-gradient(var(--neon-cyan) var(--prob), var(--bg-secondary) 0deg); - display: flex; - align-items: center; - justify-content: center; - box-shadow: var(--shadow-glow); - margin-bottom: 1.5rem; -} - -.gaugeCircle::before { - content: ""; - position: absolute; - inset: 15px; - background: var(--bg-primary); - border-radius: 50%; - border: 1px solid rgba(0, 240, 255, 0.2); -} - -.gaugeText { - position: relative; - font-size: 3.5rem; - font-weight: 800; - color: var(--text-primary); -} - -.gaugeLabel { - color: var(--text-secondary); - font-size: 1.1rem; - text-align: center; -} - -.rating { - margin-top: 1rem; - padding: 0.25rem 1rem; - border-radius: 20px; - font-size: 0.9rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; -} - -.ratingGood { - background: rgba(0, 255, 136, 0.1); - color: var(--neon-green); - border: 1px solid var(--neon-green); -} - -.ratingBad { - background: rgba(255, 42, 42, 0.1); - color: var(--neon-red); - border: 1px solid var(--neon-red); -} - -/* Insight */ -.insightText { - line-height: 1.8; - font-size: 1.1rem; - color: var(--text-primary); -} - -.insightHighlight { - color: var(--neon-cyan); - font-weight: 600; -} - -/* SHAP Bars */ -.shapContainer { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.shapRow { - display: grid; - grid-template-columns: 150px 1fr 60px; - align-items: center; - gap: 1rem; -} - -.shapLabel { - font-size: 0.9rem; - color: var(--text-secondary); - text-align: right; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.barTrack { - height: 12px; - background: rgba(255,255,255,0.05); - border-radius: 6px; - position: relative; - overflow: hidden; -} - -.barFill { - height: 100%; - position: absolute; - top: 0; - border-radius: 6px; - transition: width 1s cubic-bezier(0.16, 1, 0.3, 1); -} - -.barPositive { - background: linear-gradient(90deg, transparent, var(--neon-cyan)); - left: 50%; - box-shadow: var(--shadow-glow); -} - -.barNegative { - background: linear-gradient(270deg, transparent, var(--neon-red)); - right: 50%; - box-shadow: var(--shadow-glow-red); -} - -.shapValue { - font-size: 0.85rem; - font-family: var(--font-geist-mono), monospace; -} - -/* Tabs */ -.tabsContainer { - display: flex; - gap: 1rem; - margin-bottom: 2rem; - z-index: 10; -} - -.tabBtn { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 240, 255, 0.3); - color: var(--text-muted); - padding: 0.75rem 2rem; - border-radius: 20px; - cursor: pointer; - transition: var(--transition-fast); - font-family: inherit; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; -} - -.tabBtn:hover { - background: rgba(0, 240, 255, 0.1); - color: var(--neon-cyan); -} - -.tabActive { - background: rgba(0, 240, 255, 0.15); - border-color: var(--neon-cyan); - color: var(--neon-cyan); - box-shadow: 0 0 15px rgba(0, 240, 255, 0.3); -} - -/* Compare Form */ -.compareForm { - display: flex; - flex-direction: column; - gap: 1.5rem; - width: 100%; -} - -.compareInputsRow { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; -} - -.vsText { - font-size: 1.5rem; - font-weight: 800; - color: var(--text-muted); - font-style: italic; -} - -.compareInputsRowSmall { - display: flex; - gap: 1rem; - justify-content: center; -} - -.compareSubmitBtn { - background: linear-gradient(90deg, rgba(0, 240, 255, 0.2), rgba(0, 240, 255, 0.05)); - border: 1px solid var(--neon-cyan); - color: var(--neon-cyan); - padding: 1rem 2rem; - border-radius: var(--border-radius-lg); - cursor: pointer; - font-family: inherit; - font-size: 1.1rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 2px; - transition: var(--transition-fast); -} - -.compareSubmitBtn:hover:not(:disabled) { - background: rgba(0, 240, 255, 0.2); - box-shadow: var(--shadow-glow); -} - -.compareSubmitBtn:disabled { - border-color: rgba(0, 240, 255, 0.1); - color: var(--text-muted); - cursor: not-allowed; - background: transparent; -} diff --git a/frontend/components/saas/DriverComparison.tsx b/frontend/components/saas/DriverComparison.tsx deleted file mode 100644 index 2914c97586537786816cacf8136d46483830122f..0000000000000000000000000000000000000000 --- a/frontend/components/saas/DriverComparison.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; - -export default function DriverComparison() { - const data = [ - { metric: 'Qualifying Pace', Norris: 98, Verstappen: 95 }, - { metric: 'Race Pace', Norris: 95, Verstappen: 97 }, - { metric: 'Win Rate', Norris: 45, Verstappen: 52 }, - { metric: 'Avg Finish', Norris: 2.1, Verstappen: 1.8 }, - ]; - - return ( -
-
- -

Driver Comparison Results

-
- -
-
- - - - - - - - - - - -
-
-
- ); -} diff --git a/frontend/components/saas/Hero.tsx b/frontend/components/saas/Hero.tsx deleted file mode 100644 index d6c1749629b911e2b834377e3bbfee0d7128417f..0000000000000000000000000000000000000000 --- a/frontend/components/saas/Hero.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -export default function Hero() { - return ( -
-
- - LightGBM Model v2.1 Deployed -
- -

- Formula 1
- Intelligence Platform -

- -

- Predict race outcomes, compare drivers, analyze telemetry, and understand every prediction with mathematically-backed explainable AI. -

-
- ); -} diff --git a/frontend/components/saas/Metrics.tsx b/frontend/components/saas/Metrics.tsx deleted file mode 100644 index 2cfa51b056181f27e924a304be34b2b0323b8176..0000000000000000000000000000000000000000 --- a/frontend/components/saas/Metrics.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import { Database, ShieldCheck, Cpu } from "lucide-react"; - -export default function Metrics() { - const metrics = [ - { label: "Model Accuracy", value: "71.8%", icon: Cpu, sub: "Out of sample (2023-2025)" }, - { label: "Data Coverage", value: "8.2M", icon: Database, sub: "Rows of F1 Telemetry" }, - { label: "System Status", value: "Healthy", icon: ShieldCheck, sub: "No data drift detected" }, - ]; - - return ( -
-
- {metrics.map((m, i) => ( -
-
- -
-
-
{m.label}
-
{m.value}
-
{m.sub}
-
-
- ))} -
-
- ); -} diff --git a/frontend/components/saas/Navbar.tsx b/frontend/components/saas/Navbar.tsx deleted file mode 100644 index e5fc0d95cbae11e9b7e77ea8aba7987c75f6401f..0000000000000000000000000000000000000000 --- a/frontend/components/saas/Navbar.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; - -import { Activity } from "lucide-react"; -import Link from "next/link"; - -export default function Navbar() { - return ( - - ); -} diff --git a/frontend/components/saas/PrimaryActions.tsx b/frontend/components/saas/PrimaryActions.tsx deleted file mode 100644 index 7d6f66f839b290461d60afaf391dbb9cfde73ddb..0000000000000000000000000000000000000000 --- a/frontend/components/saas/PrimaryActions.tsx +++ /dev/null @@ -1,93 +0,0 @@ -"use client"; - -import { ArrowRight, Search, Zap } from "lucide-react"; -import { useState } from "react"; - -export default function PrimaryActions() { - const [query, setQuery] = useState(""); - - const handlePredict = (e: React.FormEvent) => { - e.preventDefault(); - if (query) { - document.getElementById('upcoming-gp')?.scrollIntoView({ behavior: 'smooth' }); - } - }; - - const handleCompare = (e: React.FormEvent) => { - e.preventDefault(); - document.getElementById('driver-comparison')?.scrollIntoView({ behavior: 'smooth' }); - }; - - return ( -
-
- - {/* Predict Card */} -
-
-
- - Predict a Race Outcome -
-

Ask any natural language question about an upcoming race.

-
- -
-
- - setQuery(e.target.value)} - placeholder="Who will win the Canadian GP?" - className="w-full bg-[#000000] border border-[#222222] rounded-lg py-4 pl-12 pr-4 text-white placeholder:text-[#444444] focus:outline-none focus:border-[#0070F3] focus:ring-1 focus:ring-[#0070F3] transition-all text-lg" - /> -
- -
- Who wins Monaco? - Predict Silverstone podium -
-
-
- - {/* Compare Card */} -
-
-
- - Compare Drivers -
-

Analyze head-to-head performance across metrics.

-
- -
-
-
- -
-
VS
-
- -
-
- -
-
- -
-
- ); -} diff --git a/frontend/components/saas/ShapExplanation.tsx b/frontend/components/saas/ShapExplanation.tsx deleted file mode 100644 index 58b07d341f3dc1e60caeb31129f268e7ee01ca38..0000000000000000000000000000000000000000 --- a/frontend/components/saas/ShapExplanation.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client"; - -import { Brain } from "lucide-react"; - -export default function ShapExplanation() { - const factors = [ - { name: "Grid Position", value: "+3.18", type: "positive" }, - { name: "Driver Form (Last 3)", value: "+1.39", type: "positive" }, - { name: "Team Perf Index", value: "+1.01", type: "positive" }, - { name: "Wet Weather Risk", value: "-0.85", type: "negative" }, - { name: "Reliability Concerns", value: "-1.22", type: "negative" }, - ]; - - return ( -
-
- -

Why The Model Thinks This

-
- -
-
-
Prediction Confidence
-
92%
-
MATHEMATICALLY SOUND
-
- -
-
Feature Contributions (SHAP)
- -
- {factors.map((f, i) => ( -
- {f.name} -
- {f.value} -
- {f.type === 'positive' ? ( -
- ) : ( -
- )} -
-
-
- ))} -
-
-
-
- ); -} diff --git a/frontend/components/saas/Telemetry.tsx b/frontend/components/saas/Telemetry.tsx deleted file mode 100644 index a61071050b8a018b2e31a1929d82420f3e2f1721..0000000000000000000000000000000000000000 --- a/frontend/components/saas/Telemetry.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { Activity } from "lucide-react"; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; - -export default function Telemetry() { - const data = Array.from({ length: 50 }).map((_, i) => ({ - lap: i + 1, - ver: 80 + Math.random() * 2 + (i * 0.05), // Verstappen lap times - nor: 80.5 + Math.random() * 1.5 + (i * 0.04), // Norris lap times - })); - - return ( -
-
- -

Telemetry Analysis

-
- -
-
-
Simulated Race Pace & Tire Degradation
-
-
Verstappen
-
Norris
-
-
- -
- - - - - - - - - - -
-
-
- ); -} diff --git a/frontend/components/saas/UpcomingGP.tsx b/frontend/components/saas/UpcomingGP.tsx deleted file mode 100644 index be1a8f28b61b4aacfaf9c93893cc3df857327495..0000000000000000000000000000000000000000 --- a/frontend/components/saas/UpcomingGP.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import { CloudRain, MapPin, Trophy } from "lucide-react"; - -export default function UpcomingGP() { - const predictions = [ - { name: "Norris", prob: 42, color: "bg-[#FF8700]" }, - { name: "Verstappen", prob: 31, color: "bg-[#0070F3]" }, - { name: "Leclerc", prob: 17, color: "bg-[#E00000]" }, - { name: "Piastri", prob: 10, color: "bg-[#FF8700]" }, - ]; - - return ( -
-
- -

Predicted Podium

-
- -
-
- Montreal, Canada - 40% Rain Probability -
- -
- {predictions.map((p, i) => ( -
-
{p.name}
-
-
-
-
{p.prob}%
-
- ))} -
-
-
- ); -} diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx deleted file mode 100644 index 8e87f2aeaef187e80a26bf438f593f5ecaa61db7..0000000000000000000000000000000000000000 --- a/frontend/components/ui/button.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground shadow-sm shadow-black/5 hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground shadow-sm shadow-black/5 hover:bg-destructive/90", - outline: - "border border-input bg-background shadow-sm shadow-black/5 hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground shadow-sm shadow-black/5 hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-lg px-3 text-xs", - lg: "h-10 rounded-lg px-8", - icon: "h-9 w-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - }, -); -Button.displayName = "Button"; - -export { Button, buttonVariants }; diff --git a/frontend/components/ui/popover.tsx b/frontend/components/ui/popover.tsx deleted file mode 100644 index 9064c0a73be3ec8bb422971fc1441d35bbc1abe7..0000000000000000000000000000000000000000 --- a/frontend/components/ui/popover.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import * as PopoverPrimitive from "@radix-ui/react-popover"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverAnchor = PopoverPrimitive.Anchor; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - showArrow?: boolean; - } ->(({ className, align = "center", sideOffset = 4, showArrow = false, ...props }, ref) => ( - - - {props.children} - {showArrow && ( - - )} - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }; diff --git a/frontend/components/ui/ruixen-query-box.tsx b/frontend/components/ui/ruixen-query-box.tsx deleted file mode 100644 index ee963491ccde6b871a4b398d823e87fa044dcabb..0000000000000000000000000000000000000000 --- a/frontend/components/ui/ruixen-query-box.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { SendHorizonal } from "lucide-react"; -import { useState } from "react"; -import { cn } from "@/lib/utils"; -import { Textarea } from "@/components/ui/textarea"; -import { useAutoResizeTextarea } from "@/hooks/use-auto-resize-textarea"; - -export default function RuixenQueryBox({ onSubmit }: { onSubmit: (query: string) => void }) { - const { textareaRef, adjustHeight } = useAutoResizeTextarea({ - minHeight: 56, - maxHeight: 220, - }); - - const [inputValue, setInputValue] = useState(""); - - const handleSend = () => { - if (!inputValue.trim()) return; - // Call the parent's submit handler instead of just console.log - onSubmit(inputValue); - setInputValue(""); - adjustHeight(true); - }; - - return ( -
-
-