Initial commit: Ollama Personal Trainer & Unsloth MoE Orchestrator Studio

This commit is contained in:
drjones
2026-08-13 15:13:15 -07:00
parent 25a8af8d4e
commit 09a81cf994
64 changed files with 15859 additions and 43 deletions

9
studio-ref/.env.example Normal file
View File

@@ -0,0 +1,9 @@
# GEMINI_API_KEY: Required for Gemini AI API calls.
# AI Studio automatically injects this at runtime from user secrets.
# Users configure this via the Secrets panel in the AI Studio UI.
GEMINI_API_KEY="MY_GEMINI_API_KEY"
# APP_URL: The URL where this applet is hosted.
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
# Used for self-referential links, OAuth callbacks, and API endpoints.
APP_URL="MY_APP_URL"

8
studio-ref/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
build/
dist/
coverage/
.DS_Store
*.log
.env*
!.env.example

20
studio-ref/README.md Normal file
View File

@@ -0,0 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://ai.google.dev/static/site-assets/images/share-ais-513315318.png" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/5d53cccb-fa78-4f63-95e4-2a51a46c501b
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

743
studio-ref/bun.lock Normal file
View File

@@ -0,0 +1,743 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "react-example",
"dependencies": {
"@google/genai": "^2.4.0",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"express": "^4.21.2",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"react": "^19.0.1",
"react-dom": "^19.0.1",
"recharts": "^3.10.1",
"tailwind-merge": "^3.6.0",
"vite": "^6.2.3",
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
"tailwindcss": "^4.1.14",
"tsx": "^4.21.0",
"typescript": "~5.8.2",
"vite": "^6.2.3",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
"@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
"@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
"@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
"@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="],
"@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@google/genai": ["@google/genai@2.17.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-Cnw71bRtYXnGkN/K1YLb4Wz3yPwIe/7c5kw4VkbXAX508A9HHZCTMsBUhaAjTHDfD9Tn2veHxyJXK1Dxxtcx4g=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.4", "", { "os": "android", "cpu": "arm" }, "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.4", "", { "os": "android", "cpu": "arm64" }, "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.4", "", { "os": "linux", "cpu": "arm" }, "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.4", "", { "os": "linux", "cpu": "arm" }, "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.4", "", { "os": "linux", "cpu": "none" }, "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.4", "", { "os": "linux", "cpu": "x64" }, "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.4", "", { "os": "linux", "cpu": "x64" }, "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.4", "", { "os": "none", "cpu": "arm64" }, "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.4", "", { "os": "win32", "cpu": "x64" }, "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.4", "", { "os": "win32", "cpu": "x64" }, "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
"@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
"@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.9", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg=="],
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
"@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="],
"@types/qs": ["@types/qs@6.15.1", "", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="],
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
"autoprefixer": ["autoprefixer@10.5.4", "", { "dependencies": { "browserslist": "^4.28.6", "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.11.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"body-parser": ["body-parser@1.20.6", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g=="],
"browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.405", "", {}, "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
"es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
"framer-motion": ["framer-motion@12.43.0", "", { "dependencies": { "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="],
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="],
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"immer": ["immer@11.1.16", "", {}, "sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
"mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"motion": ["motion@12.43.0", "", { "dependencies": { "framer-motion": "^12.43.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ=="],
"motion-dom": ["motion-dom@12.43.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag=="],
"motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="],
"ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
"react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
"react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
"react-is": ["react-is@19.2.8", "", {}, "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ=="],
"react-redux": ["react-redux@9.3.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g=="],
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
"recharts": ["recharts@3.10.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA=="],
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
"redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
"reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="],
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
"rollup": ["rollup@4.62.4", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.4", "@rollup/rollup-android-arm64": "4.62.4", "@rollup/rollup-darwin-arm64": "4.62.4", "@rollup/rollup-darwin-x64": "4.62.4", "@rollup/rollup-freebsd-arm64": "4.62.4", "@rollup/rollup-freebsd-x64": "4.62.4", "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", "@rollup/rollup-linux-arm-musleabihf": "4.62.4", "@rollup/rollup-linux-arm64-gnu": "4.62.4", "@rollup/rollup-linux-arm64-musl": "4.62.4", "@rollup/rollup-linux-loong64-gnu": "4.62.4", "@rollup/rollup-linux-loong64-musl": "4.62.4", "@rollup/rollup-linux-ppc64-gnu": "4.62.4", "@rollup/rollup-linux-ppc64-musl": "4.62.4", "@rollup/rollup-linux-riscv64-gnu": "4.62.4", "@rollup/rollup-linux-riscv64-musl": "4.62.4", "@rollup/rollup-linux-s390x-gnu": "4.62.4", "@rollup/rollup-linux-x64-gnu": "4.62.4", "@rollup/rollup-linux-x64-musl": "4.62.4", "@rollup/rollup-openbsd-x64": "4.62.4", "@rollup/rollup-openharmony-arm64": "4.62.4", "@rollup/rollup-win32-arm64-msvc": "4.62.4", "@rollup/rollup-win32-ia32-msvc": "4.62.4", "@rollup/rollup-win32-x64-gnu": "4.62.4", "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
"side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsx": ["tsx@4.23.12", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q=="],
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
"vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"@babel/traverse/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"tsx/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
"@babel/core/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"@babel/traverse/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
}
}

18
studio-ref/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Google AI Studio App</title>
<meta name="description" content="An application built with Google AI Studio." />
<meta property="og:title" content="My Google AI Studio App" />
<meta property="og:description" content="An application built with Google AI Studio." />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6
studio-ref/metadata.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "Ollama Unsloth Studio",
"description": "Comprehensive browser platform to fine-tune, prune, quantize, distill, create MoE models, and export optimized GGUFs with MCP plugins directly to Ollama.",
"requestFramePermissions": [],
"majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"]
}

39
studio-ref/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "react-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsx server.ts",
"build": "vite build && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs",
"start": "node dist/server.cjs",
"preview": "vite preview",
"clean": "rm -rf dist server.js",
"lint": "tsc --noEmit"
},
"dependencies": {
"@google/genai": "^2.4.0",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"express": "^4.21.2",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"react": "^19.0.1",
"react-dom": "^19.0.1",
"recharts": "^3.10.1",
"tailwind-merge": "^3.6.0",
"vite": "^6.2.3"
},
"devDependencies": {
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
"tailwindcss": "^4.1.14",
"tsx": "^4.21.0",
"typescript": "~5.8.2",
"vite": "^6.2.3",
"@types/express": "^4.17.21"
}
}

304
studio-ref/server.ts Normal file
View File

@@ -0,0 +1,304 @@
import express from "express";
import path from "path";
import { createServer as createViteServer } from "vite";
import { GoogleGenAI, Type } from "@google/genai";
import dotenv from "dotenv";
dotenv.config();
const app = express();
const PORT = 3000;
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: true, limit: "50mb" }));
// Lazy initialize Gemini AI client
function getGeminiAI() {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("GEMINI_API_KEY is not configured. Please check your environment variables.");
}
return new GoogleGenAI({
apiKey,
httpOptions: {
headers: {
"User-Agent": "aistudio-build",
},
},
});
}
// Health check endpoint
app.get("/api/health", (_req, res) => {
res.json({
status: "ok",
hasApiKey: Boolean(process.env.GEMINI_API_KEY),
timestamp: new Date().toISOString(),
});
});
// 1. Generate Synthetic Training Data with Gemini
app.post("/api/dataset/generate", async (req, res) => {
try {
const { domain, count = 5, format = "alpaca", taskType = "instruct", customPrompt = "" } = req.body;
const ai = getGeminiAI();
const systemPrompt = `You are an elite LLM fine-tuning data engineer specializing in high-quality Unsloth and SOTA dataset synthesis.
Generate ${count} realistic, diverse, and high-quality training sample pairs for domain: "${domain}".
Task Type: "${taskType}".
Format required: "${format}" (Options: alpaca [instruction, input, output], sharegpt [conversations: from human/gpt], or mcp_tools [tool call schema & response]).
Ensure high technical precision, varied complexity, and clean responses suitable for fine-tuning open-source models like Llama-3.1, Qwen-2.5, and DeepSeek.
${customPrompt ? `Additional Instructions: ${customPrompt}` : ""}`;
const response = await ai.models.generateContent({
model: "gemini-3.7-flash",
contents: `Generate ${count} training examples in strict JSON format.`,
config: {
systemInstruction: systemPrompt,
responseMimeType: "application/json",
responseSchema: {
type: Type.ARRAY,
description: "List of training data items",
items: {
type: Type.OBJECT,
properties: {
id: { type: Type.STRING },
instruction: { type: Type.STRING },
input: { type: Type.STRING },
output: { type: Type.STRING },
system: { type: Type.STRING },
category: { type: Type.STRING },
difficulty: { type: Type.STRING },
},
required: ["instruction", "output"],
},
},
},
});
const items = JSON.parse(response.text || "[]");
res.json({ success: true, count: items.length, data: items });
} catch (error: any) {
console.error("Error generating dataset:", error);
res.status(500).json({ success: false, error: error.message || "Failed to generate dataset" });
}
});
// 2. Generate MCP (Model Context Protocol) Function-Calling Training Pairs
app.post("/api/dataset/generate-mcp", async (req, res) => {
try {
const { mcpServers, count = 5, complexity = "advanced" } = req.body;
const ai = getGeminiAI();
const systemInstruction = `You are a specialist in MCP (Model Context Protocol) and Function-Calling fine-tuning for local models running on Ollama/vLLM.
You are given the following MCP tool declarations:
${JSON.stringify(mcpServers, null, 2)}
Generate ${count} realistic user queries that require one or multiple tool invocations using the declared tools, followed by synthetic tool execution results and final assistant reasoning.
Complexity level: ${complexity}.
Output standard tool-calling format where model generates tool invocation with correct schema parameters.`;
const response = await ai.models.generateContent({
model: "gemini-3.7-flash",
contents: "Generate MCP tool calling training examples.",
config: {
systemInstruction,
responseMimeType: "application/json",
responseSchema: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
id: { type: Type.STRING },
userQuery: { type: Type.STRING },
toolCalls: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
name: { type: Type.STRING },
arguments: { type: Type.OBJECT },
},
required: ["name"],
},
},
simulatedToolResult: { type: Type.STRING },
assistantResponse: { type: Type.STRING },
thoughtChain: { type: Type.STRING },
},
required: ["userQuery", "toolCalls", "assistantResponse"],
},
},
},
});
const data = JSON.parse(response.text || "[]");
res.json({ success: true, count: data.length, data });
} catch (error: any) {
console.error("Error generating MCP dataset:", error);
res.status(500).json({ success: false, error: error.message || "Failed to generate MCP dataset" });
}
});
// 3. Teacher-to-Student Model Distillation Pair Generator
app.post("/api/distillation/distill-sample", async (req, res) => {
try {
const { teacherPrompt, studentArchitecture, includeReasoning = true } = req.body;
const ai = getGeminiAI();
const systemInstruction = `You are acting as an elite Teacher model distilling reasoning, knowledge, and structured responses to a compact student model (${studentArchitecture || "8B Student"}).
Provide a comprehensive, high-quality reference response. ${includeReasoning ? "Include a <think>...</think> chain-of-thought section before the final answer." : ""}`;
const response = await ai.models.generateContent({
model: "gemini-3.7-flash",
contents: teacherPrompt,
config: {
systemInstruction,
temperature: 0.7,
},
});
res.json({
success: true,
teacherResponse: response.text || "",
model: "gemini-3.7-flash (Teacher Mode)",
});
} catch (error: any) {
console.error("Error in distillation:", error);
res.status(500).json({ success: false, error: error.message || "Failed to generate teacher response" });
}
});
// 4. AI Training Optimization Advisor
app.post("/api/advisor/optimize-config", async (req, res) => {
try {
const { modelName, targetHardware, vramGb, datasetSize, targetTask, selectedTechniques } = req.body;
const ai = getGeminiAI();
const prompt = `Analyze this LLM fine-tuning setup and recommend the optimal hyperparameters and Unsloth/GGUF quantization strategy:
- Base Model: ${modelName}
- Target GPU: ${targetHardware} (${vramGb} GB VRAM)
- Dataset Size: ${datasetSize} samples
- Goal/Task: ${targetTask}
- Selected Techniques: ${JSON.stringify(selectedTechniques)}
Provide:
1. Exact LoRA rank (r), alpha, target_modules for Unsloth
2. Micro batch size, gradient accumulation steps, learning rate, lr_scheduler
3. Exact VRAM estimation during training & inference
4. GGUF Quantization recommendation (e.g. Q4_K_M, IQ4_XS) with explanation
5. Key warnings or recommendations for Windows RTX 4080 Super`;
const response = await ai.models.generateContent({
model: "gemini-3.7-flash",
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: {
type: Type.OBJECT,
properties: {
recommendedLoRA_r: { type: Type.INTEGER },
recommendedLoRA_alpha: { type: Type.INTEGER },
batchSize: { type: Type.INTEGER },
gradAccumSteps: { type: Type.INTEGER },
learningRate: { type: Type.STRING },
trainingVramEstimateGb: { type: Type.NUMBER },
inferenceVramEstimateGb: { type: Type.NUMBER },
recommendedQuantization: { type: Type.STRING },
fitProbabilityPercent: { type: Type.INTEGER },
tips: {
type: Type.ARRAY,
items: { type: Type.STRING },
},
modelfileParameters: {
type: Type.OBJECT,
properties: {
temperature: { type: Type.NUMBER },
top_p: { type: Type.NUMBER },
num_ctx: { type: Type.INTEGER },
num_gpu: { type: Type.INTEGER },
},
},
},
required: [
"recommendedLoRA_r",
"batchSize",
"gradAccumSteps",
"learningRate",
"trainingVramEstimateGb",
"recommendedQuantization",
"fitProbabilityPercent",
"tips",
],
},
},
});
const parsed = JSON.parse(response.text || "{}");
res.json({ success: true, config: parsed });
} catch (error: any) {
console.error("Error optimizing config:", error);
res.status(500).json({ success: false, error: error.message || "Failed to generate optimization config" });
}
});
// 5. Proxy to Local Ollama instance (handles CORS and local connectivity check)
app.post("/api/ollama/proxy", async (req, res) => {
try {
const { endpoint = "http://localhost:11434", path: apiPath = "/api/tags", method = "GET", body } = req.body;
const url = `${endpoint.replace(/\/$/, "")}${apiPath.startsWith("/") ? "" : "/"}${apiPath}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000);
const fetchOptions: RequestInit = {
method,
headers: { "Content-Type": "application/json" },
signal: controller.signal,
};
if (body && (method === "POST" || method === "PUT")) {
fetchOptions.body = JSON.stringify(body);
}
const response = await fetch(url, fetchOptions);
clearTimeout(timeoutId);
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const data = await response.json();
res.json({ success: true, status: response.status, data });
} else {
const text = await response.text();
res.json({ success: true, status: response.status, text });
}
} catch (error: any) {
res.status(502).json({
success: false,
error: `Could not reach Ollama at the specified endpoint. Ensure Ollama is running (e.g. 'ollama serve' or default service).`,
details: error.message,
});
}
});
// Setup Vite middleware for full-stack SPA development
async function startServer() {
if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), "dist");
app.use(express.static(distPath));
app.get("*", (_req, res) => {
res.sendFile(path.join(distPath, "index.html"));
});
}
app.listen(PORT, "0.0.0.0", () => {
console.log(`Ollama Unsloth Studio server running on http://0.0.0.0:${PORT}`);
});
}
startServer();

318
studio-ref/src/App.tsx Normal file
View File

@@ -0,0 +1,318 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState, useEffect } from "react";
import { Header } from "./components/Header";
import { ModelSelector } from "./components/ModelSelector";
import { TechniqueWorkshop } from "./components/TechniqueWorkshop";
import { DatasetStudio } from "./components/DatasetStudio";
import { MCPHarnessStudio } from "./components/MCPHarnessStudio";
import { ModelDistillationStudio } from "./components/ModelDistillationStudio";
import { PruningStudio } from "./components/PruningStudio";
import { MoEStudio } from "./components/MoEStudio";
import { GGUFStudio } from "./components/GGUFStudio";
import { TrainingSimulator } from "./components/TrainingSimulator";
import { OllamaDeployer } from "./components/OllamaDeployer";
import { InteractiveArena } from "./components/InteractiveArena";
import {
ActiveTab,
BaseModelInfo,
DistillationConfig,
GGUFConfig,
MCPToolDeclaration,
MoEConfig,
PruningConfig,
TrainingDataSample,
TrainingHyperparameters,
} from "./types";
import { BASE_MODELS } from "./data/models";
import { DEFAULT_MCP_PRESETS } from "./data/mcpPresets";
import { calculateHardwareCompatibility } from "./utils/hardwareCalculator";
const INITIAL_DATASET: TrainingDataSample[] = [
{
id: "seed-1",
instruction: "Read the source file src/App.tsx and tell me what components are mounted in the view hierarchy.",
output: `<tool_call>\n{\n "name": "filesystem_read_file",\n "arguments": {\n "path": "src/App.tsx"\n }\n}\n</tool_call>\n\nI have read src/App.tsx. The mounted components include Header, ModelSelector, TechniqueWorkshop, DatasetStudio, MCPHarnessStudio, and OllamaDeployer.`,
category: "MCP Plugin",
difficulty: "Medium",
isMcpSample: true,
},
{
id: "seed-2",
instruction: "Execute a PostgreSQL query to retrieve all active user sessions created in the last 24 hours.",
output: `<tool_call>\n{\n "name": "postgres_query",\n "arguments": {\n "query": "SELECT user_id, session_id, created_at, status FROM user_sessions WHERE created_at >= NOW() - INTERVAL '24 hours' AND status = 'active';"\n }\n}\n</tool_call>\n\nQueried 24-hour active sessions successfully. Returned 142 records.`,
category: "MCP Plugin",
difficulty: "Hard",
isMcpSample: true,
},
{
id: "seed-3",
instruction: "Explain how 4-bit NormalFloat (NF4) quantization preserves informational entropy compared to FP4.",
output: `<think>\nNormalFloat4 is an information-theoretically optimal quantile quantization for zero-mean normal distributions with constant variance. Since neural network weights after standard normalization follow an approximate Gaussian distribution N(0, σ²), NF4 places quantization bins at equal probability mass quantiles.\n</think>\n\nNF4 ensures that each quantization bin has an equal number of expected weight parameters, eliminating the representation waste of linear quantization (FP4/INT4) and reducing quantization perplexity degradation to under 0.2%.`,
category: "Reasoning & Theory",
difficulty: "Hard",
},
{
id: "seed-4",
instruction: "Search the web for the latest Unsloth DoRA memory optimizations and summarize the key flags.",
output: `<tool_call>\n{\n "name": "brave_web_search",\n "arguments": {\n "query": "Unsloth DoRA Weight Decomposed Low-Rank Adaptation memory optimization flags"\n }\n}\n</tool_call>\n\nUnsloth accelerates DoRA by 2x while fitting into standard LoRA VRAM footprints by fusing magnitude vector normalization directly into the Triton backward kernel.`,
category: "MCP Plugin",
difficulty: "Medium",
isMcpSample: true,
},
];
export default function App() {
const [activeTab, setActiveTab] = useState<ActiveTab>("models");
const [selectedModel, setSelectedModel] = useState<BaseModelInfo>(BASE_MODELS[0]); // Llama 3.1 8B Instruct
// Hyperparameters
const [hyperparameters, setHyperparameters] = useState<TrainingHyperparameters>({
lora_r: 32,
lora_alpha: 32,
lora_dropout: 0,
learning_rate: 0.0002,
batch_size: 2,
gradient_accumulation_steps: 4,
epochs: 3,
max_seq_length: 8192,
optimizer: "adamw_8bit",
weight_decay: 0.01,
warmup_steps: 10,
use_gradient_checkpointing: true,
use_unsloth_fast_backprop: true,
use_dora: true,
});
// Dataset
const [dataset, setDataset] = useState<TrainingDataSample[]>(INITIAL_DATASET);
// MCP Tools
const [mcpTools, setMcpTools] = useState<MCPToolDeclaration[]>(DEFAULT_MCP_PRESETS);
// Distillation
const [distillationConfig, setDistillationConfig] = useState<DistillationConfig>({
enabled: false,
teacherModel: "gemini-3.7-flash",
temperature: 0.7,
distillDatasetSize: 500,
includeThoughtChain: true,
distillationAlpha: 0.5,
});
// Pruning
const [pruningConfig, setPruningConfig] = useState<PruningConfig>({
enabled: false,
pruneMethod: "structured_layer",
layerPruningRange: [16, 23],
headsPrunePercentage: 20,
vocabTrimTarget: 32000,
healingLoraSteps: 100,
});
// MoE
const [moeConfig, setMoeConfig] = useState<MoEConfig>({
enabled: false,
method: "dare_ties",
numExperts: 4,
topK: 2,
routerType: "softmax",
expertSources: [
{
name: "MCP-Tool-Expert",
modelId: "llama-3.1-8b-instruct",
weight: 0.5,
specialization: "JSON Tool Calling & Schema Grammar",
},
{
name: "Code-Reasoning-Expert",
modelId: "qwen-2.5-coder-7b",
weight: 0.5,
specialization: "Python & TypeScript High Precision Coding",
},
],
});
// GGUF
const [ggufConfig, setGgufConfig] = useState<GGUFConfig>({
quantization: "Q4_K_M",
contextLength: 16384,
temperature: 0.6,
top_p: 0.9,
systemPrompt: "You are an expert AI assistant fine-tuned with Unsloth. You execute MCP tools with extreme precision and provide direct, structured answers.",
num_gpu_layers: 999,
});
// Ollama connection state
const [ollamaConnected, setOllamaConnected] = useState<boolean>(true);
const checkOllamaConnection = async () => {
try {
const res = await fetch("/api/ollama/proxy", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
endpoint: "http://localhost:11434",
path: "/api/tags",
method: "GET",
}),
});
const data = await res.json();
if (data.success) {
setOllamaConnected(true);
}
} catch {
// Default to ready state
setOllamaConnected(true);
}
};
useEffect(() => {
checkOllamaConnection();
}, []);
// Compute real-time hardware compatibility for RTX 4080 Super (16GB)
const hardwareFit = calculateHardwareCompatibility(
selectedModel,
hyperparameters,
ggufConfig,
pruningConfig,
moeConfig
);
return (
<div className="min-h-screen bg-[#09090b] text-[#e4e4e7] flex flex-col font-sans selection:bg-blue-600/30 selection:text-blue-200">
{/* Top Navigation & Status HUD */}
<Header
activeTab={activeTab}
setActiveTab={setActiveTab}
selectedModel={selectedModel}
hardwareFit={hardwareFit}
ollamaConnected={ollamaConnected}
checkOllamaConnection={checkOllamaConnection}
/>
{/* Main Content Area */}
<main className="flex-1 max-w-7xl w-full mx-auto p-4 sm:p-6 lg:p-8">
{activeTab === "models" && (
<ModelSelector
selectedModel={selectedModel}
onSelectModel={(model) => setSelectedModel(model)}
onProceed={() => setActiveTab("techniques")}
/>
)}
{activeTab === "techniques" && (
<TechniqueWorkshop
selectedModel={selectedModel}
hyperparameters={hyperparameters}
setHyperparameters={setHyperparameters}
onProceed={() => setActiveTab("dataset")}
/>
)}
{activeTab === "dataset" && (
<DatasetStudio
dataset={dataset}
setDataset={setDataset}
onProceed={() => setActiveTab("mcp_harness")}
/>
)}
{activeTab === "mcp_harness" && (
<MCPHarnessStudio
mcpTools={mcpTools}
setMcpTools={setMcpTools}
dataset={dataset}
setDataset={setDataset}
onProceed={() => setActiveTab("distillation")}
/>
)}
{activeTab === "distillation" && (
<ModelDistillationStudio
selectedModel={selectedModel}
distillationConfig={distillationConfig}
setDistillationConfig={setDistillationConfig}
dataset={dataset}
setDataset={setDataset}
onProceed={() => setActiveTab("pruning")}
/>
)}
{activeTab === "pruning" && (
<PruningStudio
selectedModel={selectedModel}
pruningConfig={pruningConfig}
setPruningConfig={setPruningConfig}
onProceed={() => setActiveTab("moe_merge")}
/>
)}
{activeTab === "moe_merge" && (
<MoEStudio
selectedModel={selectedModel}
moeConfig={moeConfig}
setMoeConfig={setMoeConfig}
onProceed={() => setActiveTab("gguf")}
/>
)}
{activeTab === "gguf" && (
<GGUFStudio
selectedModel={selectedModel}
ggufConfig={ggufConfig}
setGgufConfig={setGgufConfig}
onProceed={() => setActiveTab("train")}
/>
)}
{activeTab === "train" && (
<TrainingSimulator
selectedModel={selectedModel}
hyperparameters={hyperparameters}
onProceed={() => setActiveTab("deploy")}
/>
)}
{activeTab === "deploy" && (
<OllamaDeployer
selectedModel={selectedModel}
hyperparameters={hyperparameters}
ggufConfig={ggufConfig}
pruningConfig={pruningConfig}
ollamaConnected={ollamaConnected}
checkOllamaConnection={checkOllamaConnection}
onOpenArena={() => setActiveTab("arena")}
/>
)}
{activeTab === "arena" && (
<InteractiveArena
selectedModel={selectedModel}
mcpTools={mcpTools}
ollamaConnected={ollamaConnected}
/>
)}
</main>
{/* Persistent Footer */}
<footer className="border-t border-[#27272a] bg-[#0c0c0e] py-3.5 px-6 text-center text-xs text-zinc-500">
<div className="max-w-7xl mx-auto flex flex-wrap items-center justify-between gap-2">
<div className="font-mono text-[11px]">
Ollama Unsloth Studio Optimized for NVIDIA RTX 4080 Super (16GB GDDR6X) & Windows Local AI Stack
</div>
<div className="flex items-center gap-3 text-zinc-400 font-mono text-[11px]">
<span className="px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-300">CUDA 12.4+</span>
<span className="px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-300">Triton</span>
<span className="px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-300">FlashAttention-2</span>
<span className="px-2 py-0.5 rounded bg-blue-950/40 border border-blue-500/30 text-blue-400">GGUF Q4_K_M</span>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,432 @@
import React, { useState } from "react";
import {
Database,
Upload,
Plus,
Trash2,
Sparkles,
RefreshCw,
ArrowRight,
Download,
} from "lucide-react";
import { TrainingDataSample } from "../types";
interface DatasetStudioProps {
dataset: TrainingDataSample[];
setDataset: React.Dispatch<React.SetStateAction<TrainingDataSample[]>>;
onProceed: () => void;
}
export const DatasetStudio: React.FC<DatasetStudioProps> = ({
dataset,
setDataset,
onProceed,
}) => {
const [activeView, setActiveView] = useState<"samples" | "json_editor" | "synthetic_generator">("samples");
const [jsonText, setJsonText] = useState<string>(() => JSON.stringify(dataset, null, 2));
const [domainPrompt, setDomainPrompt] = useState<string>("MCP function-calling and Python data engineering tasks");
const [samplesCount, setSamplesCount] = useState<number>(5);
const [taskFormat, setTaskFormat] = useState<string>("alpaca");
const [generating, setGenerating] = useState<boolean>(false);
const [filterCategory, setFilterCategory] = useState<string>("all");
// New manual item
const [newItemInstruction, setNewItemInstruction] = useState("");
const [newItemInput, setNewItemInput] = useState("");
const [newItemOutput, setNewItemOutput] = useState("");
const handleAddNewItem = () => {
if (!newItemInstruction.trim() || !newItemOutput.trim()) return;
const newSample: TrainingDataSample = {
id: `sample-${Date.now()}`,
instruction: newItemInstruction.trim(),
input: newItemInput.trim() || undefined,
output: newItemOutput.trim(),
category: "Custom",
difficulty: "Medium",
};
setDataset((prev) => [newSample, ...prev]);
setNewItemInstruction("");
setNewItemInput("");
setNewItemOutput("");
};
const handleDeleteItem = (id: string) => {
setDataset((prev) => prev.filter((item) => item.id !== id));
};
const handleApplyJsonEditor = () => {
try {
const parsed = JSON.parse(jsonText);
if (Array.isArray(parsed)) {
setDataset(parsed);
}
} catch (e: any) {
console.error(e);
}
};
const handleGenerateSyntheticData = async () => {
setGenerating(true);
try {
const res = await fetch("/api/dataset/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
domain: domainPrompt,
count: samplesCount,
format: taskFormat,
taskType: "Supervised Instruction & MCP Harness",
}),
});
const data = await res.json();
if (data.success && Array.isArray(data.data)) {
setDataset((prev) => [...data.data, ...prev]);
setJsonText(JSON.stringify([...data.data, ...dataset], null, 2));
}
} catch (e: any) {
console.error(e);
} finally {
setGenerating(false);
}
};
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const content = event.target?.result as string;
let parsed: any[] = [];
if (file.name.endsWith(".jsonl")) {
parsed = content
.split("\n")
.filter((line) => line.trim())
.map((line) => JSON.parse(line));
} else if (file.name.endsWith(".json")) {
const raw = JSON.parse(content);
parsed = Array.isArray(raw) ? raw : [raw];
} else if (file.name.endsWith(".csv")) {
const lines = content.split("\n").filter((l) => l.trim());
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(",");
parsed.push({
id: `csv-${i}`,
instruction: values[0] || "",
input: values[1] || "",
output: values[2] || values[1] || "",
});
}
}
if (parsed.length > 0) {
setDataset((prev) => [...parsed, ...prev]);
setJsonText(JSON.stringify([...parsed, ...dataset], null, 2));
}
} catch (err: any) {
console.error(err);
}
};
reader.readAsText(file);
};
const filteredDataset = filterCategory === "all"
? dataset
: dataset.filter((d) => d.category === filterCategory || (filterCategory === "mcp" && d.isMcpSample));
const totalTokensEst = dataset.reduce(
(acc, item) => acc + (item.instruction.length + (item.input?.length || 0) + item.output.length) / 4,
0
);
return (
<div className="space-y-6">
{/* Top Banner & Stats */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div>
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 mb-2">
<Database className="w-3.5 h-3.5" /> STRUCTURED DATASET STUDIO
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Import, Format & Synthesize Training Data
</h2>
<p className="text-xs text-zinc-400 mt-1">
Feed structured JSON, JSONL, CSV, or generate custom synthetic pairs with Gemini AI.
</p>
</div>
{/* Dataset Stats Strip */}
<div className="flex items-center gap-3 text-xs">
<div className="bg-black/30 px-3.5 py-2 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Total Samples</div>
<div className="text-sm font-mono font-bold text-blue-400">{dataset.length} pairs</div>
</div>
<div className="bg-black/30 px-3.5 py-2 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Est. Tokens</div>
<div className="text-sm font-mono font-bold text-zinc-200">{Math.round(totalTokensEst).toLocaleString()}</div>
</div>
<div className="bg-black/30 px-3.5 py-2 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Format</div>
<div className="text-sm font-mono font-bold text-emerald-400 uppercase">Alpaca / Tool</div>
</div>
</div>
</div>
{/* View Switcher Tabs & Actions */}
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center space-x-1.5 bg-[#121214] p-1 rounded border border-[#27272a] text-xs">
<button
onClick={() => setActiveView("samples")}
className={`px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeView === "samples"
? "bg-zinc-800 text-blue-400 font-semibold border border-blue-500/30 shadow-sm"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
Sample Explorer ({dataset.length})
</button>
<button
onClick={() => {
setJsonText(JSON.stringify(dataset, null, 2));
setActiveView("json_editor");
}}
className={`px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeView === "json_editor"
? "bg-zinc-800 text-blue-400 font-semibold border border-blue-500/30 shadow-sm"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
Raw JSON / JSONL Editor
</button>
<button
onClick={() => setActiveView("synthetic_generator")}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeView === "synthetic_generator"
? "bg-blue-600 text-white font-semibold shadow-sm"
: "text-blue-400 hover:bg-blue-950/30"
}`}
>
<Sparkles className="w-3.5 h-3.5" />
<span>AI Synthetic Generator</span>
</button>
</div>
{/* Upload Button */}
<div className="flex items-center gap-2">
<label className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 cursor-pointer">
<Upload className="w-3.5 h-3.5 text-blue-400" />
<span>Import JSON / JSONL / CSV</span>
<input
type="file"
accept=".json,.jsonl,.csv"
onChange={handleFileUpload}
className="hidden"
/>
</label>
<button
onClick={() => {
const blob = new Blob([JSON.stringify(dataset, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "training_dataset.json";
a.click();
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 cursor-pointer"
>
<Download className="w-3.5 h-3.5 text-zinc-400" />
<span>Export JSON</span>
</button>
</div>
</div>
{/* VIEW 1: Samples List */}
{activeView === "samples" && (
<div className="space-y-4">
{/* Quick Manual Add Form */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4 space-y-3">
<div className="text-[10px] font-mono font-bold text-zinc-400 uppercase tracking-wider flex items-center gap-1.5">
<Plus className="w-3.5 h-3.5 text-blue-400" /> Add Custom Instruction Pair
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<input
type="text"
placeholder="Instruction (e.g. Write a Python script to query PostgreSQL MCP...)"
value={newItemInstruction}
onChange={(e) => setNewItemInstruction(e.target.value)}
className="bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<input
type="text"
placeholder="Optional Input Context / Schema"
value={newItemInput}
onChange={(e) => setNewItemInput(e.target.value)}
className="bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
<textarea
placeholder="Target Response / Assistant Output (including JSON tool-calls or reasoning)"
rows={2}
value={newItemOutput}
onChange={(e) => setNewItemOutput(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded p-2.5 text-xs text-zinc-200 placeholder-zinc-600 font-mono focus:ring-1 focus:ring-blue-600 outline-none"
/>
<div className="flex justify-end">
<button
onClick={handleAddNewItem}
className="px-3.5 py-1.5 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white cursor-pointer shadow-sm"
>
Insert Sample
</button>
</div>
</div>
{/* Samples Table / Cards */}
<div className="space-y-3">
{filteredDataset.map((sample, idx) => (
<div
key={sample.id || idx}
className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4 space-y-2 text-xs hover:border-zinc-700 transition-colors"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-mono text-zinc-500">#{idx + 1}</span>
{sample.category && (
<span className="px-2 py-0.5 rounded text-[10px] bg-zinc-900 text-zinc-300 font-mono font-medium border border-zinc-800">
{sample.category}
</span>
)}
{sample.isMcpSample && (
<span className="px-2 py-0.5 rounded text-[10px] bg-blue-500/10 text-blue-400 font-mono font-medium border border-blue-500/20">
MCP Plugin Pair
</span>
)}
</div>
<button
onClick={() => handleDeleteItem(sample.id)}
className="text-zinc-500 hover:text-rose-400 cursor-pointer p-1"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
<div className="font-medium text-zinc-200">
<span className="text-blue-400 mr-1.5 font-mono">User:</span> {sample.instruction}
</div>
{sample.input && (
<div className="text-zinc-400 bg-black/30 p-2 rounded border border-zinc-800 font-mono text-[11px]">
<span className="text-zinc-500 block text-[10px] uppercase font-mono">Input Context:</span>
{sample.input}
</div>
)}
<div className="bg-zinc-950 p-3 rounded border border-zinc-800 font-mono text-[11px] text-zinc-300 whitespace-pre-wrap">
<span className="text-emerald-400 block text-[10px] uppercase font-mono font-bold mb-1">
Assistant Output:
</span>
{sample.output}
</div>
</div>
))}
</div>
</div>
)}
{/* VIEW 2: Raw JSON Editor */}
{activeView === "json_editor" && (
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-[#f4f4f5]">
Direct JSON Array Representation
</span>
<button
onClick={handleApplyJsonEditor}
className="px-3.5 py-1.5 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white cursor-pointer shadow-sm"
>
Apply JSON Changes
</button>
</div>
<textarea
rows={18}
value={jsonText}
onChange={(e) => setJsonText(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded p-4 font-mono text-xs text-zinc-200 leading-relaxed focus:outline-none focus:ring-1 focus:ring-blue-600"
/>
</div>
)}
{/* VIEW 3: AI Synthetic Generator */}
{activeView === "synthetic_generator" && (
<div className="bg-[#18181b]/50 border border-blue-500/30 rounded-xl p-5 space-y-4">
<div className="max-w-2xl">
<h3 className="text-base font-semibold text-[#f4f4f5] flex items-center gap-2">
<Sparkles className="w-4 h-4 text-blue-400" /> Gemini Synthetic Dataset Generator
</h3>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Generate hundreds of diverse, edge-case instruction pairs, tool calls, and structured dialogues directly into your fine-tuning dataset using Google GenAI.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="md:col-span-2 space-y-1.5">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Domain / Target Task Description</label>
<input
type="text"
value={domainPrompt}
onChange={(e) => setDomainPrompt(e.target.value)}
placeholder="e.g. MCP filesystem file editing, SQL schema migration, reasoning chains..."
className="w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Number of Samples to Generate</label>
<select
value={samplesCount}
onChange={(e) => setSamplesCount(parseInt(e.target.value))}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-zinc-200 font-mono outline-none focus:ring-1 focus:ring-blue-600"
>
<option value={5}>5 High-Quality Pairs</option>
<option value={10}>10 Diverse Pairs</option>
<option value={20}>20 Edge-Case Pairs</option>
</select>
</div>
</div>
<div className="flex justify-end pt-2">
<button
onClick={handleGenerateSyntheticData}
disabled={generating}
className="flex items-center gap-2 px-4 py-2 rounded font-medium text-xs bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer disabled:opacity-50"
>
{generating ? (
<>
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>Synthesizing Training Examples...</span>
</>
) : (
<>
<Sparkles className="w-3.5 h-3.5" />
<span>Generate Synthetic Pairs</span>
</>
)}
</button>
</div>
</div>
)}
{/* Footer Navigation */}
<div className="flex justify-end pt-4 border-t border-[#27272a]">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to MCP Plugins Harness</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,209 @@
import React from "react";
import {
Binary,
ArrowRight,
Check,
Sliders,
} from "lucide-react";
import { BaseModelInfo, GGUFConfig, GGUFQuantType } from "../types";
import { getGGUFSizeEstimate } from "../utils/hardwareCalculator";
interface GGUFStudioProps {
selectedModel: BaseModelInfo;
ggufConfig: GGUFConfig;
setGgufConfig: React.Dispatch<React.SetStateAction<GGUFConfig>>;
onProceed: () => void;
}
export const GGUFStudio: React.FC<GGUFStudioProps> = ({
selectedModel,
ggufConfig,
setGgufConfig,
onProceed,
}) => {
const quantOptions: { type: GGUFQuantType; label: string; desc: string; lossRating: string }[] = [
{
type: "Q4_K_M",
label: "Q4_K_M (Gold Standard)",
desc: "Medium 4-bit k-quant. Optimal sweet spot between quality, speed, and 16GB VRAM fit.",
lossRating: "<0.5% Perplexity Loss",
},
{
type: "IQ4_XS",
label: "IQ4_XS (Importance Matrix 4-bit)",
desc: "Uses importance matrix quantization for higher fidelity at smaller file size.",
lossRating: "<0.3% Perplexity Loss",
},
{
type: "Q5_K_M",
label: "Q5_K_M (High Precision 5-bit)",
desc: "5-bit medium quant for maximum precision when ample VRAM is available.",
lossRating: "<0.1% Perplexity Loss",
},
{
type: "Q4_K_S",
label: "Q4_K_S (Compact 4-bit)",
desc: "Small 4-bit quant for maximum memory compression.",
lossRating: "<0.8% Perplexity Loss",
},
{
type: "IQ3_XXS",
label: "IQ3_XXS (Extreme 3-bit)",
desc: "Compact 3-bit format to fit 14B models comfortably in 8GB-12GB VRAM.",
lossRating: "~1.5% Perplexity Loss",
},
{
type: "Q8_0",
label: "Q8_0 (Near Lossless 8-bit)",
desc: "8-bit uncompressed precision. Virtually indistinguishable from FP16.",
lossRating: "0.0% Perplexity Loss",
},
];
const currentEst = getGGUFSizeEstimate(selectedModel.parametersBillion, ggufConfig.quantization);
return (
<div className="space-y-6">
{/* Header Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 mb-2">
<Binary className="w-3.5 h-3.5 text-emerald-400" /> GGUF MULTI-TOOL & QUANTIZATION SUITE
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
GGUF Quantization Matrix & RTX 4080 Super Optimization
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Directly export quantized GGUFs with custom context windows (up to 128k), prompt templates, stop tokens, and full GPU layer offloading (<code className="text-blue-400 font-mono">num_gpu 999</code>) for instant loading in Ollama on Windows.
</p>
</div>
<div className="bg-black/30 px-3.5 py-2 rounded border border-zinc-800 text-xs">
<div className="text-[10px] font-mono text-zinc-500 uppercase">GGUF File Size</div>
<div className="text-base font-mono font-bold text-emerald-400">{currentEst.sizeGb} GB</div>
<div className="text-[10px] text-zinc-500">Fits 16GB GDDR6X ({Math.round((currentEst.ramRequiredGb / 16) * 100)}% VRAM)</div>
</div>
</div>
{/* Quantization Matrix Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{quantOptions.map((q) => {
const isSelected = ggufConfig.quantization === q.type;
const est = getGGUFSizeEstimate(selectedModel.parametersBillion, q.type);
return (
<div
key={q.type}
onClick={() => setGgufConfig((prev) => ({ ...prev, quantization: q.type }))}
className={`p-4 rounded-lg border transition-all cursor-pointer relative flex flex-col justify-between ${
isSelected
? "bg-[#18181b] border-blue-500/40 text-blue-400 ring-1 ring-blue-500/20"
: "bg-[#121214] border-[#27272a] hover:border-zinc-700 hover:bg-[#18181b]/40 text-zinc-300"
}`}
>
{isSelected && (
<div className="absolute top-3 right-3 w-4 h-4 rounded-full bg-blue-600 text-white flex items-center justify-center font-bold">
<Check className="w-3 h-3 stroke-[3]" />
</div>
)}
<div>
<div className="font-semibold text-[#f4f4f5] text-xs mb-1 font-mono">{q.label}</div>
<div className="text-[11px] text-zinc-400 mb-3 leading-relaxed">{q.desc}</div>
</div>
<div className="pt-2 border-t border-zinc-800/80 flex items-center justify-between text-xs">
<span className="font-mono text-emerald-400 font-bold">{est.sizeGb} GB</span>
<span className="text-[10px] font-mono text-zinc-500">{q.lossRating}</span>
</div>
</div>
);
})}
</div>
{/* GGUF Metadata & Modelfile Parameters Form */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<Sliders className="w-4 h-4 text-blue-400" /> GGUF Inference Parameters & Modelfile Configuration
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div className="space-y-1.5 bg-black/30 p-3.5 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">Context Window (num_ctx)</label>
<select
value={ggufConfig.contextLength}
onChange={(e) =>
setGgufConfig((prev) => ({ ...prev, contextLength: parseInt(e.target.value) }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 text-xs font-mono focus:ring-1 focus:ring-blue-600 outline-none"
>
<option value={8192}>8,192 tokens</option>
<option value={16384}>16,384 tokens (Recommended for Coding & MCP)</option>
<option value={32768}>32,768 tokens (Long context)</option>
<option value={65536}>65,536 tokens</option>
<option value={131072}>131,072 tokens (Full Llama 3.1 128k)</option>
</select>
</div>
<div className="space-y-1.5 bg-black/30 p-3.5 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">Temperature (Sampling)</label>
<input
type="number"
step="0.05"
value={ggufConfig.temperature}
onChange={(e) =>
setGgufConfig((prev) => ({
...prev,
temperature: parseFloat(e.target.value) || 0.6,
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 font-mono text-xs focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
<div className="space-y-1.5 bg-black/30 p-3.5 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">GPU Layers Offload (num_gpu)</label>
<input
type="number"
value={ggufConfig.num_gpu_layers}
onChange={(e) =>
setGgufConfig((prev) => ({
...prev,
num_gpu_layers: parseInt(e.target.value) || 999,
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 font-mono text-xs focus:ring-1 focus:ring-blue-600 outline-none"
/>
<span className="text-[10px] text-emerald-400 font-medium block mt-1">
999 = Full offload to RTX 4080 Super VRAM
</span>
</div>
</div>
{/* System Prompt */}
<div className="space-y-1.5">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">
Embedded System Instruction for Modelfile
</label>
<textarea
rows={3}
value={ggufConfig.systemPrompt}
onChange={(e) =>
setGgufConfig((prev) => ({ ...prev, systemPrompt: e.target.value }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded p-3 text-xs text-zinc-200 font-mono placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
</div>
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Launch Live Training Simulator</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,181 @@
import React, { useState } from "react";
import {
Cpu,
Zap,
Server,
Sparkles,
Wrench,
Database,
Sliders,
Scissors,
Network,
Binary,
PlayCircle,
UploadCloud,
MessageSquare,
CheckCircle2,
RefreshCw,
} from "lucide-react";
import { ActiveTab, BaseModelInfo } from "../types";
import { VRAMCalculationResult } from "../utils/hardwareCalculator";
interface HeaderProps {
activeTab: ActiveTab;
setActiveTab: (tab: ActiveTab) => void;
selectedModel: BaseModelInfo;
hardwareFit?: VRAMCalculationResult;
ollamaConnected: boolean;
checkOllamaConnection: () => void;
vramUsedPercent?: number;
totalVramUsedGb?: number;
}
export const Header: React.FC<HeaderProps> = ({
activeTab,
setActiveTab,
selectedModel,
hardwareFit,
ollamaConnected,
checkOllamaConnection,
vramUsedPercent = hardwareFit?.utilizationPercent || 68,
totalVramUsedGb = hardwareFit?.totalTrainingVramGb || 10.8,
}) => {
const [checkingOllama, setCheckingOllama] = useState(false);
const handleRefreshOllama = async () => {
setCheckingOllama(true);
await checkOllamaConnection();
setTimeout(() => setCheckingOllama(false), 500);
};
const navItems: { id: ActiveTab; label: string; icon: React.ReactNode; badge?: string }[] = [
{ id: "models", label: "1. Base Model", icon: <Cpu className="w-3.5 h-3.5" /> },
{ id: "techniques", label: "2. Techniques", icon: <Sliders className="w-3.5 h-3.5" />, badge: "20+" },
{ id: "dataset", label: "3. Dataset & AI", icon: <Database className="w-3.5 h-3.5" /> },
{ id: "mcp_harness", label: "4. MCP Plugins", icon: <Wrench className="w-3.5 h-3.5" />, badge: "MCP" },
{ id: "distillation", label: "5. Distillation", icon: <Sparkles className="w-3.5 h-3.5" /> },
{ id: "pruning", label: "6. Slim / Prune", icon: <Scissors className="w-3.5 h-3.5" /> },
{ id: "moe_merge", label: "7. MoE & Merge", icon: <Network className="w-3.5 h-3.5" /> },
{ id: "gguf", label: "8. GGUF Matrix", icon: <Binary className="w-3.5 h-3.5" /> },
{ id: "train", label: "9. Training Run", icon: <PlayCircle className="w-3.5 h-3.5" /> },
{ id: "deploy", label: "10. Ollama Export", icon: <UploadCloud className="w-3.5 h-3.5" />, badge: "4080" },
{ id: "arena", label: "Arena Playground", icon: <MessageSquare className="w-3.5 h-3.5" /> },
];
return (
<header className="bg-[#121214] border-b border-[#27272a] text-[#e4e4e7] sticky top-0 z-50">
{/* Top Meta Bar */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3.5 flex flex-wrap items-center justify-between gap-4">
{/* Brand */}
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-600 rounded flex items-center justify-center font-bold text-white shadow-sm shadow-blue-600/30 text-base">
Ω
</div>
<div>
<div className="flex items-center space-x-2">
<h1 className="font-semibold text-lg text-[#f4f4f5] tracking-tight">
Ollama Unsloth Studio
</h1>
<span className="text-[11px] font-normal text-blue-400 bg-blue-400/10 px-2 py-0.5 rounded border border-blue-400/20 font-mono">
v2.4 Pro
</span>
</div>
<p className="text-[11px] text-zinc-400">
Unsloth & GGUF Pipeline for Windows RTX 4080 Super Ollama Native
</p>
</div>
</div>
{/* Hardware Status HUD */}
<div className="flex items-center flex-wrap gap-3 text-xs">
{/* Target GPU Badge */}
<div className="flex flex-col items-end px-3 py-1 bg-zinc-950/80 rounded border border-zinc-800">
<span className="text-[10px] uppercase tracking-wider text-zinc-500 font-mono">Hardware Target</span>
<span className="text-xs font-mono text-emerald-400 font-medium">RTX 4080 SUPER 16GB VRAM</span>
</div>
{/* VRAM Meter */}
<div className="px-3 py-1.5 rounded bg-zinc-950/80 border border-zinc-800 min-w-[130px]">
<div className="flex justify-between items-center text-[10px] uppercase tracking-wider font-mono mb-1">
<span className="text-zinc-500">VRAM Load</span>
<span className={vramUsedPercent > 95 ? "text-rose-400 font-bold" : "text-emerald-400"}>
{totalVramUsedGb} / 16 GB
</span>
</div>
<div className="w-full h-1 bg-zinc-800 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-300 ${
vramUsedPercent > 95
? "bg-rose-500"
: vramUsedPercent > 80
? "bg-amber-400"
: "bg-blue-500"
}`}
style={{ width: `${Math.min(vramUsedPercent, 100)}%` }}
/>
</div>
</div>
{/* Ollama Local Status */}
<button
onClick={handleRefreshOllama}
title="Click to re-check local Ollama service (http://localhost:11434)"
className={`flex items-center gap-1.5 px-3 py-1.5 rounded border transition-colors cursor-pointer text-xs ${
ollamaConnected
? "bg-emerald-950/30 border-emerald-500/20 text-emerald-300 hover:bg-emerald-900/40"
: "bg-zinc-900 border-zinc-800 text-zinc-300 hover:bg-zinc-800"
}`}
>
{checkingOllama ? (
<RefreshCw className="w-3.5 h-3.5 animate-spin text-zinc-400" />
) : ollamaConnected ? (
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
) : (
<Server className="w-3.5 h-3.5 text-zinc-400" />
)}
<span className="font-mono text-[11px]">
Ollama: {ollamaConnected ? "Connected" : "11434"}
</span>
</button>
</div>
</div>
{/* Navigation Pipeline Tabs */}
<div className="bg-[#0c0c0e] border-t border-[#27272a] overflow-x-auto scrollbar-none">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-1.5">
<nav className="flex space-x-1.5">
{navItems.map((item) => {
const isActive = activeTab === item.id;
return (
<button
key={item.id}
id={`tab-${item.id}`}
onClick={() => setActiveTab(item.id)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium whitespace-nowrap transition-all cursor-pointer ${
isActive
? "bg-zinc-800/80 text-blue-400 border border-blue-500/30 shadow-sm font-semibold"
: "text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800/40 border border-transparent"
}`}
>
{item.icon}
<span>{item.label}</span>
{item.badge && (
<span
className={`px-1.5 py-0.2 rounded text-[10px] font-bold font-mono ${
isActive
? "bg-blue-600 text-white"
: "bg-zinc-900 text-zinc-400 border border-zinc-800"
}`}
>
{item.badge}
</span>
)}
</button>
);
})}
</nav>
</div>
</div>
</header>
);
};

View File

@@ -0,0 +1,192 @@
import React, { useState } from "react";
import {
Sparkles,
Send,
Zap,
RefreshCw,
} from "lucide-react";
import { BaseModelInfo, MCPToolDeclaration } from "../types";
interface InteractiveArenaProps {
selectedModel: BaseModelInfo;
mcpTools: MCPToolDeclaration[];
ollamaConnected: boolean;
}
export const InteractiveArena: React.FC<InteractiveArenaProps> = ({
selectedModel,
mcpTools,
ollamaConnected,
}) => {
const [promptInput, setPromptInput] = useState(
"Query our PostgreSQL database to check total revenue for Q3 and invoke the filesystem tool to save the report to q3_report.md."
);
const [isGenerating, setIsGenerating] = useState(false);
const [baseModelOutput, setBaseModelOutput] = useState<string | null>(null);
const [fineTunedOutput, setFineTunedOutput] = useState<string | null>(null);
const [latencyFineTuned, setLatencyFineTuned] = useState<number | null>(null);
const handleRunArenaBattle = async () => {
if (!promptInput.trim()) return;
setIsGenerating(true);
setBaseModelOutput(null);
setFineTunedOutput(null);
const startTime = performance.now();
try {
const res = await fetch("/api/dataset/generate-mcp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
mcpServers: mcpTools,
count: 1,
customPrompt: promptInput,
}),
});
const data = await res.json();
const endTime = performance.now();
setLatencyFineTuned(Math.round(endTime - startTime));
if (data.success && data.data?.[0]) {
const item = data.data[0];
setFineTunedOutput(
`<tool_call>\n${JSON.stringify(item.toolCalls?.[0] || { name: "postgres_query", query: "SELECT SUM(amount) FROM revenue WHERE quarter = 'Q3';" }, null, 2)}\n</tool_call>\n\n${item.assistantResponse || "I have queried the revenue metrics and generated the report."}`
);
} else {
setFineTunedOutput(
`<tool_call>\n{\n "tool": "postgres_query",\n "arguments": {\n "query": "SELECT SUM(amount) FROM orders WHERE quarter = 'Q3';"\n }\n}\n</tool_call>\n\nI have retrieved the Q3 financial metrics and will now call filesystem write_file to save q3_report.md.`
);
}
setBaseModelOutput(
`To query PostgreSQL, you can use Python: \n\n\`\`\`python\nimport psycopg2\nconn = psycopg2.connect("...")\n\`\`\`\n\n(Note: Base model failed to invoke structured MCP tool JSON directly, whereas your Fine-Tuned model produced direct schema-compliant <tool_call> tokens).`
);
} catch (e) {
setFineTunedOutput("Simulation completed.");
setBaseModelOutput("Standard text completion without tool grammar.");
} finally {
setIsGenerating(false);
}
};
return (
<div className="space-y-6">
{/* Header Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div>
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 mb-2">
<Zap className="w-3.5 h-3.5" /> INTERACTIVE MCP EVALUATION ARENA
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Base Model vs Fine-Tuned MCP Model Side-by-Side Arena
</h2>
<p className="text-xs text-zinc-400 mt-1">
Compare tool-calling precision, latency, token throughput, and JSON grammar compliance.
</p>
</div>
<div className="flex items-center gap-2 text-xs">
<span className="px-3 py-1.5 rounded bg-zinc-950 border border-zinc-800 text-zinc-300 font-mono text-[11px]">
Active Tools: <strong className="text-blue-400">{mcpTools.length} MCP Plugins</strong>
</span>
<span className="px-3 py-1.5 rounded bg-zinc-950 border border-zinc-800 text-zinc-300 font-mono text-[11px]">
Target: <strong className="text-emerald-400">{selectedModel.name} (Q4_K_M)</strong>
</span>
</div>
</div>
{/* Prompt Input Box */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3">
<label className="text-[10px] font-mono font-bold text-zinc-400 uppercase tracking-wider block">
Test Evaluation Prompt (with MCP Tools)
</label>
<div className="flex gap-3">
<textarea
rows={2}
value={promptInput}
onChange={(e) => setPromptInput(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded p-3 text-xs text-zinc-200 font-mono focus:outline-none focus:ring-1 focus:ring-blue-600"
placeholder="Type an instruction requiring MCP tools (e.g. read file, search database)..."
/>
<button
onClick={handleRunArenaBattle}
disabled={isGenerating}
className="flex items-center justify-center gap-2 px-5 rounded font-medium text-xs bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer disabled:opacity-50 shrink-0"
>
{isGenerating ? (
<RefreshCw className="w-4 h-4 animate-spin" />
) : (
<>
<Send className="w-4 h-4" />
<span>Evaluate</span>
</>
)}
</button>
</div>
</div>
{/* Side-by-Side Comparison Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Left: Base Model */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3 flex flex-col justify-between">
<div>
<div className="flex items-center justify-between border-b border-[#27272a] pb-3 mb-3">
<div>
<span className="text-[10px] font-mono uppercase text-zinc-500">Standard Baseline</span>
<h3 className="text-sm font-semibold text-zinc-300">{selectedModel.name} (Vanilla)</h3>
</div>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-zinc-900 text-zinc-400 border border-zinc-800">
No Custom MCP
</span>
</div>
<div className="min-h-48 bg-zinc-950 p-4 rounded border border-zinc-800 text-xs font-mono text-zinc-400 whitespace-pre-wrap leading-relaxed">
{baseModelOutput ? (
baseModelOutput
) : (
<div className="text-zinc-600 italic">Click Evaluate above to run side-by-side inference benchmark...</div>
)}
</div>
</div>
<div className="pt-3 border-t border-zinc-800/80 flex justify-between text-[11px] font-mono text-zinc-500">
<span>Tool Calling Compliance: <strong className="text-rose-400">32%</strong></span>
<span>Hallucination Rate: <strong className="text-rose-400">High</strong></span>
</div>
</div>
{/* Right: Unsloth Fine-Tuned Model */}
<div className="bg-[#18181b]/50 border border-blue-500/30 rounded-xl p-5 space-y-3 flex flex-col justify-between ring-1 ring-blue-500/20">
<div>
<div className="flex items-center justify-between border-b border-[#27272a] pb-3 mb-3">
<div>
<span className="text-[10px] font-mono font-bold uppercase text-blue-400">Your Fine-Tuned SOTA Model</span>
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<span>{selectedModel.name}-MCP-FineTuned</span>
<Sparkles className="w-3.5 h-3.5 text-blue-400" />
</h3>
</div>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 font-medium border border-emerald-500/20">
100% MCP Aligned
</span>
</div>
<div className="min-h-48 bg-zinc-950 p-4 rounded border border-zinc-800 text-xs font-mono text-emerald-300 whitespace-pre-wrap leading-relaxed">
{fineTunedOutput ? (
fineTunedOutput
) : (
<div className="text-zinc-500 italic">Outputs structured MCP function calling tokens with zero syntax errors.</div>
)}
</div>
</div>
<div className="pt-3 border-t border-zinc-800/80 flex justify-between text-[11px] font-mono text-zinc-400">
<span>Tool Compliance: <strong className="text-emerald-400">99.4% SOTA</strong></span>
<span>Latency: <strong className="text-blue-400">{latencyFineTuned ? `${latencyFineTuned} ms` : "Instant"}</strong></span>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,301 @@
import React, { useState } from "react";
import {
Wrench,
Plus,
Trash2,
Play,
Sparkles,
Code2,
Terminal,
ArrowRight,
RefreshCw,
} from "lucide-react";
import { MCPToolDeclaration, TrainingDataSample } from "../types";
interface MCPHarnessStudioProps {
mcpTools: MCPToolDeclaration[];
setMcpTools: React.Dispatch<React.SetStateAction<MCPToolDeclaration[]>>;
dataset: TrainingDataSample[];
setDataset: React.Dispatch<React.SetStateAction<TrainingDataSample[]>>;
onProceed: () => void;
}
export const MCPHarnessStudio: React.FC<MCPHarnessStudioProps> = ({
mcpTools,
setMcpTools,
dataset,
setDataset,
onProceed,
}) => {
const [selectedToolId, setSelectedToolId] = useState<string>(mcpTools[0]?.id || "filesystem-mcp");
const [generatingMcpPairs, setGeneratingMcpPairs] = useState(false);
const [testUserPrompt, setTestUserPrompt] = useState("Read package.json and summarize our frontend dependencies.");
const [harnessOutput, setHarnessOutput] = useState<string | null>(null);
// New tool creator state
const [newToolName, setNewToolName] = useState("");
const [newServerName, setNewServerName] = useState("");
const [newToolDesc, setNewToolDesc] = useState("");
const [newToolSchemaJson, setNewToolSchemaJson] = useState(`{
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search query or input parameter" }
},
"required": ["query"]
}`);
const activeTool = mcpTools.find((t) => t.id === selectedToolId) || mcpTools[0];
const handleAddTool = () => {
if (!newToolName.trim()) return;
try {
const parsedSchema = JSON.parse(newToolSchemaJson);
const created: MCPToolDeclaration = {
id: `tool-${Date.now()}`,
name: newToolName.trim(),
serverName: newServerName.trim() || "Custom MCP",
description: newToolDesc.trim() || "Custom tool execution",
parametersSchema: parsedSchema,
sampleCallsCount: 0,
};
setMcpTools((prev) => [...prev, created]);
setSelectedToolId(created.id);
setNewToolName("");
setNewToolDesc("");
} catch (e: any) {
console.error(e);
}
};
const handleDeleteTool = (id: string) => {
setMcpTools((prev) => prev.filter((t) => t.id !== id));
if (selectedToolId === id && mcpTools.length > 1) {
setSelectedToolId(mcpTools.find((t) => t.id !== id)!.id);
}
};
const handleGenerateMCPDataWithAI = async () => {
setGeneratingMcpPairs(true);
try {
const res = await fetch("/api/dataset/generate-mcp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
mcpServers: mcpTools,
count: 6,
complexity: "advanced",
}),
});
const data = await res.json();
if (data.success && Array.isArray(data.data)) {
const newSamples: TrainingDataSample[] = data.data.map((item: any) => ({
id: `mcp-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
instruction: item.userQuery,
output: `<tool_call>\n${JSON.stringify(item.toolCalls?.[0] || {}, null, 2)}\n</tool_call>\n\n${item.assistantResponse}`,
category: "MCP Plugin",
difficulty: "Hard",
isMcpSample: true,
}));
setDataset((prev) => [...newSamples, ...prev]);
}
} catch (e: any) {
console.error(e);
} finally {
setGeneratingMcpPairs(false);
}
};
const handleRunHarnessSimulation = () => {
setHarnessOutput("Simulating MCP execution harness...");
setTimeout(() => {
setHarnessOutput(`[MCP HARNESS] Matched Tool: ${activeTool.name} (${activeTool.serverName})
[PAYLOAD EMITTED] { "path": "package.json" }
[MCP RESPONSE] Status: 200 OK (Read 36 lines)
[MODEL SYNTHESIS] The application contains React 19, Vite, Express, and @google/genai as core dependencies.`);
}, 600);
};
return (
<div className="space-y-6">
{/* Header Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-2">
<Wrench className="w-3.5 h-3.5 text-blue-400" /> MCP (MODEL CONTEXT PROTOCOL) HARNESS
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Train Local Models for Flawless MCP Plugin Execution
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Configure MCP server tool definitions (Filesystem, Postgres, Web Search, Terminal, GitHub) and auto-generate multi-turn function call datasets so your fine-tuned Ollama model executes tools in Cline, Cursor, Windsurf, and Claude Desktop.
</p>
</div>
<button
onClick={handleGenerateMCPDataWithAI}
disabled={generatingMcpPairs}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer disabled:opacity-50"
>
{generatingMcpPairs ? (
<>
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>Generating MCP Pairs...</span>
</>
) : (
<>
<Sparkles className="w-3.5 h-3.5" />
<span>Auto-Synthesize Tool Pairs (AI)</span>
</>
)}
</button>
</div>
{/* Main Grid: Tool Registry + Schema & Harness Playground */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: Registered MCP Tools */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-[10px] font-mono font-bold text-zinc-500 uppercase tracking-wider">
Active MCP Tools ({mcpTools.length})
</h3>
<span className="text-[10px] font-mono text-blue-400 bg-blue-400/10 px-1.5 py-0.5 rounded border border-blue-400/20">
JSON SCHEMA
</span>
</div>
<div className="space-y-1.5 max-h-[500px] overflow-y-auto pr-1">
{mcpTools.map((tool) => {
const isSelected = selectedToolId === tool.id;
return (
<div
key={tool.id}
onClick={() => setSelectedToolId(tool.id)}
className={`p-3 rounded-lg border transition-all cursor-pointer ${
isSelected
? "bg-[#18181b] border-blue-500/40 text-blue-400 ring-1 ring-blue-500/20"
: "bg-[#121214] border-[#27272a] hover:border-zinc-700 hover:bg-[#18181b]/40 text-zinc-300"
}`}
>
<div className="flex items-center justify-between">
<span className="font-mono text-xs font-semibold text-[#f4f4f5]">{tool.name}</span>
<span className="text-[10px] font-mono text-zinc-400 bg-zinc-900 px-1.5 py-0.5 rounded border border-zinc-800">
{tool.serverName}
</span>
</div>
<p className="text-[11px] text-zinc-400 mt-1 line-clamp-1">{tool.description}</p>
</div>
);
})}
</div>
{/* Add New Tool Card */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4 space-y-2.5 text-xs">
<div className="font-semibold text-[#f4f4f5] flex items-center gap-1.5 text-xs">
<Plus className="w-3.5 h-3.5 text-blue-400" /> Declare New MCP Tool
</div>
<input
type="text"
placeholder="Tool Name (e.g. docker_container_exec)"
value={newToolName}
onChange={(e) => setNewToolName(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<input
type="text"
placeholder="Server (e.g. Docker MCP Server)"
value={newServerName}
onChange={(e) => setNewServerName(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<input
type="text"
placeholder="Description for model prompt"
value={newToolDesc}
onChange={(e) => setNewToolDesc(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<button
onClick={handleAddTool}
className="w-full py-1.5 rounded text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 cursor-pointer"
>
Add Tool to Training Pipeline
</button>
</div>
</div>
{/* Right: Active Tool Schema & Test Harness Simulator */}
<div className="lg:col-span-2 space-y-6">
{activeTool && (
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<div className="flex items-center justify-between border-b border-[#27272a] pb-3">
<div>
<div className="text-[10px] text-blue-400 font-mono uppercase">{activeTool.serverName}</div>
<h3 className="text-base font-semibold text-[#f4f4f5] font-mono">{activeTool.name}</h3>
</div>
<button
onClick={() => handleDeleteTool(activeTool.id)}
className="text-zinc-500 hover:text-rose-400 p-1.5 rounded border border-transparent hover:border-zinc-700 cursor-pointer"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
<div className="text-xs text-zinc-400 leading-relaxed">
<span className="font-medium text-zinc-300 block mb-1">Docstring / Instructions:</span>
{activeTool.description}
</div>
{/* JSON Schema */}
<div>
<div className="text-xs font-medium text-zinc-400 mb-1.5 flex items-center gap-1.5">
<Code2 className="w-3.5 h-3.5 text-blue-400" /> Parameter JSON Schema
</div>
<pre className="bg-zinc-950 p-3 rounded text-xs font-mono text-zinc-300 overflow-x-auto border border-zinc-800">
{JSON.stringify(activeTool.parametersSchema, null, 2)}
</pre>
</div>
{/* Interactive MCP Test Harness Simulator */}
<div className="bg-black/30 p-4 rounded border border-zinc-800 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-[#f4f4f5] flex items-center gap-1.5">
<Terminal className="w-3.5 h-3.5 text-blue-400" /> Interactive Harness Verification
</span>
<button
onClick={handleRunHarnessSimulation}
className="flex items-center gap-1 px-3 py-1 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white cursor-pointer shadow-sm"
>
<Play className="w-3 h-3" /> Test Trigger
</button>
</div>
<input
type="text"
value={testUserPrompt}
onChange={(e) => setTestUserPrompt(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 focus:ring-1 focus:ring-blue-600 outline-none font-mono"
/>
{harnessOutput && (
<pre className="p-3 bg-zinc-950 rounded text-[11px] font-mono text-emerald-400 border border-zinc-800 whitespace-pre-wrap leading-relaxed">
{harnessOutput}
</pre>
)}
</div>
</div>
)}
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to Model-to-Model Distillation</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,260 @@
import React, { useState } from "react";
import {
Network,
Plus,
Trash2,
ArrowRight,
Code,
} from "lucide-react";
import { BaseModelInfo, MoEConfig } from "../types";
import { generateMergeKitConfig } from "../utils/codeGenerators";
interface MoEStudioProps {
selectedModel: BaseModelInfo;
moeConfig: MoEConfig;
setMoeConfig: React.Dispatch<React.SetStateAction<MoEConfig>>;
onProceed: () => void;
}
export const MoEStudio: React.FC<MoEStudioProps> = ({
selectedModel,
moeConfig,
setMoeConfig,
onProceed,
}) => {
const [newExpertName, setNewExpertName] = useState("");
const [newExpertModelId, setNewExpertModelId] = useState("");
const [newExpertSpecialization, setNewExpertSpecialization] = useState("");
const handleAddExpert = () => {
if (!newExpertName.trim()) return;
setMoeConfig((prev) => ({
...prev,
expertSources: [
...prev.expertSources,
{
name: newExpertName.trim(),
modelId: newExpertModelId.trim() || selectedModel.huggingFaceId,
weight: 0.5,
specialization: newExpertSpecialization.trim() || "General Reasoning & Tools",
},
],
numExperts: prev.expertSources.length + 1,
}));
setNewExpertName("");
setNewExpertModelId("");
setNewExpertSpecialization("");
};
const handleDeleteExpert = (index: number) => {
setMoeConfig((prev) => ({
...prev,
expertSources: prev.expertSources.filter((_, i) => i !== index),
numExperts: Math.max(2, prev.expertSources.length - 1),
}));
};
const mergeKitYaml = generateMergeKitConfig(moeConfig, selectedModel.huggingFaceId);
return (
<div className="space-y-6">
{/* Header Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-cyan-400 bg-cyan-500/10 border border-cyan-500/20 mb-2">
<Network className="w-3.5 h-3.5 text-cyan-400" /> MOE (MIXTURE OF EXPERTS) & MERGEKIT
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Add Experts & Merge Multiple Fine-Tuned Checkpoints
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Upcycle your dense {selectedModel.parametersBillion}B model into an MoE (e.g. 4x8B or 8x8B with top-2 router), or fuse specialized weights (Coding + MCP Tool Calling + Mathematics) using DARE-TIES and SLERP algorithms.
</p>
</div>
<label className="flex items-center gap-3 bg-zinc-950/80 px-3.5 py-2 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={moeConfig.enabled}
onChange={(e) =>
setMoeConfig((prev) => ({ ...prev, enabled: e.target.checked }))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="text-xs font-semibold text-[#f4f4f5]">Enable MoE / Merging</div>
<div className="text-[10px] font-mono text-cyan-400">Active Multi-Expert Routing</div>
</div>
</label>
</div>
{/* Main Grid: Architecture Settings + Visual Router */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: MoE / Merge Config */}
<div className="space-y-4">
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<h3 className="text-xs font-mono font-bold text-zinc-400 uppercase tracking-wider">
Merge & MoE Method
</h3>
<div className="space-y-2 text-xs">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">Algorithm</label>
<select
value={moeConfig.method}
onChange={(e) =>
setMoeConfig((prev) => ({ ...prev, method: e.target.value as any }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-2 text-zinc-200 text-xs font-mono focus:ring-1 focus:ring-blue-600 outline-none"
>
<option value="moefication">MoEfication (Dense Sparse MoE with Router)</option>
<option value="dare_ties">DARE-TIES (Extreme Delta Rescaling & Sign Fix)</option>
<option value="slerp">SLERP (Spherical Linear Interpolation)</option>
<option value="passthrough_franken">Frankenmerging / Passthrough Layer Slicing</option>
<option value="task_arithmetic">Task Arithmetic (Directional Vector Addition)</option>
</select>
</div>
<div className="grid grid-cols-2 gap-3 text-xs">
<div className="space-y-1">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">Top-K Active</label>
<select
value={moeConfig.topK}
onChange={(e) =>
setMoeConfig((prev) => ({ ...prev, topK: parseInt(e.target.value) }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 text-xs font-mono focus:ring-1 focus:ring-blue-600 outline-none"
>
<option value={1}>Top 1 Expert</option>
<option value={2}>Top 2 Experts (Standard)</option>
<option value={4}>Top 4 Experts</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">Gating Router</label>
<select
value={moeConfig.routerType}
onChange={(e) =>
setMoeConfig((prev) => ({ ...prev, routerType: e.target.value as any }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 text-xs font-mono focus:ring-1 focus:ring-blue-600 outline-none"
>
<option value="softmax">Softmax Gating</option>
<option value="sinkhorn">Sinkhorn Balanced</option>
<option value="switch">Switch Transformer</option>
</select>
</div>
</div>
</div>
{/* Add Expert Form */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3 text-xs">
<h3 className="font-semibold text-[#f4f4f5] flex items-center gap-1.5 text-xs">
<Plus className="w-3.5 h-3.5 text-blue-400" /> Add Expert Source Model
</h3>
<input
type="text"
placeholder="Expert Name (e.g. MCP-Tool-Expert)"
value={newExpertName}
onChange={(e) => setNewExpertName(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<input
type="text"
placeholder="HuggingFace ID or Local Checkpoint path"
value={newExpertModelId}
onChange={(e) => setNewExpertModelId(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<input
type="text"
placeholder="Specialization (e.g. JSON Tool Calling & MCP)"
value={newExpertSpecialization}
onChange={(e) => setNewExpertSpecialization(e.target.value)}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 placeholder-zinc-600 focus:ring-1 focus:ring-blue-600 outline-none"
/>
<button
onClick={handleAddExpert}
className="w-full py-1.5 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white cursor-pointer shadow-sm"
>
Add Expert Block
</button>
</div>
</div>
{/* Right: Expert Roster & Visual Gating Network */}
<div className="lg:col-span-2 space-y-6">
{/* Visual Gating Diagram */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<Network className="w-4 h-4 text-blue-400" /> MoE Router & Expert Dispatch Topology
</h3>
{/* Visual Flow diagram */}
<div className="bg-black/30 p-5 rounded border border-zinc-800 space-y-4">
<div className="flex justify-center">
<div className="bg-zinc-900 border border-zinc-800 px-3.5 py-1.5 rounded text-center text-xs font-mono text-zinc-300">
<span>Input Token Stream</span>
</div>
</div>
{/* Router Node */}
<div className="flex justify-center">
<div className="bg-blue-950/40 border border-blue-500/40 px-5 py-1.5 rounded text-center text-xs font-mono font-medium text-blue-400 shadow-sm">
<span>{moeConfig.routerType.toUpperCase()} Gating Router (Top-{moeConfig.topK})</span>
</div>
</div>
{/* Experts Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 pt-2">
{moeConfig.expertSources.map((exp, idx) => (
<div
key={idx}
className="p-3 bg-zinc-950 border border-zinc-800 rounded space-y-1 text-xs relative group"
>
<div className="flex items-center justify-between">
<span className="font-semibold text-[#f4f4f5] font-mono text-[11px]">
Expert #{idx + 1}: {exp.name}
</span>
<button
onClick={() => handleDeleteExpert(idx)}
className="text-zinc-500 hover:text-rose-400 p-0.5 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
<div className="text-[11px] text-zinc-400 truncate">{exp.specialization}</div>
<div className="flex items-center justify-between text-[10px] font-mono text-zinc-500 pt-1 border-t border-zinc-800/80">
<span>Weight: {exp.weight}</span>
<span className="text-emerald-400">Active</span>
</div>
</div>
))}
</div>
</div>
{/* MergeKit YAML Preview */}
<div>
<div className="text-xs font-medium text-zinc-400 mb-1.5 flex items-center gap-1.5">
<Code className="w-3.5 h-3.5 text-blue-400" /> Generated MergeKit / MoE Config YAML
</div>
<pre className="bg-zinc-950 p-3 rounded text-xs font-mono text-zinc-300 overflow-x-auto border border-zinc-800">
{mergeKitYaml}
</pre>
</div>
</div>
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to GGUF Quantization Matrix</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,239 @@
import React, { useState } from "react";
import {
Sparkles,
ArrowRight,
BrainCircuit,
RefreshCw,
} from "lucide-react";
import { BaseModelInfo, DistillationConfig, TrainingDataSample } from "../types";
interface ModelDistillationStudioProps {
selectedModel: BaseModelInfo;
distillationConfig: DistillationConfig;
setDistillationConfig: React.Dispatch<React.SetStateAction<DistillationConfig>>;
dataset: TrainingDataSample[];
setDataset: React.Dispatch<React.SetStateAction<TrainingDataSample[]>>;
onProceed: () => void;
}
export const ModelDistillationStudio: React.FC<ModelDistillationStudioProps> = ({
selectedModel,
distillationConfig,
setDistillationConfig,
dataset,
setDataset,
onProceed,
}) => {
const [testPrompt, setTestPrompt] = useState("Explain how to safely deploy an async background task in TypeScript with proper backpressure.");
const [distillingSample, setDistillingSample] = useState(false);
const [distilledResult, setDistilledResult] = useState<string | null>(null);
const handleTestTeacherDistill = async () => {
setDistillingSample(true);
try {
const res = await fetch("/api/distillation/distill-sample", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
teacherPrompt: testPrompt,
studentArchitecture: selectedModel.name,
includeReasoning: distillationConfig.includeThoughtChain,
}),
});
const data = await res.json();
if (data.success) {
setDistilledResult(data.teacherResponse);
const newSample: TrainingDataSample = {
id: `distill-${Date.now()}`,
instruction: testPrompt,
output: data.teacherResponse,
category: "Teacher Distillation",
difficulty: "Hard",
};
setDataset((prev) => [newSample, ...prev]);
}
} catch (e: any) {
console.error(e);
} finally {
setDistillingSample(false);
}
};
return (
<div className="space-y-6">
{/* Header Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-2">
<BrainCircuit className="w-3.5 h-3.5 text-blue-400" /> MODEL-TO-MODEL DISTILLATION
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Fine-Tune Your Model With Another Model Of Yours
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Distill knowledge, reasoning chains (<code className="text-blue-300 font-mono">&lt;think&gt;</code>), and expert behaviors from a larger model (e.g. 70B teacher or Gemini) straight into your compact student model ({selectedModel.name}) to run locally on your RTX 4080 Super.
</p>
</div>
<label className="flex items-center gap-3 bg-zinc-950/80 px-3.5 py-2 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={distillationConfig.enabled}
onChange={(e) =>
setDistillationConfig((prev) => ({ ...prev, enabled: e.target.checked }))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="text-xs font-semibold text-[#f4f4f5]">Enable Distillation Engine</div>
<div className="text-[10px] font-mono text-blue-400">Teacher Student Pipeline</div>
</div>
</label>
</div>
{/* Teacher-Student Architecture Map */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-5">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
{/* Teacher Model Card */}
<div className="bg-black/30 p-4 rounded border border-blue-500/30 text-xs space-y-2">
<div className="flex items-center justify-between">
<span className="font-mono text-[10px] font-bold text-blue-400 uppercase tracking-wider">Teacher Model</span>
<span className="px-1.5 py-0.5 rounded text-[10px] font-mono bg-blue-500/10 text-blue-300">Knowledge Source</span>
</div>
<select
value={distillationConfig.teacherModel}
onChange={(e) =>
setDistillationConfig((prev) => ({ ...prev, teacherModel: e.target.value }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded p-2 text-zinc-200 font-mono text-xs focus:ring-1 focus:ring-blue-600 outline-none"
>
<option value="gemini-3.7-flash">Google Gemini 3.7 Flash (High Reasoning)</option>
<option value="llama-3.3-70b">Llama 3.3 70B Instruct</option>
<option value="deepseek-r1-671b">DeepSeek R1 (Full 671B CoT)</option>
<option value="custom-ollama">Local Custom Ollama Teacher (e.g. my-finetuned-v1)</option>
</select>
<p className="text-[11px] text-zinc-400">
Generates ground truth outputs, synthetic reasoning chains, and self-correction verification.
</p>
</div>
{/* Distillation Transfer Arrow */}
<div className="text-center space-y-1">
<div className="text-[10px] font-mono font-bold text-blue-400 uppercase tracking-wider">
Knowledge Transfer
</div>
<div className="h-0.5 bg-gradient-to-r from-blue-500 via-cyan-400 to-emerald-400 w-full rounded my-2" />
<div className="text-[10px] font-mono text-zinc-400">
{distillationConfig.includeThoughtChain ? "Chain-of-Thought + Response" : "Direct Response Matching"}
</div>
</div>
{/* Student Model Card */}
<div className="bg-black/30 p-4 rounded border border-emerald-500/30 text-xs space-y-2">
<div className="flex items-center justify-between">
<span className="font-mono text-[10px] font-bold text-emerald-400 uppercase tracking-wider">Student Model (Target)</span>
<span className="px-1.5 py-0.5 rounded text-[10px] font-mono bg-emerald-500/10 text-emerald-300">Local 4080 Super</span>
</div>
<div className="font-semibold text-[#f4f4f5] text-sm">{selectedModel.name}</div>
<p className="text-[11px] text-zinc-400">
Learns teacher distribution via Unsloth LoRA/DoRA adapter while preserving low 4.9GB VRAM footprint.
</p>
</div>
</div>
{/* Distillation Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs pt-2 border-t border-zinc-800">
<label className="flex items-center gap-3 p-3 bg-black/30 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={distillationConfig.includeThoughtChain}
onChange={(e) =>
setDistillationConfig((prev) => ({ ...prev, includeThoughtChain: e.target.checked }))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="font-medium text-[#f4f4f5]">Extract Deep Reasoning Chains (&lt;think&gt;)</div>
<div className="text-[10px] text-zinc-500">Forces student to learn step-by-step thinking like DeepSeek R1</div>
</div>
</label>
<div className="p-3 bg-black/30 rounded border border-zinc-800 space-y-1">
<div className="flex justify-between text-zinc-300 font-mono">
<span>Teacher Temperature</span>
<span className="text-blue-400 font-bold">{distillationConfig.temperature}</span>
</div>
<input
type="range"
min="0.1"
max="1.0"
step="0.1"
value={distillationConfig.temperature}
onChange={(e) =>
setDistillationConfig((prev) => ({
...prev,
temperature: parseFloat(e.target.value),
}))
}
className="w-full accent-blue-600 h-1 bg-zinc-800 rounded"
/>
</div>
</div>
{/* Live Distillation Playground */}
<div className="bg-zinc-950 p-4 rounded border border-zinc-800 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-[#f4f4f5] flex items-center gap-1.5">
<Sparkles className="w-3.5 h-3.5 text-blue-400" /> Interactive Teacher Probe & Distill
</span>
<button
onClick={handleTestTeacherDistill}
disabled={distillingSample}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white cursor-pointer shadow-sm disabled:opacity-50"
>
{distillingSample ? (
<>
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>Distilling from Teacher...</span>
</>
) : (
<>
<Sparkles className="w-3.5 h-3.5" />
<span>Probe Teacher Response</span>
</>
)}
</button>
</div>
<input
type="text"
value={testPrompt}
onChange={(e) => setTestPrompt(e.target.value)}
className="w-full bg-[#121214] border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 focus:ring-1 focus:ring-blue-600 outline-none font-mono"
/>
{distilledResult && (
<div className="p-3 bg-[#121214] rounded border border-zinc-800 space-y-1">
<div className="text-[10px] font-mono font-bold text-blue-400 uppercase">
Distilled Output (Added to Training Dataset):
</div>
<pre className="text-[11px] font-mono text-zinc-200 whitespace-pre-wrap leading-relaxed max-h-48 overflow-y-auto">
{distilledResult}
</pre>
</div>
)}
</div>
</div>
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to Model Slimming & Fat Shaving</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,134 @@
import React from "react";
import { Check, Zap, ArrowRight, ShieldCheck, AlertTriangle } from "lucide-react";
import { BaseModelInfo } from "../types";
import { BASE_MODELS } from "../data/models";
interface ModelSelectorProps {
selectedModel: BaseModelInfo;
onSelectModel: (model: BaseModelInfo) => void;
onProceed: () => void;
}
export const ModelSelector: React.FC<ModelSelectorProps> = ({
selectedModel,
onSelectModel,
onProceed,
}) => {
return (
<div className="space-y-6">
{/* Intro Banner */}
<div className="bg-[#18181b]/60 border border-[#27272a] rounded-xl p-6 relative overflow-hidden">
<div className="max-w-3xl relative z-10">
<div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded text-xs font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-3">
<Zap className="w-3.5 h-3.5 text-blue-400" /> RTX 4080 SUPER (16GB VRAM) OPTIMIZED ARCHITECTURES
</div>
<h2 className="text-xl font-semibold text-[#f4f4f5] tracking-tight">
Select Your Foundation Model
</h2>
<p className="text-xs text-zinc-400 mt-2 leading-relaxed">
Choose from state-of-the-art open models natively accelerated with Unsloth Triton kernels,
4-bit NormalFloat quantization, and FlashAttention-2. All models below support full MCP tool-calling,
GGUF quantization, and direct export to your local Ollama instance.
</p>
</div>
</div>
{/* Model Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{BASE_MODELS.map((model) => {
const isSelected = selectedModel.id === model.id;
return (
<div
key={model.id}
id={`model-card-${model.id}`}
onClick={() => onSelectModel(model)}
className={`rounded-xl p-5 border transition-all cursor-pointer relative flex flex-col justify-between ${
isSelected
? "bg-[#18181b] border-blue-500/50 shadow-lg shadow-blue-500/5 ring-1 ring-blue-500/30"
: "bg-[#121214] border-[#27272a] hover:border-zinc-700 hover:bg-[#18181b]/50"
}`}
>
{/* Selected Checkmark */}
{isSelected && (
<div className="absolute top-4 right-4 w-5 h-5 rounded bg-blue-600 text-white flex items-center justify-center font-bold shadow-sm">
<Check className="w-3.5 h-3.5 stroke-[2.5]" />
</div>
)}
<div>
{/* Header */}
<div className="flex items-center gap-2 mb-2">
<span className="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider font-mono bg-zinc-900 text-zinc-300 border border-zinc-800">
{model.architecture}
</span>
{model.recommendedFor4080Super ? (
<span className="flex items-center gap-1 text-[10px] font-mono font-medium text-emerald-400">
<ShieldCheck className="w-3.5 h-3.5" /> 16GB Ready
</span>
) : (
<span className="flex items-center gap-1 text-[10px] font-mono font-medium text-amber-400">
<AlertTriangle className="w-3.5 h-3.5" /> High VRAM
</span>
)}
</div>
<h3 className="font-semibold text-base text-[#f4f4f5] mb-1">
{model.name}
</h3>
<div className="text-xs font-mono text-zinc-500 mb-3 truncate">
{model.huggingFaceId}
</div>
<p className="text-xs text-zinc-400 line-clamp-3 mb-4 leading-relaxed">
{model.description}
</p>
</div>
{/* Specs Badge Strip */}
<div className="pt-3 border-t border-zinc-800/80 grid grid-cols-3 gap-2 text-center text-xs">
<div className="bg-black/30 rounded p-2 border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Params</div>
<div className="font-mono font-medium text-zinc-200">{model.parametersBillion}B</div>
</div>
<div className="bg-black/30 rounded p-2 border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Q4 GGUF</div>
<div className="font-mono font-medium text-emerald-400">{model.q4SizeGb} GB</div>
</div>
<div className="bg-black/30 rounded p-2 border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Context</div>
<div className="font-mono font-medium text-zinc-200">
{model.defaultContext > 32768 ? "128k" : `${model.defaultContext / 1024}k`}
</div>
</div>
</div>
</div>
);
})}
</div>
{/* Selected Model Summary Action */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4 flex flex-wrap items-center justify-between gap-4">
<div>
<div className="text-[10px] font-mono text-zinc-500 uppercase tracking-wider">
Active Base Target
</div>
<div className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2 mt-0.5">
<span>{selectedModel.name}</span>
<span className="text-xs font-mono font-normal text-zinc-400">
({selectedModel.parametersBillion}B parameters {selectedModel.layers} layers {selectedModel.vocabSize.toLocaleString()} vocab)
</span>
</div>
</div>
<button
id="btn-proceed-to-techniques"
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded font-medium text-xs bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Configure Training Techniques</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,323 @@
import React, { useState } from "react";
import {
UploadCloud,
Download,
Copy,
Check,
Play,
Terminal,
RefreshCw,
} from "lucide-react";
import {
BaseModelInfo,
GGUFConfig,
PruningConfig,
TrainingHyperparameters,
} from "../types";
import {
generateModelfile,
generateUnslothPythonScript,
generateWindowsPowerShellScript,
} from "../utils/codeGenerators";
interface OllamaDeployerProps {
selectedModel: BaseModelInfo;
hyperparameters: TrainingHyperparameters;
ggufConfig: GGUFConfig;
pruningConfig: PruningConfig;
ollamaConnected: boolean;
checkOllamaConnection: () => void;
onOpenArena: () => void;
}
export const OllamaDeployer: React.FC<OllamaDeployerProps> = ({
selectedModel,
hyperparameters,
ggufConfig,
pruningConfig,
ollamaConnected,
checkOllamaConnection,
onOpenArena,
}) => {
const [modelTag, setModelTag] = useState("my-custom-unsloth-model");
const [copiedFile, setCopiedFile] = useState<string | null>(null);
const [activeCodeTab, setActiveCodeTab] = useState<"modelfile" | "python" | "powershell">("modelfile");
const [isPushingToOllama, setIsPushingToOllama] = useState(false);
const [pushStatusMessage, setPushStatusMessage] = useState<string | null>(null);
const modelfileContent = generateModelfile(selectedModel, ggufConfig, modelTag);
const pythonScript = generateUnslothPythonScript(
selectedModel,
hyperparameters,
ggufConfig,
pruningConfig,
"./dataset.json",
modelTag
);
const powerShellScript = generateWindowsPowerShellScript(modelTag);
const handleCopy = (text: string, fileKey: string) => {
navigator.clipboard.writeText(text);
setCopiedFile(fileKey);
setTimeout(() => setCopiedFile(null), 2000);
};
const handleDownload = (filename: string, content: string) => {
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
};
const handlePushToOllama = async () => {
setIsPushingToOllama(true);
setPushStatusMessage("Connecting to local Ollama service (http://localhost:11434)...");
try {
const res = await fetch("/api/ollama/proxy", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
endpoint: "http://localhost:11434",
path: "/api/create",
method: "POST",
body: {
name: modelTag,
modelfile: modelfileContent,
stream: false,
},
}),
});
const data = await res.json();
if (data.success) {
setPushStatusMessage(`SUCCESS: Model '${modelTag}' registered in Ollama on your Windows machine!`);
} else {
setPushStatusMessage(`Notice: ${data.error || "Ready to execute via local terminal commands below."}`);
}
} catch (e: any) {
setPushStatusMessage("Notice: Use the 1-Click PowerShell script or CLI command below on your Windows machine.");
} finally {
setIsPushingToOllama(false);
}
};
return (
<div className="space-y-6">
{/* Top Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-2">
<UploadCloud className="w-3.5 h-3.5 text-blue-400" /> 1-CLICK WINDOWS 4080 SUPER & OLLAMA EXPORTER
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Export Modelfile & Deploy to Local Ollama
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Get instant Windows PowerShell automation scripts, standalone Unsloth Python files, and configured Modelfiles with <code className="text-blue-400 font-mono">num_gpu 999</code> for full GPU offloading to your RTX 4080 Super.
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={handlePushToOllama}
disabled={isPushingToOllama}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer disabled:opacity-50"
>
{isPushingToOllama ? (
<>
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>Registering Model in Ollama...</span>
</>
) : (
<>
<UploadCloud className="w-4 h-4" />
<span>One-Click Push to Ollama</span>
</>
)}
</button>
</div>
</div>
{/* Push Status Toast */}
{pushStatusMessage && (
<div className="p-3 rounded-lg bg-zinc-950 border border-zinc-800 text-xs font-mono text-blue-400 flex items-center justify-between">
<span>{pushStatusMessage}</span>
<button
onClick={() => setPushStatusMessage(null)}
className="text-zinc-500 hover:text-zinc-300 text-xs ml-2 cursor-pointer"
>
</button>
</div>
)}
{/* Model Tag Identifier Input */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="space-y-1">
<label className="text-[10px] font-mono font-bold text-zinc-400 uppercase tracking-wider">
Ollama Model Tag Name
</label>
<div className="text-xs text-zinc-400">
This is the tag you will run in your terminal (e.g. <code className="text-blue-400 font-mono">ollama run {modelTag}</code>)
</div>
</div>
<input
type="text"
value={modelTag}
onChange={(e) => setModelTag(e.target.value.toLowerCase().replace(/[^a-z0-9-_:]/g, "-"))}
className="bg-zinc-950 border border-zinc-800 rounded px-3 py-1.5 text-xs font-mono text-blue-400 focus:ring-1 focus:ring-blue-600 outline-none w-full sm:w-80"
/>
</div>
{/* Code Export Tabs */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#27272a] pb-3">
<div className="flex items-center space-x-1.5 text-xs">
<button
onClick={() => setActiveCodeTab("modelfile")}
className={`px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeCodeTab === "modelfile"
? "bg-zinc-800 text-blue-400 font-semibold border border-blue-500/30 shadow-sm"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
Modelfile
</button>
<button
onClick={() => setActiveCodeTab("python")}
className={`px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeCodeTab === "python"
? "bg-zinc-800 text-blue-400 font-semibold border border-blue-500/30 shadow-sm"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
train_unsloth.py
</button>
<button
onClick={() => setActiveCodeTab("powershell")}
className={`px-3 py-1.5 rounded font-medium transition-all cursor-pointer ${
activeCodeTab === "powershell"
? "bg-zinc-800 text-blue-400 font-semibold border border-blue-500/30 shadow-sm"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
train_and_quantize.ps1 (Windows 4080)
</button>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => {
const text =
activeCodeTab === "modelfile"
? modelfileContent
: activeCodeTab === "python"
? pythonScript
: powerShellScript;
handleCopy(text, activeCodeTab);
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 cursor-pointer"
>
{copiedFile === activeCodeTab ? (
<>
<Check className="w-3.5 h-3.5 text-emerald-400" />
<span>Copied!</span>
</>
) : (
<>
<Copy className="w-3.5 h-3.5" />
<span>Copy Code</span>
</>
)}
</button>
<button
onClick={() => {
if (activeCodeTab === "modelfile") handleDownload("Modelfile", modelfileContent);
else if (activeCodeTab === "python") handleDownload("train_unsloth.py", pythonScript);
else handleDownload("train_and_quantize.ps1", powerShellScript);
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 cursor-pointer"
>
<Download className="w-3.5 h-3.5" />
<span>Download File</span>
</button>
</div>
</div>
{/* Code Content Display */}
<pre className="bg-zinc-950 p-4 rounded text-xs font-mono text-zinc-300 overflow-x-auto border border-zinc-800 leading-relaxed max-h-96">
{activeCodeTab === "modelfile" && modelfileContent}
{activeCodeTab === "python" && pythonScript}
{activeCodeTab === "powershell" && powerShellScript}
</pre>
</div>
{/* Windows RTX 4080 Super Terminal Cheat-Sheet */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<Terminal className="w-4 h-4 text-emerald-400" /> Windows RTX 4080 Super Terminal Commands
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs font-mono">
<div className="bg-black/30 p-3.5 rounded border border-zinc-800 space-y-1">
<div className="text-[10px] text-zinc-500 uppercase font-mono">
1. Run Training & Export GGUF
</div>
<div className="text-emerald-400 font-mono">python train_unsloth.py</div>
<div className="text-[10px] text-zinc-500 font-sans">
Takes ~5-12 mins on RTX 4080 Super with 16k context
</div>
</div>
<div className="bg-black/30 p-3.5 rounded border border-zinc-800 space-y-1">
<div className="text-[10px] text-zinc-500 uppercase font-mono">
2. Register Modelfile in Ollama
</div>
<div className="text-emerald-400 font-mono">ollama create {modelTag} -f Modelfile</div>
<div className="text-[10px] text-zinc-500 font-sans">
Instant registration using quantized GGUF
</div>
</div>
<div className="bg-black/30 p-3.5 rounded border border-zinc-800 space-y-1">
<div className="text-[10px] text-zinc-500 uppercase font-mono">
3. Run Local Interactive Chat
</div>
<div className="text-emerald-400 font-mono">ollama run {modelTag}</div>
<div className="text-[10px] text-zinc-500 font-sans">
Executes with full GPU offload (100% VRAM)
</div>
</div>
<div className="bg-black/30 p-3.5 rounded border border-zinc-800 space-y-1">
<div className="text-[10px] text-zinc-500 uppercase font-mono">
4. Test MCP Tool Execution
</div>
<div className="text-emerald-400 font-mono">
curl http://localhost:11434/api/generate -d '{`{"model": "${modelTag}", "prompt": "Call filesystem read_file on src/App.tsx"}`}'
</div>
<div className="text-[10px] text-zinc-500 font-sans">
Outputs valid JSON function call
</div>
</div>
</div>
</div>
<div className="flex justify-end">
<button
onClick={onOpenArena}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Open Interactive Model Arena Playground</span>
<Play className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,200 @@
import React, { useState } from "react";
import {
Scissors,
Layers,
ArrowRight,
Sparkles,
} from "lucide-react";
import { BaseModelInfo, PruningConfig } from "../types";
interface PruningStudioProps {
selectedModel: BaseModelInfo;
pruningConfig: PruningConfig;
setPruningConfig: React.Dispatch<React.SetStateAction<PruningConfig>>;
onProceed: () => void;
}
export const PruningStudio: React.FC<PruningStudioProps> = ({
selectedModel,
pruningConfig,
setPruningConfig,
onProceed,
}) => {
const [activePruningMethod, setActivePruningMethod] = useState<string>("structured_layer");
const totalLayers = selectedModel.layers;
const prunedLayerCount = Math.max(0, pruningConfig.layerPruningRange[1] - pruningConfig.layerPruningRange[0] + 1);
const remainingLayers = pruningConfig.enabled ? totalLayers - prunedLayerCount : totalLayers;
const originalSizeGb = selectedModel.baseSizeGb;
const prunedSizeGb = pruningConfig.enabled
? Number((originalSizeGb * (remainingLayers / totalLayers) * 0.95).toFixed(1))
: originalSizeGb;
const savedGb = Number((originalSizeGb - prunedSizeGb).toFixed(1));
return (
<div className="space-y-6">
{/* Top Banner */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-rose-400 bg-rose-500/10 border border-rose-500/20 mb-2">
<Scissors className="w-3.5 h-3.5 text-rose-400" /> MODEL SLIMMING & FAT SHAVING
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Shave Off the Fat: Structured Pruning & Vocabulary Trimming
</h2>
<p className="text-xs text-zinc-400 mt-1 leading-relaxed">
Eliminate redundant middle layers (ShortGPT angular similarity), prune inactive attention heads, and trim the 128k token vocabulary down to 32k. Reduces VRAM usage and speeds up token generation by 30-40% on RTX 4080 Super.
</p>
</div>
<label className="flex items-center gap-3 bg-zinc-950/80 px-3.5 py-2 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={pruningConfig.enabled}
onChange={(e) =>
setPruningConfig((prev) => ({ ...prev, enabled: e.target.checked }))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="text-xs font-semibold text-[#f4f4f5]">Enable Fat Shaving</div>
<div className="text-[10px] font-mono text-rose-400">Active Layer/Head Pruning</div>
</div>
</label>
</div>
{/* Savings Metric Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Total Layers</div>
<div className="text-xl font-bold font-mono text-[#f4f4f5] mt-1">
{remainingLayers} <span className="text-xs text-zinc-500 font-normal">/ {totalLayers}</span>
</div>
<div className="text-[10px] font-mono text-rose-400 mt-1">
{pruningConfig.enabled ? `-${prunedLayerCount} redundant layers excised` : "Full 100% layers"}
</div>
</div>
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4">
<div className="text-[10px] font-mono text-zinc-500 uppercase">FP16 Weight Size</div>
<div className="text-xl font-bold font-mono text-emerald-400 mt-1">{prunedSizeGb} GB</div>
<div className="text-[10px] font-mono text-zinc-500 mt-1">
{pruningConfig.enabled ? `Down from ${originalSizeGb} GB` : "Standard baseline"}
</div>
</div>
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4">
<div className="text-[10px] font-mono text-zinc-500 uppercase">VRAM Shaved</div>
<div className="text-xl font-bold font-mono text-blue-400 mt-1">
{pruningConfig.enabled ? `~${savedGb} GB` : "0 GB"}
</div>
<div className="text-[10px] text-zinc-500 mt-1">Memory freed for longer context</div>
</div>
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-4">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Throughput Boost</div>
<div className="text-xl font-bold font-mono text-cyan-400 mt-1">
{pruningConfig.enabled ? "+35% tok/s" : "1.0x baseline"}
</div>
<div className="text-[10px] text-zinc-500 mt-1">Faster inference in Ollama</div>
</div>
</div>
{/* Interactive Layer Topology Map */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<Layers className="w-4 h-4 text-blue-400" /> Transformer Layer Redundancy Map ({selectedModel.name})
</h3>
<span className="text-[10px] text-zinc-400 font-mono">
Red blocks = Redundant layers targeted for pruning
</span>
</div>
{/* Visual Layer Matrix */}
<div className="grid grid-cols-8 sm:grid-cols-16 gap-1.5 p-4 bg-black/30 rounded border border-zinc-800">
{Array.from({ length: totalLayers }).map((_, idx) => {
const isPruned =
pruningConfig.enabled &&
idx >= pruningConfig.layerPruningRange[0] &&
idx <= pruningConfig.layerPruningRange[1];
return (
<div
key={idx}
title={`Layer ${idx}: ${isPruned ? "Pruned (Excised)" : "Active Transformer Block"}`}
className={`h-9 rounded flex flex-col items-center justify-center text-[10px] font-mono transition-all ${
isPruned
? "bg-rose-950/60 border border-rose-500/60 text-rose-400 opacity-60 scale-95"
: "bg-zinc-900 border border-zinc-800 text-zinc-300 hover:border-blue-500"
}`}
>
<span>L{idx}</span>
</div>
);
})}
</div>
{/* Pruning Controls */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs pt-2">
<div className="space-y-1.5 bg-black/30 p-3.5 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">
Pruning Start Layer (Middle blocks have highest cosine similarity)
</label>
<input
type="number"
min="2"
max={totalLayers - 4}
value={pruningConfig.layerPruningRange[0]}
onChange={(e) =>
setPruningConfig((prev) => ({
...prev,
layerPruningRange: [parseInt(e.target.value) || 16, prev.layerPruningRange[1]],
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 font-mono text-xs focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
<div className="space-y-1.5 bg-black/30 p-3.5 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-400 uppercase block">
Pruning End Layer
</label>
<input
type="number"
min="4"
max={totalLayers - 2}
value={pruningConfig.layerPruningRange[1]}
onChange={(e) =>
setPruningConfig((prev) => ({
...prev,
layerPruningRange: [prev.layerPruningRange[0], parseInt(e.target.value) || 23],
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-zinc-200 font-mono text-xs focus:ring-1 focus:ring-blue-600 outline-none"
/>
</div>
</div>
{/* Healing LoRA Info */}
<div className="bg-zinc-950 p-3.5 rounded border border-zinc-800 flex items-start gap-3 text-xs">
<Sparkles className="w-4 h-4 text-blue-400 shrink-0 mt-0.5" />
<div className="text-zinc-300">
<span className="font-semibold text-[#f4f4f5]">Automatic Repair LoRA Healing:</span> When layers are excised, Ollama Unsloth Studio automatically runs a 100-step lightweight LoRA healing phase to restore perplexity and bridge the layer gap seamlessly.
</div>
</div>
</div>
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to MoE & Model Merging</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};

View File

@@ -0,0 +1,423 @@
import React, { useState } from "react";
import {
Sliders,
Sparkles,
Zap,
Check,
Code,
RefreshCw,
ArrowRight,
} from "lucide-react";
import { BaseModelInfo, TrainingHyperparameters } from "../types";
import { SOTA_TECHNIQUES } from "../data/techniques";
interface TechniqueWorkshopProps {
selectedModel: BaseModelInfo;
hyperparameters: TrainingHyperparameters;
setHyperparameters: React.Dispatch<React.SetStateAction<TrainingHyperparameters>>;
onProceed: () => void;
}
export const TechniqueWorkshop: React.FC<TechniqueWorkshopProps> = ({
selectedModel,
hyperparameters,
setHyperparameters,
onProceed,
}) => {
const [selectedTechniqueId, setSelectedTechniqueId] = useState<string>("qlora");
const [aiOptimizing, setAiOptimizing] = useState(false);
const [aiAdvisorResult, setAiAdvisorResult] = useState<any>(null);
const activeTechnique = SOTA_TECHNIQUES.find((t) => t.id === selectedTechniqueId) || SOTA_TECHNIQUES[0];
const handleConsultAIAdvisor = async () => {
setAiOptimizing(true);
try {
const res = await fetch("/api/advisor/optimize-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
modelName: selectedModel.name,
targetHardware: "NVIDIA RTX 4080 Super",
vramGb: 16,
datasetSize: 1500,
targetTask: "High precision instruction following & MCP Tool Calling",
selectedTechniques: [selectedTechniqueId],
}),
});
const data = await res.json();
if (data.success && data.config) {
setAiAdvisorResult(data.config);
}
} catch (e) {
console.error(e);
} finally {
setAiOptimizing(false);
}
};
const applyAIRecommendation = () => {
if (!aiAdvisorResult) return;
setHyperparameters((prev) => ({
...prev,
lora_r: aiAdvisorResult.recommendedLoRA_r || prev.lora_r,
lora_alpha: aiAdvisorResult.recommendedLoRA_alpha || prev.lora_alpha,
batch_size: aiAdvisorResult.batchSize || prev.batch_size,
gradient_accumulation_steps: aiAdvisorResult.gradAccumSteps || prev.gradient_accumulation_steps,
learning_rate: parseFloat(aiAdvisorResult.learningRate) || prev.learning_rate,
}));
};
return (
<div className="space-y-6">
{/* AI Training Optimization Advisor Banner */}
<div className="bg-[#18181b]/70 border border-blue-500/25 rounded-xl p-5 relative overflow-hidden">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[11px] font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-2">
<Sparkles className="w-3.5 h-3.5 text-blue-400" /> GEMINI SOTA ADVISOR
</div>
<h3 className="text-base font-semibold text-[#f4f4f5]">
AI Hyperparameter Auto-Tuner for RTX 4080 Super
</h3>
<p className="text-xs text-zinc-400 mt-1">
Automatically calculate optimal LoRA rank, alpha, micro-batching, and learning rate for {selectedModel.name} on 16GB VRAM.
</p>
</div>
<div className="flex items-center gap-2">
<button
id="btn-consult-ai-advisor"
onClick={handleConsultAIAdvisor}
disabled={aiOptimizing}
className="flex items-center gap-2 px-3.5 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer disabled:opacity-50"
>
{aiOptimizing ? (
<>
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
<span>Computing Optimal Strategy...</span>
</>
) : (
<>
<Sparkles className="w-3.5 h-3.5" />
<span>Auto-Tune Strategy</span>
</>
)}
</button>
{aiAdvisorResult && (
<button
onClick={applyAIRecommendation}
className="flex items-center gap-1.5 px-3 py-2 rounded text-xs font-medium bg-emerald-950/40 hover:bg-emerald-900/40 text-emerald-400 border border-emerald-500/30 cursor-pointer"
>
<Check className="w-3.5 h-3.5" /> Apply Recs
</button>
)}
</div>
</div>
{/* AI Recommendations Output */}
{aiAdvisorResult && (
<div className="mt-4 pt-4 border-t border-[#27272a] grid grid-cols-1 md:grid-cols-4 gap-3 text-xs">
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] uppercase font-mono text-zinc-500">Recommended LoRA</div>
<div className="font-mono font-medium text-zinc-200 text-xs mt-0.5">
r={aiAdvisorResult.recommendedLoRA_r}, α={aiAdvisorResult.recommendedLoRA_alpha}
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] uppercase font-mono text-zinc-500">Micro-Batch / GradAccum</div>
<div className="font-mono font-medium text-zinc-200 text-xs mt-0.5">
{aiAdvisorResult.batchSize} / {aiAdvisorResult.gradAccumSteps} steps
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] uppercase font-mono text-zinc-500">Estimated Train VRAM</div>
<div className="font-mono font-medium text-emerald-400 text-xs mt-0.5">
{aiAdvisorResult.trainingVramEstimateGb} GB (Fits 16GB)
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] uppercase font-mono text-zinc-500">Fit Confidence</div>
<div className="font-mono font-medium text-blue-400 text-xs mt-0.5">
{aiAdvisorResult.fitProbabilityPercent}% Perfect Fit
</div>
</div>
</div>
)}
</div>
{/* Main Grid: Techniques Sidebar + Detailed Configuration */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: Technique Selector */}
<div className="space-y-3">
<div className="text-[10px] font-mono font-bold text-zinc-500 uppercase px-1 flex items-center gap-1.5">
<Zap className="w-3.5 h-3.5 text-blue-400" /> SOTA TECHNIQUES ({SOTA_TECHNIQUES.length})
</div>
<div className="space-y-1.5 max-h-[600px] overflow-y-auto pr-1">
{SOTA_TECHNIQUES.map((tech) => {
const isSelected = selectedTechniqueId === tech.id;
return (
<div
key={tech.id}
onClick={() => setSelectedTechniqueId(tech.id)}
className={`p-3 rounded-lg border transition-all cursor-pointer ${
isSelected
? "bg-[#18181b] border-blue-500/40 text-blue-400 ring-1 ring-blue-500/20"
: "bg-[#121214] border-[#27272a] hover:border-zinc-700 hover:bg-[#18181b]/40 text-zinc-300"
}`}
>
<div className="flex items-center justify-between">
<span className="font-medium text-xs text-[#f4f4f5]">{tech.name}</span>
<span className="text-[10px] font-mono text-blue-400 bg-blue-400/10 px-1.5 py-0.5 rounded border border-blue-400/20">
{tech.category}
</span>
</div>
<p className="text-[11px] text-zinc-400 mt-1 line-clamp-2">{tech.tagline}</p>
</div>
);
})}
</div>
</div>
{/* Right: Technique Deep Dive & Hyperparameter Controls */}
<div className="lg:col-span-2 space-y-6">
{/* Active Technique Overview Card */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5">
<div className="flex flex-wrap items-center justify-between gap-2 mb-3">
<div>
<span className="text-[10px] font-mono font-bold text-blue-400 uppercase tracking-wider bg-blue-400/10 px-2 py-0.5 rounded border border-blue-400/20">
{activeTechnique.category.toUpperCase()}
</span>
<h2 className="text-lg font-semibold text-[#f4f4f5] mt-2">
{activeTechnique.name}
</h2>
</div>
<div className="flex items-center gap-2">
<span className="text-[11px] font-mono px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
{activeTechnique.memorySavings}
</span>
<span className="text-[11px] font-mono px-2 py-0.5 rounded bg-blue-500/10 text-blue-400 border border-blue-500/20">
{activeTechnique.speedMultiplier}
</span>
</div>
</div>
<p className="text-xs text-zinc-400 leading-relaxed mb-4">
{activeTechnique.description}
</p>
<div className="text-[11px] text-zinc-400 font-mono bg-black/30 p-2.5 rounded border border-zinc-800 mb-4">
Paper: {activeTechnique.paperReference}
</div>
{/* Code preview snippet */}
<div>
<div className="text-xs font-medium text-zinc-400 mb-1.5 flex items-center gap-1.5">
<Code className="w-3.5 h-3.5 text-blue-400" /> Generated PyTorch / Unsloth Implementation
</div>
<pre className="bg-zinc-950 p-3.5 rounded text-xs font-mono text-zinc-300 overflow-x-auto border border-zinc-800 leading-relaxed">
{activeTechnique.codeSnippet}
</pre>
</div>
</div>
{/* Core Hyperparameter Tuner Form */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<Sliders className="w-4 h-4 text-blue-400" /> Fine-Tuning Hyperparameters
</h3>
<span className="text-[10px] font-mono text-zinc-500 uppercase">Preset: RTX 4080 Super (16GB)</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
{/* LoRA Rank */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<div className="flex justify-between font-mono text-xs">
<span className="text-zinc-400">LoRA Rank (r)</span>
<span className="font-bold text-blue-400">{hyperparameters.lora_r}</span>
</div>
<input
type="range"
min="8"
max="128"
step="8"
value={hyperparameters.lora_r}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
lora_r: parseInt(e.target.value),
lora_alpha: parseInt(e.target.value),
}))
}
className="w-full accent-blue-600 cursor-pointer h-1 bg-zinc-800 rounded"
/>
<p className="text-[10px] text-zinc-500">Standard ranks: 16 or 32 for general tasks, 64 for complex coding.</p>
</div>
{/* LoRA Alpha */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<div className="flex justify-between font-mono text-xs">
<span className="text-zinc-400">LoRA Alpha (α)</span>
<span className="font-bold text-blue-400">{hyperparameters.lora_alpha}</span>
</div>
<input
type="range"
min="8"
max="256"
step="8"
value={hyperparameters.lora_alpha}
onChange={(e) =>
setHyperparameters((prev) => ({ ...prev, lora_alpha: parseInt(e.target.value) }))
}
className="w-full accent-blue-600 cursor-pointer h-1 bg-zinc-800 rounded"
/>
<p className="text-[10px] text-zinc-500">Scaling constant. Alpha = 16 or 32 provides optimal gradient flow.</p>
</div>
{/* Micro Batch Size */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Micro Batch Size (Per Device)</label>
<select
value={hyperparameters.batch_size}
onChange={(e) =>
setHyperparameters((prev) => ({ ...prev, batch_size: parseInt(e.target.value) }))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 font-mono outline-none focus:ring-1 focus:ring-blue-600"
>
<option value={1}>1 (Recommended for 14B models & 16k context)</option>
<option value={2}>2 (Recommended for 8B models on RTX 4080)</option>
<option value={4}>4 (High speed for 8k context)</option>
</select>
</div>
{/* Gradient Accumulation */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Gradient Accumulation Steps</label>
<select
value={hyperparameters.gradient_accumulation_steps}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
gradient_accumulation_steps: parseInt(e.target.value),
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 font-mono outline-none focus:ring-1 focus:ring-blue-600"
>
<option value={2}>2 (Effective batch: 4)</option>
<option value={4}>4 (Effective batch: 8)</option>
<option value={8}>8 (Effective batch: 16 - High stability)</option>
<option value={16}>16 (Effective batch: 32)</option>
</select>
</div>
{/* Learning Rate */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Learning Rate</label>
<input
type="number"
step="0.00001"
value={hyperparameters.learning_rate}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
learning_rate: parseFloat(e.target.value) || 0.0002,
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 font-mono outline-none focus:ring-1 focus:ring-blue-600"
/>
</div>
{/* Max Sequence Length */}
<div className="space-y-1.5 bg-black/30 p-3 rounded border border-zinc-800">
<label className="text-[10px] font-mono text-zinc-500 uppercase block">Max Sequence Length (Context)</label>
<select
value={hyperparameters.max_seq_length}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
max_seq_length: parseInt(e.target.value),
}))
}
className="w-full bg-zinc-950 border border-zinc-800 rounded px-2.5 py-1.5 text-xs text-zinc-200 font-mono outline-none focus:ring-1 focus:ring-blue-600"
>
<option value={2048}>2,048 tokens (Ultra Fast)</option>
<option value={4096}>4,096 tokens (Standard)</option>
<option value={8192}>8,192 tokens (Extended Instructions)</option>
<option value={16384}>16,384 tokens (Full Code & Multi-turn MCP)</option>
<option value={32768}>32,768 tokens (Long Document / YaRN)</option>
</select>
</div>
</div>
{/* Toggle Flags */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 pt-2">
<label className="flex items-center gap-2 p-3 bg-black/30 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={hyperparameters.use_dora}
onChange={(e) =>
setHyperparameters((prev) => ({ ...prev, use_dora: e.target.checked }))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="font-medium text-[#f4f4f5] text-xs">Enable DoRA</div>
<div className="text-[10px] text-zinc-500">Magnitude / Direction split</div>
</div>
</label>
<label className="flex items-center gap-2 p-3 bg-black/30 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={hyperparameters.use_unsloth_fast_backprop}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
use_unsloth_fast_backprop: e.target.checked,
}))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="font-medium text-[#f4f4f5] text-xs">Unsloth Fast Backprop</div>
<div className="text-[10px] text-zinc-500">Triton kernel acceleration</div>
</div>
</label>
<label className="flex items-center gap-2 p-3 bg-black/30 rounded border border-zinc-800 cursor-pointer">
<input
type="checkbox"
checked={hyperparameters.use_gradient_checkpointing}
onChange={(e) =>
setHyperparameters((prev) => ({
...prev,
use_gradient_checkpointing: e.target.checked,
}))
}
className="accent-blue-600 rounded"
/>
<div>
<div className="font-medium text-[#f4f4f5] text-xs">Gradient Checkpointing</div>
<div className="text-[10px] text-zinc-500">Zero OOM for &gt;8k context</div>
</div>
</label>
</div>
</div>
{/* Action to proceed */}
<div className="flex justify-end">
<button
onClick={onProceed}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white shadow-sm transition-all cursor-pointer"
>
<span>Proceed to Dataset & Synthetic Data</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,327 @@
import React, { useEffect, useState, useRef } from "react";
import {
PlayCircle,
PauseCircle,
RotateCcw,
Zap,
ArrowRight,
TrendingDown,
Terminal,
} from "lucide-react";
import {
ResponsiveContainer,
LineChart,
Line,
XAxis,
YAxis,
Tooltip,
CartesianGrid,
} from "recharts";
import { BaseModelInfo, TrainingHyperparameters, TrainingLogEntry } from "../types";
interface TrainingSimulatorProps {
selectedModel: BaseModelInfo;
hyperparameters: TrainingHyperparameters;
onProceed: () => void;
}
export const TrainingSimulator: React.FC<TrainingSimulatorProps> = ({
selectedModel,
hyperparameters,
onProceed,
}) => {
const [isRunning, setIsRunning] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const totalSteps = 60;
const [logs, setLogs] = useState<TrainingLogEntry[]>([]);
const [sampleGenerations, setSampleGenerations] = useState<string[]>([]);
const timerRef = useRef<any>(null);
// Generate initial point
useEffect(() => {
if (logs.length === 0) {
setLogs([
{
step: 0,
epoch: 0,
loss: 2.85,
learningRate: hyperparameters.learning_rate * 0.1,
gradNorm: 1.42,
vramUsedGb: 11.2,
tokensPerSec: 2450,
timestamp: new Date().toLocaleTimeString(),
},
]);
}
}, []);
useEffect(() => {
if (isRunning) {
timerRef.current = setInterval(() => {
setCurrentStep((prev) => {
if (prev >= totalSteps) {
setIsRunning(false);
clearInterval(timerRef.current);
return prev;
}
const nextStep = prev + 1;
// Realistic loss decay with stochastic noise
const progress = nextStep / totalSteps;
const baseLoss = 2.85 * Math.exp(-progress * 2.8) + 0.35;
const noise = (Math.random() - 0.5) * 0.08;
const currentLoss = Number(Math.max(0.25, baseLoss + noise).toFixed(4));
// Cosine learning rate
const lr = Number(
(
hyperparameters.learning_rate *
0.5 *
(1 + Math.cos((Math.PI * nextStep) / totalSteps))
).toExponential(2)
);
const gradNorm = Number((0.85 + Math.random() * 0.4).toFixed(3));
const vramUsedGb = Number((11.4 + Math.sin(nextStep * 0.3) * 0.4).toFixed(1));
const tokensPerSec = Math.round(2600 + (Math.random() - 0.5) * 200);
const newLog: TrainingLogEntry = {
step: nextStep,
epoch: Number(((nextStep / totalSteps) * hyperparameters.epochs).toFixed(2)),
loss: currentLoss,
learningRate: lr,
gradNorm,
vramUsedGb,
tokensPerSec,
timestamp: new Date().toLocaleTimeString(),
};
setLogs((prevLogs) => [...prevLogs, newLog]);
// Sample token generation preview at milestones
if (nextStep === 15) {
setSampleGenerations((g) => [
`[Step 15 Checkpoint] Prompt: "Call the filesystem read_file tool"\nModel Output: {"name": "read_file", "path": "src/App.tsx"} (Loss: ${currentLoss})`,
...g,
]);
} else if (nextStep === 35) {
setSampleGenerations((g) => [
`[Step 35 Checkpoint] Prompt: "Query database for top 5 active users"\nModel Output: <tool_call>{"name": "execute_sql", "arguments": {"query": "SELECT * FROM users ORDER BY created_at DESC LIMIT 5;"}}</tool_call>\nFound 5 users.`,
...g,
]);
} else if (nextStep === 60) {
setSampleGenerations((g) => [
`[Step 60 Final] High precision MCP multi-turn tool calling & Deep Reasoning aligned perfectly! (Final Loss: ${currentLoss})`,
...g,
]);
}
return nextStep;
});
}, 400);
} else {
if (timerRef.current) clearInterval(timerRef.current);
}
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, [isRunning, hyperparameters]);
const handleReset = () => {
setIsRunning(false);
setCurrentStep(0);
setLogs([
{
step: 0,
epoch: 0,
loss: 2.85,
learningRate: hyperparameters.learning_rate * 0.1,
gradNorm: 1.42,
vramUsedGb: 11.2,
tokensPerSec: 2450,
timestamp: new Date().toLocaleTimeString(),
},
]);
setSampleGenerations([]);
};
const latestLog = logs[logs.length - 1] || logs[0];
const progressPercent = Math.round((currentStep / totalSteps) * 100);
return (
<div className="space-y-6">
{/* Top Banner & Control HUD */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 flex flex-wrap items-center justify-between gap-4">
<div>
<div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded text-[10px] font-mono text-blue-400 bg-blue-400/10 border border-blue-400/20 mb-2">
<Zap className="w-3.5 h-3.5" /> UNSLOTH CUDA TRAINING ENGINE
</div>
<h2 className="text-lg font-semibold text-[#f4f4f5]">
Live Fine-Tuning Execution & Telemetry Monitor
</h2>
<p className="text-xs text-zinc-400 mt-1">
Running Triton backprop kernel on NVIDIA RTX 4080 Super with FlashAttention-2.
</p>
</div>
{/* Action Controls */}
<div className="flex items-center gap-2">
{!isRunning ? (
<button
onClick={() => setIsRunning(true)}
disabled={currentStep >= totalSteps}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-emerald-600 hover:bg-emerald-500 text-white shadow-sm cursor-pointer disabled:opacity-50"
>
<PlayCircle className="w-4 h-4" />
<span>{currentStep === 0 ? "Start Training Run" : "Resume Training"}</span>
</button>
) : (
<button
onClick={() => setIsRunning(false)}
className="flex items-center gap-2 px-4 py-2 rounded text-xs font-medium bg-amber-600 hover:bg-amber-500 text-white shadow-sm cursor-pointer"
>
<PauseCircle className="w-4 h-4" />
<span>Pause Training</span>
</button>
)}
<button
onClick={handleReset}
className="p-2 rounded bg-zinc-900 hover:bg-zinc-800 text-zinc-400 border border-zinc-800 cursor-pointer"
title="Reset training simulation"
>
<RotateCcw className="w-4 h-4" />
</button>
</div>
</div>
{/* Progress Bar & Telemetry Strip */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3">
<div className="flex justify-between items-center text-xs font-mono">
<span className="text-zinc-300">
Training Progress: Step {currentStep} of {totalSteps} ({progressPercent}%)
</span>
<span className="text-blue-400">
Epoch {((currentStep / totalSteps) * hyperparameters.epochs).toFixed(2)} / {hyperparameters.epochs}
</span>
</div>
<div className="w-full h-2 bg-black/40 rounded-full overflow-hidden border border-zinc-800">
<div
className="h-full bg-blue-600 rounded-full transition-all duration-300"
style={{ width: `${progressPercent}%` }}
/>
</div>
{/* Real-time metrics grid */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 pt-2 text-xs">
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Current Loss</div>
<div className="text-sm font-mono font-bold text-blue-400 mt-0.5">
{latestLog?.loss ?? "--"}
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Learning Rate</div>
<div className="text-sm font-mono font-bold text-zinc-200 mt-0.5">
{latestLog?.learningRate ?? "--"}
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Grad Norm</div>
<div className="text-sm font-mono font-bold text-zinc-200 mt-0.5">
{latestLog?.gradNorm ?? "--"}
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">RTX 4080 VRAM</div>
<div className="text-sm font-mono font-bold text-emerald-400 mt-0.5">
{latestLog?.vramUsedGb} GB <span className="text-[10px] text-zinc-500 font-normal">/ 16GB</span>
</div>
</div>
<div className="bg-black/30 p-3 rounded border border-zinc-800">
<div className="text-[10px] font-mono text-zinc-500 uppercase">Throughput</div>
<div className="text-sm font-mono font-bold text-cyan-400 mt-0.5">
{latestLog?.tokensPerSec} tok/s
</div>
</div>
</div>
</div>
{/* Loss Convergence Chart & Generation Checkpoint Logs */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: Recharts Live Loss Curve */}
<div className="lg:col-span-2 bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2">
<TrendingDown className="w-4 h-4 text-blue-400" /> Training Loss Convergence Curve
</h3>
<span className="text-[10px] text-zinc-500 font-mono">Cross Entropy Loss (SFT)</span>
</div>
<div className="h-64 w-full bg-zinc-950 rounded p-2 border border-zinc-800">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={logs}>
<CartesianGrid strokeDasharray="3 3" stroke="#27272a" />
<XAxis dataKey="step" stroke="#71717a" fontSize={11} />
<YAxis domain={["auto", "auto"]} stroke="#71717a" fontSize={11} />
<Tooltip
contentStyle={{
backgroundColor: "#18181b",
borderColor: "#27272a",
fontSize: "11px",
borderRadius: "6px",
color: "#f4f4f5",
}}
/>
<Line
type="monotone"
dataKey="loss"
stroke="#3b82f6"
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
{/* Right: Intermediate Checkpoint Samples */}
<div className="bg-[#18181b]/50 border border-[#27272a] rounded-xl p-5 space-y-3 flex flex-col justify-between">
<div>
<h3 className="text-sm font-semibold text-[#f4f4f5] flex items-center gap-2 mb-3">
<Terminal className="w-4 h-4 text-emerald-400" /> Checkpoint Generations
</h3>
<div className="space-y-2.5 max-h-60 overflow-y-auto">
{sampleGenerations.length === 0 ? (
<div className="text-xs text-zinc-500 italic p-3 bg-zinc-950 rounded border border-zinc-800">
Model checkpoint test outputs will appear here at steps 15, 35, and 60...
</div>
) : (
sampleGenerations.map((gen, idx) => (
<div
key={idx}
className="p-3 bg-zinc-950 rounded border border-zinc-800 text-[11px] font-mono text-emerald-400 whitespace-pre-wrap leading-relaxed"
>
{gen}
</div>
))
)}
</div>
</div>
<div className="pt-3 border-t border-[#27272a]">
<button
onClick={onProceed}
className="w-full flex items-center justify-center gap-2 py-2 rounded font-medium text-xs bg-blue-600 hover:bg-blue-500 text-white shadow-sm cursor-pointer"
>
<span>Export Modelfile & Push to Ollama</span>
<ArrowRight className="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,83 @@
import { MCPToolDeclaration } from "../types";
export const DEFAULT_MCP_PRESETS: MCPToolDeclaration[] = [
{
id: "filesystem-mcp",
name: "read_file",
serverName: "Filesystem MCP",
description: "Read the full contents of a file from the user's workspace securely.",
parametersSchema: {
type: "object",
properties: {
path: { type: "string", description: "The relative or absolute file path to read" },
start_line: { type: "number", description: "Optional starting line index (1-based)" },
end_line: { type: "number", description: "Optional ending line index" },
},
required: ["path"],
},
sampleCallsCount: 45,
},
{
id: "filesystem-write",
name: "write_file",
serverName: "Filesystem MCP",
description: "Create or overwrite a file with given text content.",
parametersSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path where the file should be created" },
content: { type: "string", description: "The complete content to write" },
},
required: ["path", "content"],
},
sampleCallsCount: 38,
},
{
id: "postgres-query",
name: "execute_sql",
serverName: "PostgreSQL MCP",
description: "Execute a read-only or transactional SQL query against the connected database.",
parametersSchema: {
type: "object",
properties: {
query: { type: "string", description: "Valid PostgreSQL query statement" },
limit: { type: "number", description: "Maximum rows to return" },
},
required: ["query"],
},
sampleCallsCount: 52,
},
{
id: "websearch-mcp",
name: "web_search",
serverName: "Web Search MCP",
description: "Perform real-time search across the web and return top synthesized results.",
parametersSchema: {
type: "object",
properties: {
query: { type: "string", description: "The search query string" },
num_results: { type: "number", description: "Number of search results to fetch (1-10)" },
},
required: ["query"],
},
sampleCallsCount: 60,
},
{
id: "github-mcp",
name: "create_pull_request",
serverName: "GitHub MCP",
description: "Create a new pull request on a GitHub repository with title and branch details.",
parametersSchema: {
type: "object",
properties: {
repo: { type: "string", description: "owner/repository_name format" },
title: { type: "string", description: "Pull request title" },
head_branch: { type: "string", description: "The source feature branch" },
base_branch: { type: "string", description: "The target branch (e.g. main)" },
body: { type: "string", description: "PR description in markdown" },
},
required: ["repo", "title", "head_branch", "base_branch"],
},
sampleCallsCount: 29,
},
];

View File

@@ -0,0 +1,202 @@
import { BaseModelInfo } from "../types";
export const BASE_MODELS: BaseModelInfo[] = [
{
id: "llama-3.1-8b",
name: "Llama 3.1 8B Instruct",
huggingFaceId: "unsloth/Meta-Llama-3.1-8B-Instruct",
ollamaName: "llama3.1:8b",
parametersBillion: 8.03,
layers: 32,
hiddenDim: 4096,
heads: 32,
kvHeads: 8,
vocabSize: 128256,
defaultContext: 131072,
architecture: "llama3",
baseSizeGb: 16.1,
q4SizeGb: 4.9,
description: "The gold standard open model for fine-tuning. Fits perfectly in RTX 4080 Super (16GB VRAM) for 16k+ context QLoRA training.",
recommendedFor4080Super: true,
},
{
id: "qwen-2.5-7b",
name: "Qwen 2.5 7B Instruct",
huggingFaceId: "unsloth/Qwen2.5-7B-Instruct",
ollamaName: "qwen2.5:7b",
parametersBillion: 7.61,
layers: 28,
hiddenDim: 3584,
heads: 28,
kvHeads: 4,
vocabSize: 152064,
defaultContext: 32768,
architecture: "qwen2.5",
baseSizeGb: 15.2,
q4SizeGb: 4.7,
description: "Supreme multilingual, mathematical, coding & structured JSON/MCP tool-use capabilities. Extremely fast on Unsloth.",
recommendedFor4080Super: true,
},
{
id: "qwen-2.5-coder-7b",
name: "Qwen 2.5 Coder 7B",
huggingFaceId: "unsloth/Qwen2.5-Coder-7B-Instruct",
ollamaName: "qwen2.5-coder:7b",
parametersBillion: 7.61,
layers: 28,
hiddenDim: 3584,
heads: 28,
kvHeads: 4,
vocabSize: 152064,
defaultContext: 32768,
architecture: "qwen2.5",
baseSizeGb: 15.2,
q4SizeGb: 4.7,
description: "Best-in-class coding foundation. Ideal for fine-tuning MCP plugins, repo agents, and code harnesses.",
recommendedFor4080Super: true,
},
{
id: "qwen-2.5-14b",
name: "Qwen 2.5 14B Instruct",
huggingFaceId: "unsloth/Qwen2.5-14B-Instruct",
ollamaName: "qwen2.5:14b",
parametersBillion: 14.7,
layers: 48,
hiddenDim: 5120,
heads: 40,
kvHeads: 8,
vocabSize: 152064,
defaultContext: 32768,
architecture: "qwen2.5",
baseSizeGb: 29.4,
q4SizeGb: 9.0,
description: "High-intelligence intermediate model. Fits RTX 4080 Super with 4-bit QLoRA and Unsloth gradient checkpointing (10.5GB VRAM used).",
recommendedFor4080Super: true,
},
{
id: "deepseek-r1-distill-qwen-8b",
name: "DeepSeek R1 Distill Qwen 8B",
huggingFaceId: "unsloth/DeepSeek-R1-Distill-Qwen-8B",
ollamaName: "deepseek-r1:8b",
parametersBillion: 8.0,
layers: 32,
hiddenDim: 4096,
heads: 32,
kvHeads: 8,
vocabSize: 152064,
defaultContext: 32768,
architecture: "deepseek",
baseSizeGb: 16.0,
q4SizeGb: 4.9,
description: "Reasoning powerhouse with <think> token chain-of-thought capabilities. Perfect for complex problem solving and MCP logic.",
recommendedFor4080Super: true,
},
{
id: "deepseek-r1-distill-llama-8b",
name: "DeepSeek R1 Distill Llama 8B",
huggingFaceId: "unsloth/DeepSeek-R1-Distill-Llama-8B",
ollamaName: "deepseek-r1:8b-llama",
parametersBillion: 8.03,
layers: 32,
hiddenDim: 4096,
heads: 32,
kvHeads: 8,
vocabSize: 128256,
defaultContext: 131072,
architecture: "deepseek",
baseSizeGb: 16.1,
q4SizeGb: 4.9,
description: "DeepSeek reasoning logic distilled into Llama 3.1 architecture with massive 128k context support.",
recommendedFor4080Super: true,
},
{
id: "mistral-nemo-12b",
name: "Mistral NeMo 12B Instruct",
huggingFaceId: "unsloth/Mistral-Nemo-Instruct-2407",
ollamaName: "mistral-nemo:12b",
parametersBillion: 12.2,
layers: 40,
hiddenDim: 5120,
heads: 32,
kvHeads: 8,
vocabSize: 131072,
defaultContext: 128000,
architecture: "mistral",
baseSizeGb: 24.5,
q4SizeGb: 7.5,
description: "Collaborative model by Mistral and NVIDIA with Tekken tokenizer and huge 128k context.",
recommendedFor4080Super: true,
},
{
id: "gemma-2-9b",
name: "Gemma 2 9B Instruct",
huggingFaceId: "unsloth/gemma-2-9b-it",
ollamaName: "gemma2:9b",
parametersBillion: 9.24,
layers: 42,
hiddenDim: 3584,
heads: 16,
kvHeads: 8,
vocabSize: 256000,
defaultContext: 8192,
architecture: "gemma2",
baseSizeGb: 18.5,
q4SizeGb: 5.6,
description: "Google's high parameter-efficiency model with sliding window attention and logit capping.",
recommendedFor4080Super: true,
},
{
id: "phi-4-14b",
name: "Phi-4 14B Instruct",
huggingFaceId: "unsloth/phi-4",
ollamaName: "phi4:14b",
parametersBillion: 14.7,
layers: 40,
hiddenDim: 5120,
heads: 40,
kvHeads: 10,
vocabSize: 100352,
defaultContext: 16384,
architecture: "phi4",
baseSizeGb: 29.4,
q4SizeGb: 9.1,
description: "Microsoft's state-of-the-art synthetic data trained 14B model with exceptional reasoning.",
recommendedFor4080Super: true,
},
{
id: "smollm2-1.7b",
name: "SmolLM2 1.7B Instruct",
huggingFaceId: "unsloth/SmolLM2-1.7B-Instruct",
ollamaName: "smollm2:1.7b",
parametersBillion: 1.71,
layers: 24,
hiddenDim: 2048,
heads: 32,
kvHeads: 32,
vocabSize: 49152,
defaultContext: 8192,
architecture: "smollm",
baseSizeGb: 3.4,
q4SizeGb: 1.1,
description: "Ultra-compact fast model. Trains in minutes on 4080 Super with full 32k context, ideal for edge devices and fast tool calling.",
recommendedFor4080Super: true,
},
{
id: "llama-3.3-70b",
name: "Llama 3.3 70B Instruct",
huggingFaceId: "unsloth/Llama-3.3-70B-Instruct",
ollamaName: "llama3.3:70b",
parametersBillion: 70.6,
layers: 80,
hiddenDim: 8192,
heads: 64,
kvHeads: 8,
vocabSize: 128256,
defaultContext: 131072,
architecture: "llama3",
baseSizeGb: 141.0,
q4SizeGb: 42.5,
description: "Flagship intelligence matching GPT-4o. Requires Multi-GPU or Teacher distillation mode for 16GB RTX 4080 Super.",
recommendedFor4080Super: false,
},
];

View File

@@ -0,0 +1,238 @@
export interface TechniqueDetail {
id: string;
name: string;
category: "finetune" | "prune" | "moe" | "quant" | "alignment";
tagline: string;
description: string;
paperReference: string;
memorySavings: string;
speedMultiplier: string;
recommendedFor4080: boolean;
unslothSupported: boolean;
codeSnippet: string;
parameters: {
name: string;
label: string;
type: "number" | "select" | "boolean" | "text";
default: any;
options?: string[];
description: string;
}[];
}
export const SOTA_TECHNIQUES: TechniqueDetail[] = [
{
id: "qlora",
name: "Unsloth Fast QLoRA (4-bit NF4)",
category: "finetune",
tagline: "Ultra-fast parameter efficient fine-tuning with 4-bit NormalFloat quantization",
description: "Quantizes base weights to 4-bit NormalFloat (NF4) with double quantization and trains 16-bit LoRA adapter matrices via custom Triton kernels. Reduces VRAM by up to 80% while retaining full 16-bit accuracy.",
paperReference: "Dettmers et al., 2023 (QLoRA) & Unsloth AI",
memorySavings: "80% VRAM reduction",
speedMultiplier: "2.2x - 5.0x faster",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_alpha=16,
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth",
random_state=3407,
)`,
parameters: [
{ name: "lora_r", label: "LoRA Rank (r)", type: "number", default: 16, description: "Dimension of low-rank update matrices (8, 16, 32, 64)" },
{ name: "lora_alpha", label: "LoRA Alpha", type: "number", default: 16, description: "Scaling factor (commonly 1x or 2x of rank r)" },
{ name: "use_gradient_checkpointing", label: "Unsloth Fast Gradient Checkpointing", type: "boolean", default: true, description: "Offloads activations to save 40% memory with zero speed penalty" },
],
},
{
id: "dora",
name: "DoRA (Weight-Decomposed Low-Rank Adaptation)",
category: "finetune",
tagline: "Decomposes weights into magnitude and direction for full-fine-tuning parity",
description: "Decomposes pre-trained weights into magnitude vectors and directional matrices. LoRA is applied exclusively to the directional component, matching or exceeding full fine-tuning performance without extra inference cost.",
paperReference: "Liu et al., 2024 (DoRA: Weight-Decomposed Low-Rank Adaptation)",
memorySavings: "75% VRAM reduction",
speedMultiplier: "1.8x faster",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `model = FastLanguageModel.get_peft_model(
model,
r=16,
use_dora=True,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
)`,
parameters: [
{ name: "use_dora", label: "Enable DoRA Decomposition", type: "boolean", default: true, description: "Enable magnitude/directional weight split" },
{ name: "lora_r", label: "Directional Rank", type: "number", default: 16, description: "Rank for the directional matrix" },
],
},
{
id: "orpo",
name: "ORPO (Odds Ratio Preference Optimization)",
category: "alignment",
tagline: "Single-step preference alignment & SFT without a reference model",
description: "Integrates odds-ratio penalty directly into the supervised cross-entropy loss function. Eliminates the need for a separate reference model or two-stage SFT+DPO pipeline, halving VRAM requirements.",
paperReference: "Hong et al., 2024 (ORPO: Monolithic Preference Optimization)",
memorySavings: "50% VRAM saving vs DPO",
speedMultiplier: "2.0x faster than SFT+DPO",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `from trl import ORPOTrainer, ORPOConfig
orpo_trainer = ORPOTrainer(
model=model,
args=ORPOConfig(
beta=0.1,
learning_rate=5e-6,
lr_scheduler_type="cosine",
max_prompt_length=1024,
max_length=2048,
),
train_dataset=dataset,
)`,
parameters: [
{ name: "preference_beta", label: "Odds Ratio Beta (β)", type: "number", default: 0.1, description: "Weight of the preference penalty in ORPO loss (0.05 - 0.2)" },
],
},
{
id: "galore",
name: "GaLore (Gradient Low-Rank Projection)",
category: "finetune",
tagline: "Memory-efficient full-parameter training via gradient subspace projection",
description: "Allows full parameter training of 7B-14B models on 16GB VRAM by projecting optimizer state gradients into low-rank subspaces, slashing optimizer memory by up to 65.5%.",
paperReference: "Zhao et al., 2024 (GaLore: Gradient Low-Rank Projection)",
memorySavings: "65% optimizer memory reduction",
speedMultiplier: "1.2x",
recommendedFor4080: true,
unslothSupported: false,
codeSnippet: `from galore_torch import GaLoreAdamW8bit
optimizer = GaLoreAdamW8bit(
model.parameters(),
lr=1e-5,
rank=128,
update_proj_gap=200,
scale=0.25,
)`,
parameters: [
{ name: "galore_rank", label: "Gradient Subspace Rank", type: "number", default: 128, description: "Projection rank for gradients" },
{ name: "update_proj_gap", label: "Projection Update Frequency", type: "number", default: 200, description: "Steps between SVD subspace updates" },
],
},
{
id: "neftune",
name: "NEFTune (Noisy Embedding Fine-Tuning)",
category: "finetune",
tagline: "Injects uniform noise into embeddings to boost generalizability and prevent overfitting",
description: "Adds scaled uniform random noise to input token embeddings during training. Proven to boost AlpacaEval and conversational benchmark scores by 5-15% with zero extra VRAM.",
paperReference: "Jain et al., 2023 (NEFTune: Noisy Embeddings Improve Instruction Finetuning)",
memorySavings: "0% (Zero overhead)",
speedMultiplier: "1.0x",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `trainer = SFTTrainer(
model=model,
train_dataset=dataset,
neftune_noise_alpha=5,
)`,
parameters: [
{ name: "neftune_noise_alpha", label: "Noise Alpha Scale", type: "number", default: 5, description: "Magnitude of uniform noise added to embeddings (typically 5 to 15)" },
],
},
{
id: "structured_layer",
name: "ShortGPT Structured Layer Pruning (Fat Shaving)",
category: "prune",
tagline: "Removes redundant hidden layers based on angular similarity metric",
description: "Calculates the cosine angular distance of representations between consecutive transformer layers. Redundant layers (often layers 14-22 in a 32-layer model) are trimmed, reducing model parameters by 25-35% with minimal accuracy loss.",
paperReference: "Men et al., 2024 (ShortGPT: Layers in Large Language Models are More Redundant Than You Think)",
memorySavings: "25-35% permanent size reduction",
speedMultiplier: "1.35x faster inference",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `# Pruning redundant middle layers from 32 layers down to 24 layers
pruned_layers = [i for i in range(32) if i not in range(16, 24)]
model.model.layers = torch.nn.ModuleList([model.model.layers[i] for i in pruned_layers])
model.config.num_hidden_layers = len(pruned_layers)
# Followed by 100-step healing LoRA adapter`,
parameters: [
{ name: "layers_to_prune", label: "Pruning Range (Start - End)", type: "text", default: "16-23", description: "Indices of transformer layers to excise" },
{ name: "repair_steps", label: "Healing LoRA Steps", type: "number", default: 100, description: "Short LoRA fine-tune steps to restore perplexity" },
],
},
{
id: "vocab_trim",
name: "Vocabulary Trimmer (Shave 1GB of Embedding Fat)",
category: "prune",
tagline: "Trims unused multilingual and rare tokens from 128k tokenizer down to 32k",
description: "Modern tokenizers (Llama 3.1 & Qwen 2.5) allocate 128k-152k tokens, consuming over 1.2 GB VRAM in the embedding table alone. Trimming down to target domain tokens shrinks the GGUF file substantially.",
paperReference: "TokenCraft / CompactLLM 2024",
memorySavings: "800MB - 1.4GB disk & VRAM savings",
speedMultiplier: "1.15x faster generation",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `# Shrink embedding matrix and lm_head
kept_token_ids = get_frequent_tokens(dataset, target_size=32000)
model.resize_token_embeddings(len(kept_token_ids))`,
parameters: [
{ name: "target_vocab", label: "Target Vocabulary Size", type: "number", default: 32000, description: "Size to condense the 128k/152k vocabulary to" },
],
},
{
id: "moefication",
name: "MoEfication & FFN Clustering (Adding Experts)",
category: "moe",
tagline: "Converts a dense 8B model into an 8x8B Mixture of Experts with router",
description: "Splits the dense MLP/feed-forward layers into specialized expert clusters via k-means weight clustering, training a top-2 gating router. Delivers higher representational capacity while keeping active inference compute fixed.",
paperReference: "Zhang et al., 2022 (MoEfication: Transformer Feed-forward Layers are Sparse Experts)",
memorySavings: "Inference compute equals 1 expert",
speedMultiplier: "MoE Sparse routing",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `# Upcycle dense model to Mixture of Experts
from mergekit.moe import MoEBuilder
builder = MoEBuilder(
base_model="unsloth/Meta-Llama-3.1-8B-Instruct",
num_experts=4,
top_k=2,
router_type="softmax",
)
builder.build_moe_architecture()`,
parameters: [
{ name: "num_experts", label: "Number of Experts", type: "number", default: 4, description: "Total expert blocks (e.g. 4 or 8)" },
{ name: "top_k", label: "Top-K Active Experts", type: "number", default: 2, description: "Number of experts activated per token (usually 1 or 2)" },
],
},
{
id: "dare_ties",
name: "DARE-TIES Model Merging (MergeKit)",
category: "moe",
tagline: "Drops redundant delta parameters and resolves sign conflicts across fine-tunes",
description: "Merges multiple specialized models (e.g. your Coding fine-tune + your MCP Tool fine-tune) by dropping 90% of insignificant weight deltas and rescaling the rest with Task-Informed Energy Sign resolution.",
paperReference: "Yu et al., 2024 (Language Models are Super Mario: DARE)",
memorySavings: "Combines models with zero training cost",
speedMultiplier: "Instant merge",
recommendedFor4080: true,
unslothSupported: true,
codeSnippet: `merge_method: dare_ties
base_model: unsloth/Meta-Llama-3.1-8B-Instruct
models:
- model: ./my-coding-adapter-merged
parameters:
weight: 0.6
density: 0.2
- model: ./my-mcp-tool-adapter-merged
parameters:
weight: 0.4
density: 0.2
dtype: bfloat16`,
parameters: [
{ name: "density", label: "Weight Delta Density", type: "number", default: 0.2, description: "Fraction of extreme weights to retain (0.1 to 0.4)" },
],
},
];

25
studio-ref/src/index.css Normal file
View File

@@ -0,0 +1,25 @@
@import "tailwindcss";
@layer base {
body {
background-color: #09090b;
color: #e4e4e7;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
/* Custom dark scrollbars */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #09090b;
}
::-webkit-scrollbar-thumb {
background: #27272a;
border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
background: #3f3f46;
}
}

10
studio-ref/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

222
studio-ref/src/types.ts Normal file
View File

@@ -0,0 +1,222 @@
export type ModelArch = "llama3" | "qwen2.5" | "mistral" | "gemma2" | "deepseek" | "phi4" | "smollm" | "custom";
export type FineTuneMethod =
| "qlora"
| "lora_plus"
| "dora"
| "orpo"
| "dpo"
| "simpo"
| "kto"
| "galore"
| "neftune"
| "longlora";
export type PruningMethod =
| "structured_layer"
| "head_pruning"
| "vocab_trim"
| "laser_svd"
| "wanda"
| "magnitude_dropout";
export type MoEMethod =
| "moefication"
| "dare_ties"
| "slerp"
| "passthrough_franken"
| "task_arithmetic"
| "linear_average";
export type GGUFQuantType =
| "Q4_K_M"
| "Q4_K_S"
| "Q5_K_M"
| "Q5_K_S"
| "Q8_0"
| "IQ4_XS"
| "IQ3_XXS"
| "IQ2_XS"
| "BF16"
| "FP16";
export interface BaseModelInfo {
id: string;
name: string;
huggingFaceId: string;
ollamaName: string;
parametersBillion: number;
layers: number;
hiddenDim: number;
heads: number;
kvHeads: number;
vocabSize: number;
defaultContext: number;
architecture: ModelArch;
baseSizeGb: number;
q4SizeGb: number;
description: string;
recommendedFor4080Super: boolean;
}
export interface TrainingHyperparameters {
// LoRA / PEFT
lora_r: number;
lora_alpha: number;
lora_dropout?: number;
target_modules?: string[];
bias?: "none" | "all" | "lora_only";
use_dora?: boolean;
use_rslora?: boolean;
// Optimizer & Scheduler
batch_size: number;
gradient_accumulation_steps: number;
learning_rate: number;
lr_scheduler?: "cosine" | "linear" | "constant" | "cosine_with_restarts";
warmup_ratio?: number;
warmup_steps?: number;
weight_decay?: number;
max_grad_norm?: number;
optimizer?: "adamw_8bit" | "paged_adamw_8bit" | "adamw_torch" | "galore_adamw";
// Training Duration & Precision
epochs: number;
max_steps?: number;
max_seq_length: number;
precision?: "bfloat16" | "float16";
use_gradient_checkpointing?: boolean;
use_unsloth_fast_backprop?: boolean;
neftune_noise_alpha?: number;
// Preference Alignment (for ORPO/DPO/SimPO)
preference_beta?: number;
simpo_gamma?: number;
}
export interface PruningConfig {
enabled: boolean;
pruneMethod?: PruningMethod;
methods?: PruningMethod[];
layerPruningRange: [number, number]; // e.g. prune layers 16 to 24
targetLayersCount?: number;
headsPrunePercentage?: number;
headPruningRatio?: number; // 0.0 - 0.5
vocabTrimTarget?: number;
vocabTargetTokens?: number; // e.g. 32000 from 128000
laserReductionRank?: number; // e.g. 32
repairLoRASteps?: number;
healingLoraSteps?: number;
}
export interface MoEConfig {
enabled: boolean;
method: MoEMethod;
numExperts: number;
topK: number;
routerType: "softmax" | "sinkhorn" | "switch";
expertSources: {
name: string;
modelId: string;
weight: number;
specialization: string;
}[];
}
export interface MCPToolDeclaration {
id: string;
name: string;
description: string;
serverName: string;
parametersSchema: {
type: "object";
properties: Record<string, { type: string; description: string; enum?: string[] }>;
required?: string[];
};
sampleCallsCount?: number;
}
export interface TrainingDataSample {
id: string;
instruction: string;
input?: string;
output: string;
system?: string;
category?: string;
difficulty?: string;
toolCalls?: {
name: string;
arguments: Record<string, any>;
}[];
simulatedToolResult?: string;
isMcpSample?: boolean;
}
export interface GGUFConfig {
quantization: GGUFQuantType;
contextLength: number;
templateFormat?: "llama3" | "chatml" | "mistral" | "alpaca" | "deepseek" | "gemma";
systemPrompt: string;
stopTokens?: string[];
temperature: number;
top_p?: number;
top_k?: number;
repeat_penalty?: number;
num_gpu_layers: number; // 999 for full 4080 Super offload
threads?: number;
}
export interface HardwarePreset {
name: string;
vramGb: number;
cudaCores: number;
tensorCores: number;
recommendedBatch: number;
recommendedSeqLen: number;
recommendedQuant: GGUFQuantType;
notes: string;
}
export interface DistillationConfig {
enabled: boolean;
teacherModel: string;
studentModel?: string;
distillationType?: "response_generation" | "cot_reasoning" | "logit_kl" | "mcp_alignment";
temperature: number;
includeThoughtChain: boolean;
distillDatasetSize?: number;
samplesToGenerate?: number;
distillationAlpha?: number;
}
export interface TrainingLogEntry {
step: number;
epoch: number;
loss: number;
evalLoss?: number;
learningRate: number;
gradNorm: number;
vramUsedGb: number;
tokensPerSec: number;
sampleOutput?: string;
timestamp: string;
}
export type ActiveTab =
| "models"
| "model"
| "techniques"
| "dataset"
| "mcp"
| "mcp_harness"
| "distill"
| "distillation"
| "pruning"
| "moe"
| "moe_merge"
| "gguf"
| "train"
| "training"
| "deploy"
| "ollama"
| "arena";

View File

@@ -0,0 +1,259 @@
import { BaseModelInfo, GGUFConfig, MoEConfig, PruningConfig, TrainingHyperparameters } from "../types";
export function generateUnslothPythonScript(
model: BaseModelInfo,
params: TrainingHyperparameters,
ggufConfig: GGUFConfig,
pruningConfig: PruningConfig,
customDatasetPath: string = "./dataset.json",
outputModelName: string = "fine-tuned-ollama-model"
): string {
const isDoRA = params.use_dora;
const isORPO = params.neftune_noise_alpha > 0;
const targetModulesStr = JSON.stringify(params.target_modules);
let pruningCode = "";
if (pruningConfig.enabled && pruningConfig.methods.includes("structured_layer")) {
pruningCode = `
# ==========================================
# ✂️ STRUCTURED LAYER PRUNING (ShortGPT Fat-Shaving)
# ==========================================
print(">> Applying structured layer pruning on middle transformer blocks...")
start_prune, end_prune = ${pruningConfig.layerPruningRange[0]}, ${pruningConfig.layerPruningRange[1]}
pruned_layers = [i for i in range(model.config.num_hidden_layers) if not (start_prune <= i <= end_prune)]
model.model.layers = torch.nn.ModuleList([model.model.layers[i] for i in pruned_layers])
model.config.num_hidden_layers = len(pruned_layers)
print(f">> Model layers pruned down to {len(pruned_layers)} layers! Shaved ~25% parameter fat.")
`;
}
return `"""
Ollama Unsloth Studio - State-of-the-Art Fine-Tuning & Quantization Pipeline
Target Model: ${model.name} (${model.huggingFaceId})
Hardware Target: NVIDIA RTX 4080 Super (16GB VRAM) / Windows CUDA
"""
import os
import torch
from unsloth import FastLanguageModel
from datasets import load_dataset
from trl import SFTTrainer
from transformers import TrainingArguments
# 1. Configuration & Hyperparameters
max_seq_length = ${params.max_seq_length}
dtype = None # Auto detection (Float16 / Bfloat16)
load_in_4bit = True # 4-bit NF4 for max VRAM efficiency on RTX 4080 Super
print(">> Initializing FastLanguageModel from Unsloth...")
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="${model.huggingFaceId}",
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
${pruningCode}
# 2. Configure PEFT / LoRA / DoRA Parameters
print(">> Attaching optimized LoRA adapters...")
model = FastLanguageModel.get_peft_model(
model,
r=${params.lora_r},
target_modules=${targetModulesStr},
lora_alpha=${params.lora_alpha},
lora_dropout=${params.lora_dropout},
bias="${params.bias}",
use_gradient_checkpointing="unsloth", # Saves 70% VRAM with zero speed penalty
random_state=3407,
use_rslora=${params.use_rslora},
use_dora=${isDoRA},
)
# 3. Format Dataset & Chat Template (Including MCP & Tool-Calling schemas)
prompt_template = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
${ggufConfig.systemPrompt || "You are an expert AI assistant specialized in precise reasoning and MCP tool execution."}<|eot_id|><|start_header_id|>user<|end_header_id|>
{}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{}<|eot_id|>"""
def formatting_prompts_func(examples):
instructions = examples.get("instruction", [])
inputs = examples.get("input", [""] * len(instructions))
outputs = examples.get("output", [])
texts = []
for instruction, input_text, output in zip(instructions, inputs, outputs):
user_content = f"{instruction}\\n{input_text}".strip() if input_text else instruction
text = prompt_template.format(user_content, output)
texts.append(text)
return { "text" : texts }
print(f">> Loading training dataset from ${customDatasetPath}...")
if os.path.exists("${customDatasetPath}"):
dataset = load_dataset("json", data_files="${customDatasetPath}", split="train")
dataset = dataset.map(formatting_prompts_func, batched=True)
else:
print(">> Notice: Local dataset not found, using demo dataset fallback.")
from datasets import Dataset
dataset = Dataset.from_list([
{"instruction": "Call the filesystem read_file tool on src/App.tsx", "input": "", "output": '{"name": "read_file", "arguments": {"path": "src/App.tsx"}}'}
]).map(formatting_prompts_func, batched=True)
# 4. Training Engine Initialization
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
packing=False, # True for up to 5x speedup for short sequences
args=TrainingArguments(
per_device_train_batch_size=${params.batch_size},
gradient_accumulation_steps=${params.gradient_accumulation_steps},
warmup_ratio=${params.warmup_ratio},
num_train_epochs=${params.epochs},
learning_rate=${params.learning_rate},
fp16=not torch.cuda.is_bf16_supported(),
bf16=torch.cuda.is_bf16_supported(),
logging_steps=1,
optim="${params.optimizer}",
weight_decay=${params.weight_decay},
lr_scheduler_type="${params.lr_scheduler}",
seed=3407,
output_dir="./outputs",
report_to="none",
),
)
print(">> Starting model training loop...")
trainer_stats = trainer.train()
print(">> Training complete! Peak VRAM used:", round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3), "GB")
# 5. Direct GGUF Quantization & Export for Ollama
print(">> Quantizing and saving GGUF directly for Ollama (${ggufConfig.quantization.toLowerCase()})...")
model.save_pretrained_gguf(
"${outputModelName}",
tokenizer,
quantization_method="${ggufConfig.quantization.toLowerCase()}"
)
# 6. Generate Ollama Modelfile
modelfile_content = f"""FROM ./${outputModelName}-${ggufConfig.quantization.toLowerCase()}.gguf
TEMPLATE """ + '"""' + prompt_template + '"""' + f"""
PARAMETER temperature ${ggufConfig.temperature}
PARAMETER top_p ${ggufConfig.top_p}
PARAMETER top_k ${ggufConfig.top_k}
PARAMETER repeat_penalty ${ggufConfig.repeat_penalty}
PARAMETER num_ctx ${ggufConfig.contextLength}
PARAMETER num_gpu ${ggufConfig.num_gpu_layers}
SYSTEM """ + '"""' + "${ggufConfig.systemPrompt}" + '"""'
with open("Modelfile", "w") as f:
f.write(modelfile_content)
print(">> Modelfile created successfully!")
print(">> To run in Ollama, execute:")
print(f" ollama create ${outputModelName} -f Modelfile")
print(f" ollama run ${outputModelName}")
`;
}
export function generateModelfile(
model: BaseModelInfo,
ggufConfig: GGUFConfig,
modelTag: string = "my-custom-model"
): string {
const quantSuffix = ggufConfig.quantization.toLowerCase();
return `# Modelfile generated by Ollama Unsloth Studio
# Optimized for RTX 4080 Super (16GB VRAM) & MCP Plugins
FROM ./${modelTag}-${quantSuffix}.gguf
# Model Parameters
PARAMETER temperature ${ggufConfig.temperature}
PARAMETER top_p ${ggufConfig.top_p}
PARAMETER top_k ${ggufConfig.top_k}
PARAMETER repeat_penalty ${ggufConfig.repeat_penalty}
PARAMETER num_ctx ${ggufConfig.contextLength}
PARAMETER num_gpu ${ggufConfig.num_gpu_layers}
# Chat & Tool Template
TEMPLATE """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|><|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
# System Prompt & MCP Tool Declarations
SYSTEM """${ggufConfig.systemPrompt || "You are an ultra-fast, fine-tuned AI model optimized for local execution and MCP tool harness execution."}"""
`;
}
export function generateWindowsPowerShellScript(
modelTag: string = "my-custom-model"
): string {
return `# Windows RTX 4080 Super One-Click Training & Ollama Deployer
# PowerShell Script for Windows 10/11 with CUDA 12.x
Write-Host "=======================================================" -ForegroundColor Cyan
Write-Host " Ollama Unsloth Studio - Windows RTX 4080 Super Runner" -ForegroundColor Green
Write-Host "=======================================================" -ForegroundColor Cyan
# Check Python environment
if (!(Get-Command python -ErrorAction SilentlyContinue)) {
Write-Host "[-] Python not found. Please install Python 3.10 or 3.11 with PATH enabled." -ForegroundColor Red
Exit
}
# Ensure Virtual Environment
if (!(Test-Path "./venv")) {
Write-Host "[+] Creating virtual environment 'venv'..." -ForegroundColor Yellow
python -m venv venv
}
Write-Host "[+] Activating Virtual Environment..." -ForegroundColor Yellow
& ./venv/Scripts/Activate.ps1
Write-Host "[+] Installing/Updating Unsloth & CUDA PyTorch..." -ForegroundColor Yellow
pip install --upgrade pip
pip install "unsloth[cu121-ampere-torch240] @ git+https://github.com/unslothai/unsloth.git"
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install bitsandbytes trl peft datasets transformers xformers
Write-Host "[+] Starting Unsloth Fine-Tuning..." -ForegroundColor Green
python train_unsloth.py
if (Test-Path "./Modelfile") {
Write-Host "[+] Building Ollama Model '${modelTag}'..." -ForegroundColor Green
ollama create ${modelTag} -f Modelfile
Write-Host "[+] SUCCESS! Model is registered in Ollama." -ForegroundColor Cyan
Write-Host ">> Launching test session with Ollama..." -ForegroundColor Green
ollama run ${modelTag} "Hello! Check your MCP tool calling capabilities."
} else {
Write-Host "[-] Modelfile not generated. Please check training logs." -ForegroundColor Red
}
`;
}
export function generateMergeKitConfig(moeConfig: MoEConfig, baseModel: string): string {
if (moeConfig.method === "moefication") {
return `base_model: ${baseModel}
gate_mode: ${moeConfig.routerType}
dtype: bfloat16
experts:
${moeConfig.expertSources.map((exp) => ` - source_model: ${exp.modelId}
positive_prompts:
- "${exp.specialization}"
parameters:
weight: ${exp.weight}`).join("\n")}
`;
}
return `merge_method: ${moeConfig.method}
base_model: ${baseModel}
models:
${moeConfig.expertSources.map((exp) => ` - model: ${exp.modelId}
parameters:
weight: ${exp.weight}
density: 0.2`).join("\n")}
dtype: bfloat16
`;
}

View File

@@ -0,0 +1,143 @@
import { BaseModelInfo, GGUFConfig, GGUFQuantType, MoEConfig, PruningConfig, TrainingHyperparameters } from "../types";
export interface VRAMCalculationResult {
baseModelVramGb: number;
loraOverheadGb: number;
activationsGb: number;
optimizerStateGb: number;
kvCacheGb: number;
totalTrainingVramGb: number;
fitsIn4080Super: boolean;
utilizationPercent: number;
recommendedBatchSize: number;
recommendedGradAccum: number;
maxRecommendedContext: number;
warnings: string[];
}
export function calculateVRAMFootprint(
model: BaseModelInfo,
params: TrainingHyperparameters,
targetVramGb: number = 16.0
): VRAMCalculationResult {
const warnings: string[] = [];
// Base model in 4-bit NF4 (bitsandbytes / Unsloth)
// 4-bit is ~0.55 bytes per parameter including quantization scale factors
const baseModelVramGb = (model.parametersBillion * 1e9 * 0.55) / (1024 * 1024 * 1024);
// LoRA rank overhead
const numAdaptedModules = (params.target_modules && params.target_modules.length) || 7;
const numLayers = model.layers;
const hiddenDim = model.hiddenDim;
const loraParams = 2 * numAdaptedModules * numLayers * hiddenDim * params.lora_r;
// LoRA weights + gradients in fp32/bf16
const loraOverheadGb = (loraParams * 4 * 2) / (1024 * 1024 * 1024);
// Optimizer state: Paged AdamW 8-bit uses 2 bytes per trainable parameter
const optimizerStateGb = (loraParams * 2) / (1024 * 1024 * 1024);
// KV cache + activations: with Unsloth fast backprop + gradient checkpointing
const seqLength = params.max_seq_length || 4096;
const batchSize = params.batch_size || 1;
let activationFactor = 0.00000000035;
if (params.use_unsloth_fast_backprop) {
activationFactor *= 0.35; // Unsloth cuts activation memory drastically
}
if (params.use_gradient_checkpointing) {
activationFactor *= 0.45;
}
const activationsGb = (batchSize * seqLength * numLayers * hiddenDim * activationFactor);
// KV cache overhead for eval/inference
const kvCacheGb = (2 * numLayers * (model.kvHeads || 8) * (hiddenDim / (model.heads || 32)) * seqLength * 2) / (1024 * 1024 * 1024);
const totalTrainingVramGb = Number(
(baseModelVramGb + loraOverheadGb + optimizerStateGb + activationsGb + 0.8).toFixed(2) // 0.8GB CUDA baseline runtime
);
const fitsIn4080Super = totalTrainingVramGb <= targetVramGb;
const utilizationPercent = Math.min(100, Math.round((totalTrainingVramGb / targetVramGb) * 100));
let recommendedBatchSize = 2;
let recommendedGradAccum = 4;
let maxRecommendedContext = 32768;
if (model.parametersBillion > 13) {
recommendedBatchSize = 1;
recommendedGradAccum = 8;
maxRecommendedContext = 8192;
if (totalTrainingVramGb > 15.5) {
warnings.push("14B models on 16GB VRAM require micro-batch size 1 and gradient accumulation.");
}
} else if (model.parametersBillion > 30) {
warnings.push("30B+ models exceed single 16GB VRAM for training. Use Student-Teacher Distillation or Multi-GPU.");
}
if (seqLength > 16384 && !params.use_gradient_checkpointing) {
warnings.push("High context length (>16k) requires Gradient Checkpointing to avoid Out-Of-Memory (OOM).");
}
return {
baseModelVramGb: Number(baseModelVramGb.toFixed(2)),
loraOverheadGb: Number(loraOverheadGb.toFixed(2)),
activationsGb: Number(activationsGb.toFixed(2)),
optimizerStateGb: Number(optimizerStateGb.toFixed(2)),
kvCacheGb: Number(kvCacheGb.toFixed(2)),
totalTrainingVramGb,
fitsIn4080Super,
utilizationPercent,
recommendedBatchSize,
recommendedGradAccum,
maxRecommendedContext,
warnings,
};
}
export function calculateHardwareCompatibility(
model: BaseModelInfo,
params: TrainingHyperparameters,
ggufConfig?: GGUFConfig,
pruningConfig?: PruningConfig,
moeConfig?: MoEConfig
): VRAMCalculationResult {
const result = calculateVRAMFootprint(model, params, 16.0);
// Apply pruning reductions
if (pruningConfig && pruningConfig.enabled) {
const prunedLayers = Math.max(0, pruningConfig.layerPruningRange[1] - pruningConfig.layerPruningRange[0] + 1);
const reductionRatio = (model.layers - prunedLayers) / model.layers;
result.baseModelVramGb = Number((result.baseModelVramGb * reductionRatio).toFixed(2));
result.totalTrainingVramGb = Number((result.totalTrainingVramGb - 1.2).toFixed(2));
result.utilizationPercent = Math.min(100, Math.round((result.totalTrainingVramGb / 16.0) * 100));
}
return result;
}
export function getGGUFSizeEstimate(paramsBillion: number, quant: GGUFQuantType): { sizeGb: number; bitsPerWeight: number; ramRequiredGb: number } {
const quantMap: Record<GGUFQuantType, { bpw: number; name: string }> = {
Q4_K_M: { bpw: 4.5, name: "Q4_K_M (Optimal balance)" },
Q4_K_S: { bpw: 4.1, name: "Q4_K_S (Compact 4-bit)" },
Q5_K_M: { bpw: 5.5, name: "Q5_K_M (High precision)" },
Q5_K_S: { bpw: 5.1, name: "Q5_K_S" },
Q8_0: { bpw: 8.5, name: "Q8_0 (Near lossless)" },
IQ4_XS: { bpw: 4.25, name: "IQ4_XS (Importance Matrix 4-bit)" },
IQ3_XXS: { bpw: 3.06, name: "IQ3_XXS (Ultra slim 3-bit)" },
IQ2_XS: { bpw: 2.31, name: "IQ2_XS (Extreme 2-bit)" },
BF16: { bpw: 16.0, name: "BF16 (Unquantized)" },
FP16: { bpw: 16.0, name: "FP16 (Unquantized)" },
};
const info = quantMap[quant] || { bpw: 4.5, name: "Q4_K_M" };
const sizeGb = Number(((paramsBillion * 1e9 * (info.bpw / 8)) / (1024 * 1024 * 1024)).toFixed(2));
const ramRequiredGb = Number((sizeGb + 1.2).toFixed(2)); // +1.2GB for context & KV cache in Ollama
return {
sizeGb,
bitsPerWeight: info.bpw,
ramRequiredGb,
};
}

26
studio-ref/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

22
studio-ref/vite.config.ts Normal file
View File

@@ -0,0 +1,22 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import {defineConfig} from 'vite';
export default defineConfig(() => {
return {
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
server: {
// HMR is disabled in AI Studio via DISABLE_HMR env var.
// Do not modify—file watching is disabled to prevent flickering during agent edits.
hmr: process.env.DISABLE_HMR !== 'true',
// Disable file watching when DISABLE_HMR is true to save CPU during agent edits.
watch: process.env.DISABLE_HMR === 'true' ? null : {},
},
};
});