From 4327907bbe2d8a2cd41d4d13c95cb085472a48fe Mon Sep 17 00:00:00 2001 From: Ekuty Barnabas Ebu <137317038+ekutyebu@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:51:23 +0100 Subject: [PATCH 1/2] feat: implement agents discovery page with filtering, sorting, and loading states --- frontend/index.html | 3 + frontend/package-lock.json | 1780 ++++++++++++++++++++ frontend/src/App.css | 345 +++- frontend/src/api/client.ts | 950 ++++++++++- frontend/src/components/AgentCard.css | 152 +- frontend/src/components/AgentCard.tsx | 31 +- frontend/src/components/SkeletonLoader.css | 159 ++ frontend/src/components/SkeletonLoader.tsx | 86 + frontend/src/components/StarRating.tsx | 77 + frontend/src/index.css | 90 +- frontend/src/pages/AgentDetailPage.tsx | 887 +++++++++- frontend/src/pages/AgentsPage.tsx | 194 ++- frontend/src/pages/HomePage.tsx | 83 +- frontend/src/pages/ToolsPage.tsx | 301 +++- 14 files changed, 4906 insertions(+), 232 deletions(-) create mode 100644 frontend/package-lock.json create mode 100644 frontend/src/components/SkeletonLoader.css create mode 100644 frontend/src/components/SkeletonLoader.tsx create mode 100644 frontend/src/components/StarRating.tsx diff --git a/frontend/index.html b/frontend/index.html index a8e5303..c886735 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,6 +5,9 @@ AgentStore — The App Store for Agents + + +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..bf2bd4e --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1780 @@ +{ + "name": "agentstore-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agentstore-frontend", + "version": "0.1.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.0" + }, + "devDependencies": { + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.3", + "vite": "^5.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz", + "integrity": "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.371", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", + "integrity": "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/src/App.css b/frontend/src/App.css index 4ef246a..06539ea 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -2,11 +2,19 @@ min-height: 100vh; display: flex; flex-direction: column; + background-color: var(--bg-primary); + background-image: radial-gradient(circle at 50% 0%, rgba(99, 102, 241, 0.12) 0%, transparent 50%), + radial-gradient(circle at 0% 100%, rgba(168, 85, 247, 0.08) 0%, transparent 40%); } .app-header { - background: #1a1a2e; - color: white; + position: sticky; + top: 0; + z-index: 100; + background: rgba(7, 9, 19, 0.75); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--glass-border); padding: 1rem 2rem; display: flex; justify-content: space-between; @@ -14,65 +22,360 @@ } .app-header .logo { - color: white; - text-decoration: none; + display: flex; + flex-direction: column; + gap: 2px; } .app-header .logo h1 { + font-family: var(--font-heading); font-size: 1.5rem; + font-weight: 800; + letter-spacing: -0.025em; + background: linear-gradient(to right, #ffffff, #a5b4fc); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } .app-header .tagline { - font-size: 0.85rem; - opacity: 0.8; + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 500; + letter-spacing: 0.05em; + text-transform: uppercase; } .app-header nav { display: flex; - gap: 1.5rem; + gap: 1.75rem; } .app-header nav a { - color: #a5b4fc; - font-weight: 500; + color: var(--text-secondary); + font-weight: 600; + font-size: 0.95rem; + position: relative; + padding: 0.25rem 0; +} + +.app-header nav a::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: var(--gradient-primary); + transition: width 0.25s ease; + border-radius: 2px; } .app-header nav a:hover { - color: white; - text-decoration: none; + color: var(--text-primary); +} + +.app-header nav a:hover::after { + width: 100%; } main { flex: 1; - padding: 2rem; + padding: 2.5rem 2rem; max-width: 1200px; margin: 0 auto; width: 100%; + box-sizing: border-box; } footer { text-align: center; - padding: 1rem; - color: #6b7280; - font-size: 0.875rem; - border-top: 1px solid #e5e7eb; + padding: 2rem; + color: var(--text-muted); + font-size: 0.85rem; + border-top: 1px solid var(--glass-border); + background: rgba(7, 9, 19, 0.4); } .page-title { - margin-bottom: 1.5rem; + font-family: var(--font-heading); + font-size: 2.25rem; + font-weight: 800; + letter-spacing: -0.025em; + background: linear-gradient(135deg, #ffffff 30%, #a5b4fc 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 0.5rem; } .agent-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; + margin-top: 1.5rem; } .placeholder-notice { - background: #fef3c7; - border: 1px solid #f59e0b; + background: rgba(245, 158, 11, 0.1); + border: 1px solid rgba(245, 158, 11, 0.2); + border-radius: 12px; + padding: 1rem 1.25rem; + margin-bottom: 1.5rem; + font-size: 0.9rem; + color: #fbbf24; + display: flex; + align-items: center; + gap: 0.75rem; + backdrop-filter: blur(8px); +} + +/* Global Premium Button Styles */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.65rem 1.25rem; border-radius: 8px; - padding: 1rem; + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + border: 1px solid transparent; + outline: none; +} + +.btn-primary { + background: var(--gradient-primary); + color: white; + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.25); +} + +.btn-primary:hover:not(:disabled) { + background: var(--gradient-hover); + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(99, 102, 241, 0.35); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); + border-color: var(--glass-border); +} + +.btn-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +/* Input Styles */ +.form-input { + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--glass-border); + border-radius: 8px; + padding: 0.65rem 1rem; + color: var(--text-primary); + font-family: inherit; + font-size: 0.9rem; + transition: all 0.2s ease; + outline: none; +} + +.form-input:focus { + border-color: var(--color-primary); + background: rgba(255, 255, 255, 0.06); + box-shadow: 0 0 10px rgba(99, 102, 241, 0.15); +} + +/* HomePage layout classes */ +.hero-section { + text-align: center; + padding: 4rem 1rem 3rem; + max-width: 850px; + margin: 0 auto; + animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.hero-title-large { + font-family: var(--font-heading); + font-size: 3.75rem; + font-weight: 800; + line-height: 1.1; + letter-spacing: -0.03em; + background: linear-gradient(135deg, #ffffff 20%, #a5b4fc 60%, #8b5cf6 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 1.5rem; +} + +.hero-subtitle { + font-size: 1.25rem; + color: var(--text-secondary); + max-width: 650px; + margin: 0 auto 2.5rem; + line-height: 1.65; +} + +.hero-buttons { + display: flex; + justify-content: center; + gap: 1.25rem; + margin-bottom: 4rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.5rem; + margin-bottom: 5rem; +} + +@media (max-width: 768px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + .hero-title-large { + font-size: 2.75rem; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + .hero-buttons { + flex-direction: column; + align-items: stretch; + } +} + +.stat-card { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 16px; + padding: 1.5rem; + text-align: center; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--gradient-glow); + opacity: 0; + transition: opacity 0.3s ease; + z-index: 0; +} + +.stat-card:hover { + transform: translateY(-4px); + border-color: var(--glass-hover-border); + box-shadow: var(--glass-shadow); +} + +.stat-card:hover::before { + opacity: 1; +} + +.stat-card-content { + position: relative; + z-index: 1; +} + +.stat-number { + font-family: var(--font-heading); + font-size: 2.75rem; + font-weight: 800; + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + line-height: 1; + margin-bottom: 0.25rem; +} + +.stat-label { + font-size: 0.8rem; + color: var(--text-secondary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: flex-end; margin-bottom: 1.5rem; + border-bottom: 1px solid var(--glass-border); + padding-bottom: 0.75rem; +} + +.section-header h2 { + font-family: var(--font-heading); + font-size: 1.75rem; + font-weight: 700; + letter-spacing: -0.02em; +} + +.section-header a { + font-weight: 600; font-size: 0.9rem; } + +.categories-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 1.25rem; + margin-bottom: 2rem; +} + +.category-card { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 16px; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + text-decoration: none; + color: inherit !important; +} + +.category-card:hover { + background: var(--glass-hover-bg); + border-color: var(--color-primary); + transform: translateY(-2px); + box-shadow: var(--glass-shadow); +} + +.category-icon { + font-size: 2rem; + margin-bottom: 0.25rem; +} + +.category-card h3 { + font-family: var(--font-heading); + font-size: 1.15rem; + font-weight: 600; + color: var(--text-primary); +} + +.category-card p { + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.4; +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 5455caa..7e1a8aa 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,47 +1,953 @@ /** * API client for AgentStore backend. * - * TODO: Students should implement full API integration. - * TODO: Add error handling, loading states, and retry logic. + * Implements full API integration, fallback offline simulation, + * error handling, loading states, and mock database. */ const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8000' -export interface AgentSummary { +// --- Interfaces --- + +export interface JSONSchema { + type: string + properties?: Record + required?: string[] +} + +export interface Agent { id: string name: string description: string category: string + creator: string + version: string + status: string rating: number downloads: number + installs: number + runs: number tags: string[] tools_required: string[] + permissions_required: string[] + inputs: JSONSchema + outputs: JSONSchema + example_use_case: string + example_prompts: string[] +} + +export type AgentSummary = Pick + +export interface Tool { + id: string + name: string + description: string + category: string + version: string + permission_level: 'read' | 'write' | 'read_write' + permissions_required: string[] + input_schema: JSONSchema + output_schema: JSONSchema + mock_implementation_notes: string +} + +export interface Review { + agent_id: string + rating: number + review: string + date: string + user: string +} + +export interface TraceStep { + step: number + action: string + details?: string + tool?: string + input?: Record + output_summary?: string +} + +export interface Trace { + agent_id: string + run_id: string + status: 'completed' | 'failed' + started_at: string + completed_at: string + user_request: string + steps: TraceStep[] + final_output: Record +} + +// --- Mock Database (In-Memory Fallback) --- + +const MOCK_TOOLS: Tool[] = [ + { + id: 'gmail_reader', + name: 'Gmail Reader', + description: 'Reads and summarizes email messages from a user\'s inbox.', + category: 'Email', + version: '1.0.0', + permission_level: 'read', + permissions_required: ['email.read'], + input_schema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search query or label filter, e.g. "is:unread"' }, + max_results: { type: 'integer', description: 'Maximum number of threads to fetch' } + }, + required: ['query'] + }, + output_schema: { + type: 'object', + properties: { + messages: { type: 'array', items: { type: 'object' } } + } + }, + mock_implementation_notes: 'Retrieves raw text snippets of mock emails for semantic parsing.' + }, + { + id: 'calendar_reader', + name: 'Calendar Reader', + description: 'Reads upcoming events and scheduled meetings from calendar feeds.', + category: 'Productivity', + version: '1.0.0', + permission_level: 'read', + permissions_required: ['calendar.read'], + input_schema: { + type: 'object', + properties: { + time_min: { type: 'string', description: 'ISO start datetime' }, + time_max: { type: 'string', description: 'ISO end datetime' } + } + }, + output_schema: { + type: 'object', + properties: { + events: { type: 'array', items: { type: 'object' } } + } + }, + mock_implementation_notes: 'Fetches event items from a calendar store, listing organizer and time ranges.' + }, + { + id: 'file_reader', + name: 'File Reader', + description: 'Parses text content from uploaded files (PDF, DOCX, TXT, CSV).', + category: 'System', + version: '1.0.0', + permission_level: 'read', + permissions_required: ['file.read'], + input_schema: { + type: 'object', + properties: { + file_path: { type: 'string', description: 'Absolute or relative workspace path' } + }, + required: ['file_path'] + }, + output_schema: { + type: 'object', + properties: { + content: { type: 'string' }, + file_size: { type: 'integer' } + } + }, + mock_implementation_notes: 'Loads file raw bytes and returns extracted text layout summaries.' + }, + { + id: 'github_reader', + name: 'GitHub Reader', + description: 'Fetches issue reports, code pull requests, commits, and comments from repositories.', + category: 'Developer', + version: '1.1.0', + permission_level: 'read', + permissions_required: ['github.read'], + input_schema: { + type: 'object', + properties: { + repo: { type: 'string', description: 'Target repo formatted as "owner/name"' }, + state: { type: 'string', enum: ['open', 'closed', 'all'] } + }, + required: ['repo'] + }, + output_schema: { + type: 'object', + properties: { + issues: { type: 'array', items: { type: 'object' } } + } + }, + mock_implementation_notes: 'Returns open issue logs, titles, authors, and existing labels.' + }, + { + id: 'notes_tool', + name: 'Notes Writer', + description: 'Creates, reads, and updates structured markdown notes in the workspace.', + category: 'Productivity', + version: '1.0.0', + permission_level: 'read_write', + permissions_required: ['notes.read', 'notes.write'], + input_schema: { + type: 'object', + properties: { + title: { type: 'string', description: 'File name title of the note' }, + content: { type: 'string', description: 'Markdown format note content body' }, + mode: { type: 'string', enum: ['append', 'overwrite'] } + }, + required: ['title', 'content'] + }, + output_schema: { + type: 'object', + properties: { + success: { type: 'boolean' }, + path: { type: 'string' } + } + }, + mock_implementation_notes: 'Writes file buffers to local data paths for developer workflows.' + }, + { + id: 'spreadsheet_tool', + name: 'Spreadsheet Editor', + description: 'Reads data rows and writes calculations back to CSV/Excel sheets.', + category: 'Data', + version: '1.0.0', + permission_level: 'read_write', + permissions_required: ['spreadsheet.read', 'spreadsheet.write'], + input_schema: { + type: 'object', + properties: { + sheet_path: { type: 'string', description: 'Spreadsheet file path' }, + formulas: { type: 'array', items: { type: 'string' }, description: 'Formulas to run on rows' } + }, + required: ['sheet_path'] + }, + output_schema: { + type: 'object', + properties: { + modified_rows: { type: 'integer' } + } + }, + mock_implementation_notes: 'Executes data cleaning calculations on cell grids.' + }, + { + id: 'web_search', + name: 'Web Search', + description: 'Retrieves web page summaries, news articles, and links matching organic query strings.', + category: 'Search', + version: '2.0.0', + permission_level: 'read', + permissions_required: ['search'], + input_schema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search keywords' } + }, + required: ['query'] + }, + output_schema: { + type: 'object', + properties: { + results: { type: 'array', items: { type: 'object' } } + } + }, + mock_implementation_notes: 'Searches public web domains to compile descriptive reference articles.' + }, + { + id: 'browser_tool', + name: 'Browser Automation', + description: 'Launches a browser environment to automate forms and scrape dynamic HTML.', + category: 'System', + version: '1.2.0', + permission_level: 'read_write', + permissions_required: ['browser.launch', 'browser.interact'], + input_schema: { + type: 'object', + properties: { + url: { type: 'string', description: 'Destination web URL address' }, + actions: { type: 'array', items: { type: 'string' }, description: 'Automation script instructions' } + }, + required: ['url'] + }, + output_schema: { + type: 'object', + properties: { + dom_snapshot: { type: 'string' }, + screenshot_url: { type: 'string' } + } + }, + mock_implementation_notes: 'Runs headless Chromium instances to fetch dynamic JavaScript SPA bundles.' + } +] + +const MOCK_AGENTS: Agent[] = [ + { + id: 'email_summarizer', + name: 'Email Summarizer Agent', + description: 'Summarizes unread emails and highlights action items from your inbox.', + category: 'Productivity', + creator: 'AgentStore Team', + version: '1.0.0', + status: 'published', + rating: 4.5, + downloads: 1250, + installs: 890, + runs: 5420, + tags: ['email', 'productivity', 'summarization'], + tools_required: ['gmail_reader'], + permissions_required: ['email.read'], + inputs: { + type: 'object', + properties: { + query: { type: 'string', description: 'Email filter, e.g. "is:unread"' }, + summary_style: { type: 'string', enum: ['brief', 'detailed', 'action_items'] } + } + }, + outputs: { + type: 'object', + properties: { + summary: { type: 'string' }, + action_items: { type: 'array', items: { type: 'string' } }, + email_count: { type: 'integer' } + } + }, + example_use_case: 'Get a morning briefing of unread emails with prioritized action items before starting work.', + example_prompts: [ + 'Summarize my unread emails from today', + 'What action items do I have in my inbox?' + ] + }, + { + id: 'github_issue_triage', + name: 'GitHub Issue Triage Agent', + description: 'Analyzes open GitHub issues, suggests labels, priority, and assignees.', + category: 'Developer Tools', + creator: 'AgentStore Team', + version: '1.0.0', + status: 'published', + rating: 4.7, + downloads: 980, + installs: 720, + runs: 3100, + tags: ['github', 'developer', 'triage', 'issues'], + tools_required: ['github_reader'], + permissions_required: ['github.read'], + inputs: { + type: 'object', + properties: { + repo: { type: 'string', description: 'owner/repo, e.g. "techx/agentstore"' }, + state: { type: 'string', enum: ['open', 'closed', 'all'] } + } + }, + outputs: { + type: 'object', + properties: { + triaged_issues: { + type: 'array', + items: { type: 'object' } + } + } + }, + example_use_case: 'Automatically triage new issues in an open-source repo every morning.', + example_prompts: [ + 'Triage all open issues in techx/agentstore', + 'Which issues need urgent attention?' + ] + }, + { + id: 'calendar_planner', + name: 'Calendar Planner Agent', + description: 'Coordinates calendar events, resolves overlaps, and drafts agendas.', + category: 'Productivity', + creator: 'AgentStore Team', + version: '1.0.2', + status: 'published', + rating: 4.3, + downloads: 850, + installs: 610, + runs: 2400, + tags: ['calendar', 'meeting', 'productivity'], + tools_required: ['calendar_reader', 'notes_tool'], + permissions_required: ['calendar.read', 'notes.write'], + inputs: { + type: 'object', + properties: { + range: { type: 'string', enum: ['day', 'week', 'month'] }, + create_agendas: { type: 'string', enum: ['yes', 'no'] } + } + }, + outputs: { + type: 'object', + properties: { + overlap_warnings: { type: 'array', items: { type: 'string' } }, + suggested_agenda_paths: { type: 'array', items: { type: 'string' } } + } + }, + example_use_case: 'Optimize weekly schedule and build meeting document skeletons in advance.', + example_prompts: [ + 'Check my calendar for conflicts this week', + 'Generate weekly agendas for overlapping meetings' + ] + }, + { + id: 'resume_reviewer', + name: 'Resume Reviewer Agent', + description: 'Provides professional resume audits against job descriptions.', + category: 'Career', + creator: 'AgentStore Team', + version: '2.0.1', + status: 'published', + rating: 4.8, + downloads: 1420, + installs: 1100, + runs: 6200, + tags: ['resume', 'career', 'job', 'audit'], + tools_required: ['file_reader'], + permissions_required: ['file.read'], + inputs: { + type: 'object', + properties: { + resume_path: { type: 'string', description: 'Path to resume PDF/DOCX' }, + target_job: { type: 'string', description: 'Target job title or description' } + } + }, + outputs: { + type: 'object', + properties: { + score: { type: 'integer' }, + keyword_gaps: { type: 'array', items: { type: 'string' } }, + formatting_issues: { type: 'array', items: { type: 'string' } } + } + }, + example_use_case: 'Audit a software developer CV against a staff engineer role specification.', + example_prompts: [ + 'Analyze resume.pdf against developer job posting', + 'What keywords are missing from my resume?' + ] + }, + { + id: 'study_buddy', + name: 'Study Buddy Agent', + description: 'Generates customized flashcards, study schedules, and summaries from textbook readings.', + category: 'Education', + creator: 'AgentStore Team', + version: '1.0.0', + status: 'published', + rating: 4.6, + downloads: 750, + installs: 500, + runs: 1980, + tags: ['education', 'study', 'flashcards', 'schedule'], + tools_required: ['notes_tool', 'web_search'], + permissions_required: ['notes.read', 'notes.write', 'search'], + inputs: { + type: 'object', + properties: { + topic: { type: 'string', description: 'Academic subject area' }, + exam_date: { type: 'string', description: 'YYYY-MM-DD target exam' } + } + }, + outputs: { + type: 'object', + properties: { + flashcards_created: { type: 'integer' }, + study_plan: { type: 'array', items: { type: 'string' } } + } + }, + example_use_case: 'Generate biochemistry flashcards and a 14-day study plan prior to midterms.', + example_prompts: [ + 'Create flashcards for cellular respiration', + 'Build a study guide for calculus' + ] + }, + { + id: 'travel_deal_finder', + name: 'Travel Deal Finder Agent', + description: 'Scrapes travel portals for flights and hotels, compiling optimized vacation bundles.', + category: 'Travel', + creator: 'AgentStore Team', + version: '1.1.0', + status: 'published', + rating: 3.9, + downloads: 910, + installs: 680, + runs: 4100, + tags: ['travel', 'deals', 'vacation', 'scraper'], + tools_required: ['web_search', 'browser_tool'], + permissions_required: ['search', 'browser.launch', 'browser.interact'], + inputs: { + type: 'object', + properties: { + origin: { type: 'string', description: 'Airport code or city' }, + destination: { type: 'string', description: 'Destination city' }, + dates: { type: 'string', description: 'Range, e.g. "July 5 - July 12"' } + } + }, + outputs: { + type: 'object', + properties: { + recommended_flights: { type: 'array', items: { type: 'object' } }, + hotel_deals: { type: 'array', items: { type: 'object' } }, + total_estimated_cost: { type: 'integer' } + } + }, + example_use_case: 'Locate cheapest round-trip flights from JFK to London Heathrow for summer break.', + example_prompts: [ + 'Find deals from NYC to Paris in August', + 'Track hotel pricing for Tokyo vacation' + ] + }, + { + id: 'data_cleaner', + name: 'Data Cleaner Agent', + description: 'Finds missing values, formats headers, and identifies outliers in spreadsheets.', + category: 'Data Analysis', + creator: 'AgentStore Team', + version: '1.0.5', + status: 'published', + rating: 4.4, + downloads: 1150, + installs: 820, + runs: 3400, + tags: ['data', 'excel', 'cleaning', 'outliers'], + tools_required: ['spreadsheet_tool', 'file_reader'], + permissions_required: ['spreadsheet.read', 'spreadsheet.write', 'file.read'], + inputs: { + type: 'object', + properties: { + file_path: { type: 'string', description: 'Path to CSV data' }, + handle_nulls: { type: 'string', enum: ['drop', 'fill_zero', 'interpolate'] } + } + }, + outputs: { + type: 'object', + properties: { + rows_processed: { type: 'integer' }, + nulls_filled: { type: 'integer' }, + outliers_detected: { type: 'integer' } + } + }, + example_use_case: 'Clean marketing CSV grids containing malformed dates and missing email inputs.', + example_prompts: [ + 'Remove null values and clean users.csv', + 'Format column headers in sales_2026.csv' + ] + }, + { + id: 'meeting_notes', + name: 'Meeting Notes Agent', + description: 'Summarizes transcripts, links relevant events, and documents meeting tasks.', + category: 'Communication', + creator: 'AgentStore Team', + version: '1.2.0', + status: 'published', + rating: 4.6, + downloads: 1380, + installs: 990, + runs: 5120, + tags: ['meeting', 'transcript', 'summary', 'collaboration'], + tools_required: ['notes_tool', 'calendar_reader'], + permissions_required: ['notes.write', 'calendar.read'], + inputs: { + type: 'object', + properties: { + transcript_path: { type: 'string', description: 'Path to meeting txt transcript' }, + meeting_name: { type: 'string', description: 'Name of event to cross-reference' } + } + }, + outputs: { + type: 'object', + properties: { + summary: { type: 'string' }, + decisions: { type: 'array', items: { type: 'string' } }, + tasks: { type: 'array', items: { type: 'object' } } + } + }, + example_use_case: 'Convert a 60-minute Zoom transcript into structured markdown action items.', + example_prompts: [ + 'Generate summary notes for design_sync.txt', + 'Extract decision list from yesterday\'s brainstorm transcript' + ] + }, + { + id: 'content_generator', + name: 'Content Generator Agent', + description: 'Generates SEO blogs, tweets, and articles based on text references and online research.', + category: 'Content Creation', + creator: 'AgentStore Team', + version: '1.0.1', + status: 'published', + rating: 4.2, + downloads: 1040, + installs: 790, + runs: 3750, + tags: ['seo', 'copywriting', 'blog', 'marketing'], + tools_required: ['file_reader', 'web_search'], + permissions_required: ['file.read', 'search'], + inputs: { + type: 'object', + properties: { + topic: { type: 'string', description: 'Content subject details' }, + tone: { type: 'string', enum: ['professional', 'casual', 'humorous'] }, + word_count: { type: 'integer', description: 'Word length limit' } + } + }, + outputs: { + type: 'object', + properties: { + draft: { type: 'string' }, + keywords_targeted: { type: 'array', items: { type: 'string' } } + } + }, + example_use_case: 'Draft a casual 500-word blog post on artificial intelligence using a research brief.', + example_prompts: [ + 'Write a blog post about electric vehicles', + 'Create 3 Twitter hooks on software engineering productivity' + ] + }, + { + id: 'budget_analyzer', + name: 'Budget Analyzer Agent', + description: 'Analyzes business expense records and charts cash flow forecasts.', + category: 'Finance', + creator: 'AgentStore Team', + version: '1.0.0', + status: 'published', + rating: 4.1, + downloads: 690, + installs: 450, + runs: 1670, + tags: ['budget', 'expenses', 'finance', 'forecasting'], + tools_required: ['spreadsheet_tool'], + permissions_required: ['spreadsheet.read', 'spreadsheet.write'], + inputs: { + type: 'object', + properties: { + ledger_path: { type: 'string', description: 'Path to finance ledger file' }, + period: { type: 'string', enum: ['monthly', 'quarterly', 'yearly'] } + } + }, + outputs: { + type: 'object', + properties: { + total_expenses: { type: 'integer' }, + burn_rate: { type: 'integer' }, + recommendations: { type: 'array', items: { type: 'string' } } + } + }, + example_use_case: 'Compile a quarterly overhead expenses report using corporate spreadsheets.', + example_prompts: [ + 'Analyze expenses in Q2_ledger.csv', + 'Calculate company burn rate and cost savings suggestions' + ] + } +] + +const MOCK_REVIEWS: Review[] = [ + { agent_id: 'email_summarizer', rating: 5, review: 'Saves me 30 minutes every morning. The action items feature is incredibly useful.', date: '2026-05-20', user: 'Sophia Carter' }, + { agent_id: 'email_summarizer', rating: 4, review: 'Good summaries but sometimes misses important emails in long threads.', date: '2026-05-22', user: 'James Wilson' }, + { agent_id: 'github_issue_triage', rating: 5, review: 'Perfect for our open source project. Label suggestions are spot on.', date: '2026-05-18', user: 'Liam Martinez' }, + { agent_id: 'meeting_notes', rating: 5, review: 'Best meeting notes agent I\'ve tried. Action items with owners are a game changer.', date: '2026-05-25', user: 'Olivia Davis' }, + { agent_id: 'meeting_notes', rating: 4, review: 'Works well for standups. Would love better integration with calendar events.', date: '2026-05-27', user: 'Noah Taylor' }, + { agent_id: 'resume_reviewer', rating: 5, review: 'Got actionable feedback that helped me land interviews. Highly recommend.', date: '2026-05-15', user: 'Emma Anderson' }, + { agent_id: 'travel_deal_finder', rating: 3, review: 'Found some deals but prices weren\'t always accurate. Needs improvement.', date: '2026-05-19', user: 'Ava Thomas' }, + { agent_id: 'study_buddy', rating: 4, review: 'Flashcards are great for exam prep. Study plans could be more customizable.', date: '2026-05-21', user: 'Elijah White' } +] + +const MOCK_TRACES: Record = { + email_summarizer: { + agent_id: 'email_summarizer', + run_id: 'run_email_001', + status: 'completed', + started_at: '2026-05-29T09:00:00Z', + completed_at: '2026-05-29T09:00:03Z', + user_request: 'Summarize my unread emails from today', + steps: [ + { step: 1, action: 'Receive user request', details: 'Parsed request: summarize unread emails, style=brief' }, + { step: 2, action: 'Identify required tools', details: 'Agent manifest requires: gmail_reader' }, + { + step: 3, + action: 'Call mock tool', + tool: 'gmail_reader', + input: { query: 'is:unread after:2026/05/29', max_results: 10 }, + output_summary: 'Retrieved 5 unread emails' + }, + { step: 4, action: 'Process tool response', details: 'Extracted subjects, senders, and key snippets from 5 messages' }, + { step: 5, action: 'Generate final answer', details: 'Produced brief summary with 3 action items' }, + { step: 6, action: 'Save run history', details: 'Run logged to trace store with run_id run_email_001' } + ], + final_output: { + summary: 'You have 5 unread emails. Key topics: project deadline (Alice), meeting reschedule (Bob), invoice approval (Finance).', + action_items: [ + 'Reply to Alice about project deadline by EOD', + 'Confirm rescheduled meeting with Bob', + 'Approve invoice #4521 in Finance portal' + ], + email_count: 5 + } + }, + github_issue_triage: { + agent_id: 'github_issue_triage', + run_id: 'run_github_001', + status: 'completed', + started_at: '2026-05-29T10:15:00Z', + completed_at: '2026-05-29T10:15:04Z', + user_request: 'Triage all open issues in techx/agentstore', + steps: [ + { step: 1, action: 'Receive user request', details: 'Parsed request: triage open issues for repo techx/agentstore' }, + { step: 2, action: 'Identify required tools', details: 'Agent manifest requires: github_reader' }, + { + step: 3, + action: 'Call mock tool', + tool: 'github_reader', + input: { repo: 'techx/agentstore', state: 'open' }, + output_summary: 'Retrieved 8 open issues' + }, + { step: 4, action: 'Process tool response', details: 'Analyzed issue titles, bodies, and existing labels' }, + { step: 5, action: 'Generate final answer', details: 'Assigned suggested labels, priority, and assignees for 8 issues' }, + { step: 6, action: 'Save run history', details: 'Run logged to trace store with run_id run_github_001' } + ], + final_output: { + triaged_issues: [ + { issue_number: 12, suggested_labels: ['bug', 'frontend'], priority: 'high', suggested_assignee: 'frontend-team' }, + { issue_number: 15, suggested_labels: ['enhancement', 'backend'], priority: 'medium', suggested_assignee: 'backend-team' } + ] + } + }, + meeting_notes: { + agent_id: 'meeting_notes', + run_id: 'run_notes_001', + status: 'completed', + started_at: '2026-05-29T11:00:00Z', + completed_at: '2026-05-29T11:00:05Z', + user_request: 'Generate summary notes for design_sync.txt', + steps: [ + { step: 1, action: 'Receive user request', details: 'Parsed request: extract summary and notes from file' }, + { step: 2, action: 'Identify required tools', details: 'Agent manifest requires: notes_tool, calendar_reader' }, + { + step: 3, + action: 'Call mock tool', + tool: 'calendar_reader', + input: { time_min: '2026-05-29T00:00:00Z', time_max: '2026-05-29T23:59:59Z' }, + output_summary: 'Identified Design Sync calendar event' + }, + { + step: 4, + action: 'Call mock tool', + tool: 'notes_tool', + input: { title: 'design_sync.txt', content: '', mode: 'append' }, + output_summary: 'Retrieved text log buffer of transcript (14,500 characters)' + }, + { step: 5, action: 'Analyze details', details: 'Extracted key decisions on navbar color, button click sizes, and search latency' }, + { step: 6, action: 'Format markdown notes', details: 'Structured outline: Decisions, Open questions, Action owners' }, + { step: 7, action: 'Write notes output', details: 'Created note path "/notes/design_sync_summary.md"' } + ], + final_output: { + summary: 'Daily sync on design system modifications. Discussed glassmorphic tokens, responsiveness, and rating details.', + decisions: [ + 'Use Indigo gradient primary theme base (#6366f1 to #a855f7)', + 'Enable overlay background glassmorphic frames on grids' + ], + tasks: [ + { task: 'Implement SVG star layout rating component', owner: 'Frontend Developer' }, + { task: 'Construct simulated terminal logs with loader delays', owner: 'UX Designer' } + ] + } + } } +// In-memory runtime state for edits +const runtimeReviews = [...MOCK_REVIEWS] +const runtimeAgents = [...MOCK_AGENTS] + +// --- API Client Methods --- + export async function fetchAgents(): Promise { - const res = await fetch(`${API_BASE}/agents`) - if (!res.ok) throw new Error('Failed to fetch agents') - return res.json() + try { + const res = await fetch(`${API_BASE}/agents`) + if (!res.ok) throw new Error('API server returned error status') + const data = await res.json() + // Map full agent objects to summary + return data.map((a: Agent) => ({ + id: a.id, + name: a.name, + description: a.description, + category: a.category, + rating: a.rating, + downloads: a.downloads, + tags: a.tags || [], + tools_required: a.tools_required || [] + })) + } catch (error) { + console.warn('Backend server unreachable. Falling back to local offline Agent DB.', error) + // Return summaries from local db + return runtimeAgents.map((a) => ({ + id: a.id, + name: a.name, + description: a.description, + category: a.category, + rating: a.rating, + downloads: a.downloads, + tags: a.tags, + tools_required: a.tools_required + })) + } } -export async function fetchAgent(id: string): Promise { - const res = await fetch(`${API_BASE}/agents/${id}`) - if (!res.ok) throw new Error(`Failed to fetch agent: ${id}`) - return res.json() +export async function fetchAgent(id: string): Promise { + try { + const res = await fetch(`${API_BASE}/agents/${id}`) + if (!res.ok) throw new Error(`Agent not found: ${id}`) + return await res.json() + } catch (error) { + console.warn(`Backend fetch failed for agent ${id}. Using offline fallback.`, error) + const localAgent = runtimeAgents.find((a) => a.id === id) + if (!localAgent) { + throw new Error(`Agent not found in offline DB: ${id}`) + } + return localAgent + } } -export async function fetchTools(): Promise { - const res = await fetch(`${API_BASE}/tools`) - if (!res.ok) throw new Error('Failed to fetch tools') - return res.json() +export async function fetchTools(): Promise { + try { + const res = await fetch(`${API_BASE}/tools`) + if (!res.ok) throw new Error('Failed to fetch tools list') + return await res.json() + } catch (error) { + console.warn('Backend fetch failed for tools. Using offline fallback.', error) + return MOCK_TOOLS + } } -export async function runAgent(id: string, input: Record = {}): Promise { - const res = await fetch(`${API_BASE}/agents/${id}/run`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ input }), - }) - if (!res.ok) throw new Error(`Failed to run agent: ${id}`) - return res.json() +export async function runAgent(id: string, input: Record = {}): Promise { + try { + const res = await fetch(`${API_BASE}/agents/${id}/run`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ input }), + }) + if (!res.ok) throw new Error(`Run failed for agent: ${id}`) + return await res.json() + } catch (error) { + console.warn(`Backend run failed for agent ${id}. Simulating trace locally.`, error) + // Artificial 500ms delay to feel premium before returning simulated trace + await new Promise((resolve) => setTimeout(resolve, 500)) + + // Check if we have pre-configured trace + const prebuilt = MOCK_TRACES[id] + if (prebuilt) { + return prebuilt + } + + // Procedural trace generation for agents without explicit traces + const agent = runtimeAgents.find((a) => a.id === id) + const agentName = agent ? agent.name : 'Unknown Agent' + const tools = agent ? agent.tools_required : [] + + const steps: TraceStep[] = [ + { step: 1, action: 'Receive execution request', details: `Parsed input: ${JSON.stringify(input)}` } + ] + + let stepCounter = 2 + tools.forEach((toolId) => { + steps.push({ + step: stepCounter++, + action: `Invoke system tool: ${toolId}`, + tool: toolId, + input: { input }, + output_summary: `Tool execution simulated successfully.` + }) + }) + + steps.push({ step: stepCounter++, action: 'Synthesize data models', details: `Compiling LLM summaries for ${agentName}` }) + steps.push({ step: stepCounter, action: 'Return final result', details: 'Simulated execution completed.' }) + + return { + agent_id: id, + run_id: `run_sim_${Math.random().toString(36).substr(2, 9)}`, + status: 'completed', + started_at: new Date(Date.now() - 3000).toISOString(), + completed_at: new Date().toISOString(), + user_request: Object.values(input)[0] as string || `Trigger execution of ${agentName}`, + steps, + final_output: { + status: 'success', + message: `Simulated run completed successfully for ${agentName}!`, + tools_triggered: tools, + timestamp: new Date().toISOString() + } + } + } +} + +export async function fetchReviews(agentId: string): Promise { + try { + const res = await fetch(`${API_BASE}/agents/${agentId}/reviews`) + if (!res.ok) throw new Error('Failed to fetch reviews') + return await res.json() + } catch { + return runtimeReviews.filter((r) => r.agent_id === agentId) + } +} + +export async function addReview(agentId: string, reviewData: { rating: number; review: string; user: string }): Promise { + const newReview: Review = { + agent_id: agentId, + rating: reviewData.rating, + review: reviewData.review, + user: reviewData.user || 'Anonymous User', + date: new Date().toISOString().split('T')[0] + } + + try { + const res = await fetch(`${API_BASE}/agents/${agentId}/reviews`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newReview) + }) + if (!res.ok) throw new Error('API review submit failed') + return await res.json() + } catch (error) { + console.warn('Backend review submit failed or unavailable. Appending to offline database.', error) + // Add review locally in-memory + runtimeReviews.unshift(newReview) + + // Update agent score in memory + const agent = runtimeAgents.find((a) => a.id === agentId) + if (agent) { + const allRatings = runtimeReviews.filter((r) => r.agent_id === agentId).map((r) => r.rating) + const avg = allRatings.reduce((sum, val) => sum + val, 0) / allRatings.length + agent.rating = avg + } + + return newReview + } +} + +export async function forkAgent(id: string, newName: string): Promise { + try { + const res = await fetch(`${API_BASE}/agents/${id}/fork`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: newName }) + }) + if (!res.ok) throw new Error('Fork failed') + return await res.json() + } catch (error) { + console.warn(`Backend fork failed. Simulating locally.`, error) + const baseAgent = runtimeAgents.find((a) => a.id === id) + if (!baseAgent) throw new Error(`Base agent ${id} not found to remix.`) + + const forked: Agent = { + ...baseAgent, + id: `${id}_fork_${Math.floor(Math.random() * 1000)}`, + name: newName, + creator: 'You (Remix)', + downloads: 0, + installs: 1, + runs: 0, + rating: 5.0 + } + + runtimeAgents.unshift(forked) + return forked + } } diff --git a/frontend/src/components/AgentCard.css b/frontend/src/components/AgentCard.css index ca3a56f..9f33e39 100644 --- a/frontend/src/components/AgentCard.css +++ b/frontend/src/components/AgentCard.css @@ -1,70 +1,164 @@ .agent-card { - display: block; - background: white; - border: 1px solid #e5e7eb; - border-radius: 12px; - padding: 1.25rem; + display: flex; + flex-direction: column; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 16px; + padding: 1.5rem; color: inherit; text-decoration: none; - transition: box-shadow 0.2s, border-color 0.2s; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); + position: relative; + overflow: hidden; + height: 100%; +} + +.agent-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--gradient-glow); + opacity: 0; + transition: opacity 0.3s ease; + z-index: 0; } .agent-card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-color: #4f46e5; + transform: translateY(-4px); + border-color: var(--glass-hover-border); + box-shadow: var(--glass-shadow); text-decoration: none; } +.agent-card:hover::before { + opacity: 1; +} + +.agent-card-header, +.description, +.agent-card-footer, +.tools { + position: relative; + z-index: 1; +} + .agent-card-header { display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 0.5rem; + gap: 0.75rem; + margin-bottom: 0.75rem; } .agent-card-header h3 { - font-size: 1.1rem; - color: #1a1a2e; + font-family: var(--font-heading); + font-size: 1.2rem; + font-weight: 700; + color: var(--text-primary); + line-height: 1.3; } .category { font-size: 0.75rem; - background: #eef2ff; - color: #4f46e5; - padding: 0.2rem 0.5rem; - border-radius: 4px; + font-weight: 600; + background: rgba(99, 102, 241, 0.15); + color: #a5b4fc; + padding: 0.25rem 0.6rem; + border-radius: 20px; + white-space: nowrap; + border: 1px solid rgba(99, 102, 241, 0.2); } .description { font-size: 0.9rem; - color: #6b7280; - margin-bottom: 0.75rem; - line-height: 1.4; + color: var(--text-secondary); + margin-bottom: 1.25rem; + line-height: 1.5; + flex-grow: 1; } .agent-card-footer { display: flex; justify-content: space-between; + align-items: center; font-size: 0.85rem; - color: #9ca3af; - margin-bottom: 0.5rem; + color: var(--text-secondary); + border-top: 1px solid rgba(255, 255, 255, 0.05); + padding-top: 0.75rem; + margin-bottom: 0.75rem; } -.rating { - color: #f59e0b; - font-weight: 600; +.rating-container { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.rating-num { + font-weight: 700; + color: var(--color-warning); +} + +.downloads { + font-weight: 500; + color: var(--text-muted); } .tools { display: flex; flex-wrap: wrap; - gap: 0.35rem; + gap: 0.4rem; } .tool-tag { - font-size: 0.7rem; - background: #f3f4f6; - color: #4b5563; - padding: 0.15rem 0.4rem; - border-radius: 3px; + font-size: 0.72rem; + font-weight: 600; + background: rgba(255, 255, 255, 0.04); + color: var(--text-secondary); + padding: 0.2rem 0.5rem; + border-radius: 6px; + border: 1px solid var(--glass-border); + transition: all 0.2s ease; +} + +.agent-card:hover .tool-tag { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.15); +} + +/* Trending Badge */ +.trending-badge { + position: absolute; + top: 0; + right: 1.5rem; + background: linear-gradient(to right, #ec4899, #f43f5e); + color: white; + font-size: 0.65rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 0.2rem 0.6rem; + border-radius: 0 0 8px 8px; + display: flex; + align-items: center; + gap: 0.25rem; + box-shadow: 0 4px 10px rgba(244, 63, 94, 0.3); + z-index: 10; +} + +.trending-pulse { + width: 6px; + height: 6px; + background-color: white; + border-radius: 50%; + animation: pulseGrad 1.5s infinite; +} + +@keyframes pulseGrad { + 0% { transform: scale(0.8); opacity: 0.5; } + 50% { transform: scale(1.2); opacity: 1; } + 100% { transform: scale(0.8); opacity: 0.5; } } diff --git a/frontend/src/components/AgentCard.tsx b/frontend/src/components/AgentCard.tsx index 0b321dd..37706a2 100644 --- a/frontend/src/components/AgentCard.tsx +++ b/frontend/src/components/AgentCard.tsx @@ -1,33 +1,44 @@ import { Link } from 'react-router-dom' import type { AgentSummary } from '../api/client' +import StarRating from './StarRating' import './AgentCard.css' interface Props { agent: AgentSummary } -/** - * AgentCard — displays a single agent in the browse grid. - * - * TODO: Add install count, trending badge, category chip styling - * TODO: Add star rating display component - */ export default function AgentCard({ agent }: Props) { + // Determine if agent is trending based on rating + downloads + const isTrending = agent.rating >= 4.5 && agent.downloads >= 1000 + return ( - + + {isTrending && ( +
+ + Trending +
+ )}

{agent.name}

{agent.category}

{agent.description}

+
- ★ {agent.rating.toFixed(1)} +
+ + {agent.rating.toFixed(1)} +
{agent.downloads.toLocaleString()} downloads
- {agent.tools_required.length > 0 && ( + + {agent.tools_required && agent.tools_required.length > 0 && (
{agent.tools_required.map((tool) => ( - {tool} + + 🔧 {tool.replace('_', ' ')} + ))}
)} diff --git a/frontend/src/components/SkeletonLoader.css b/frontend/src/components/SkeletonLoader.css new file mode 100644 index 0000000..0456efc --- /dev/null +++ b/frontend/src/components/SkeletonLoader.css @@ -0,0 +1,159 @@ +.skeleton-card { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 12px; + padding: 1.25rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.skeleton-box { + background: linear-gradient(90deg, rgba(255, 255, 255, 0.03) 25%, rgba(255, 255, 255, 0.08) 37%, rgba(255, 255, 255, 0.03) 63%); + background-size: 400% 100%; + animation: shimmer 1.4s ease infinite; + border-radius: 4px; +} + +.skeleton-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.25rem; +} + +.skeleton-header .title { + width: 50%; + height: 20px; +} + +.skeleton-header .category-badge { + width: 25%; + height: 16px; +} + +.description-line-1 { + width: 100%; + height: 14px; +} + +.description-line-2 { + width: 75%; + height: 14px; +} + +.skeleton-footer { + display: flex; + justify-content: space-between; + margin-top: 0.5rem; +} + +.rating-pill { + width: 20%; + height: 16px; +} + +.downloads-pill { + width: 30%; + height: 16px; +} + +.skeleton-tools { + display: flex; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.tool-tag-pill { + width: 25%; + height: 14px; +} + +/* Detail page skeleton */ +.skeleton-detail { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.back-link { + width: 100px; + height: 20px; +} + +.skeleton-cols { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 2rem; +} + +@media (max-width: 768px) { + .skeleton-cols { + grid-template-columns: 1fr; + } +} + +.skeleton-left-col { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.hero-avatar { + width: 64px; + height: 64px; + border-radius: 16px; +} + +.hero-title { + width: 40%; + height: 32px; +} + +.hero-desc { + width: 90%; + height: 18px; +} + +.skeleton-button-row { + display: flex; + gap: 0.75rem; +} + +.action-btn { + width: 120px; + height: 38px; + border-radius: 8px; +} + +.skeleton-section { + margin-top: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.section-title { + width: 30%; + height: 24px; + margin-bottom: 0.5rem; +} + +.list-item { + width: 100%; + height: 18px; +} + +.skeleton-right-col { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.card-box { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 12px; + height: 200px; + padding: 1.25rem; +} diff --git a/frontend/src/components/SkeletonLoader.tsx b/frontend/src/components/SkeletonLoader.tsx new file mode 100644 index 0000000..785cc27 --- /dev/null +++ b/frontend/src/components/SkeletonLoader.tsx @@ -0,0 +1,86 @@ +import './SkeletonLoader.css' + +export function AgentCardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +export function AgentGridSkeleton() { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ) +} + +export function AgentDetailSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +export function ToolCardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ) +} + +export function ToolGridSkeleton() { + return ( +
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))} +
+ ) +} diff --git a/frontend/src/components/StarRating.tsx b/frontend/src/components/StarRating.tsx new file mode 100644 index 0000000..120a177 --- /dev/null +++ b/frontend/src/components/StarRating.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react' + +interface Props { + rating: number + interactive?: boolean + onChange?: (rating: number) => void + size?: number +} + +export default function StarRating({ rating, interactive = false, onChange, size = 18 }: Props) { + const [hoverRating, setHoverRating] = useState(null) + + const activeRating = hoverRating !== null ? hoverRating : rating + + const handleMouseEnter = (index: number) => { + if (interactive) setHoverRating(index) + } + + const handleMouseLeave = () => { + if (interactive) setHoverRating(null) + } + + const handleClick = (index: number) => { + if (interactive && onChange) { + onChange(index) + } + } + + const renderStar = (index: number) => { + // Determine fill percentage + let fill = 0 + if (activeRating >= index) { + fill = 100 + } else if (activeRating > index - 1) { + fill = (activeRating - (index - 1)) * 100 + } + + const starId = `star-grad-${index}-${fill}` + + return ( + = index ? 'scale(1.2)' : 'scale(1)', + }} + onMouseEnter={() => handleMouseEnter(index)} + onMouseLeave={handleMouseLeave} + onClick={() => handleClick(index)} + > + + + + + + + = index - 0.5 ? '#f59e0b' : 'rgba(255, 255, 255, 0.2)'} + strokeWidth="1" + strokeLinejoin="round" + /> + + ) + } + + return ( +
+ {[1, 2, 3, 4, 5].map((i) => renderStar(i))} +
+ ) +} diff --git a/frontend/src/index.css b/frontend/src/index.css index bbe08e4..00833a9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,8 +1,40 @@ :root { - font-family: system-ui, -apple-system, sans-serif; - line-height: 1.5; - color: #1a1a2e; - background: #f8f9fc; + font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; + line-height: 1.6; + + /* Color Palette */ + --bg-primary: #070913; + --bg-secondary: #0d1127; + --bg-tertiary: #171c3a; + + --text-primary: #f3f4f6; + --text-secondary: #9ca3af; + --text-muted: #6b7280; + + --color-primary: #6366f1; /* Indigo */ + --color-secondary: #a855f7; /* Purple */ + --color-accent: #10b981; /* Emerald Green */ + --color-warning: #f59e0b; /* Amber */ + --color-danger: #ef4444; /* Red */ + --color-info: #0ea5e9; /* Sky Blue */ + + --gradient-primary: linear-gradient(135deg, #6366f1, #a855f7); + --gradient-glow: linear-gradient(135deg, rgba(99, 102, 241, 0.15), rgba(168, 85, 247, 0.15)); + --gradient-hover: linear-gradient(135deg, #4f46e5, #9333ea); + --gradient-accent: linear-gradient(135deg, #10b981, #0ea5e9); + + /* Glassmorphism Variables */ + --glass-bg: rgba(13, 17, 39, 0.65); + --glass-border: rgba(255, 255, 255, 0.08); + --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); + --glass-hover-bg: rgba(23, 28, 58, 0.85); + --glass-hover-border: rgba(99, 102, 241, 0.4); + + /* Fonts */ + --font-heading: 'Outfit', sans-serif; + --font-mono: 'Orbitron', monospace; + + color-scheme: dark; } * { @@ -12,14 +44,60 @@ } body { + background-color: var(--bg-primary); + color: var(--text-primary); min-height: 100vh; + overflow-x: hidden; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-track { + background: var(--bg-primary); +} +::-webkit-scrollbar-thumb { + background: var(--bg-tertiary); + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--color-primary); } a { - color: #4f46e5; + color: var(--color-primary); text-decoration: none; + transition: all 0.2s ease; } a:hover { - text-decoration: underline; + color: var(--color-secondary); + text-shadow: 0 0 8px rgba(168, 85, 247, 0.3); +} + +/* Keyframe Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(15px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes pulseGlow { + 0%, 100% { box-shadow: 0 0 15px rgba(99, 102, 241, 0.2); } + 50% { box-shadow: 0 0 25px rgba(168, 85, 247, 0.4); } +} + +@keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} + +@keyframes rotateGrad { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.fade-in { + animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; } diff --git a/frontend/src/pages/AgentDetailPage.tsx b/frontend/src/pages/AgentDetailPage.tsx index 088fd1c..e237877 100644 --- a/frontend/src/pages/AgentDetailPage.tsx +++ b/frontend/src/pages/AgentDetailPage.tsx @@ -1,78 +1,851 @@ -import { useEffect, useState } from 'react' -import { useParams, Link } from 'react-router-dom' -import { fetchAgent, runAgent } from '../api/client' - -/** - * AgentDetailPage — full agent detail view. - * - * TODO: Show tools required with links to tool pages - * TODO: Show permissions, example prompts, reviews - * TODO: Implement Run Agent button with trace viewer - * TODO: Implement Remix/Fork button - * TODO: Show version history - */ +import { useEffect, useState, useRef } from 'react' +import { useParams, Link, useNavigate } from 'react-router-dom' +import { fetchAgent, runAgent, fetchReviews, addReview, forkAgent, type Agent, type Review, type Trace } from '../api/client' +import StarRating from '../components/StarRating' +import { AgentDetailSkeleton } from '../components/SkeletonLoader' + export default function AgentDetailPage() { const { id } = useParams<{ id: string }>() - const [agent, setAgent] = useState | null>(null) - const [runResult, setRunResult] = useState(null) + const navigate = useNavigate() + + const [agent, setAgent] = useState(null) + const [reviews, setReviews] = useState([]) const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + // Simulation run state + const [inputsState, setInputsState] = useState>({}) + const [running, setRunning] = useState(false) + const [runTrace, setRunTrace] = useState(null) + const [currentStepIndex, setCurrentStepIndex] = useState(-1) + const terminalBottomRef = useRef(null) + + // Remix Modal state + const [showRemix, setShowRemix] = useState(false) + const [remixName, setRemixName] = useState('') + const [remixing, setRemixing] = useState(false) + + // Install State + const [installed, setInstalled] = useState(false) + const [installing, setInstalling] = useState(false) + + // Review Form state + const [reviewName, setReviewName] = useState('') + const [reviewRating, setReviewRating] = useState(5) + const [reviewComment, setReviewComment] = useState('') + const [submittingReview, setSubmittingReview] = useState(false) + + // JSON schema collapse states + const [inputsOpen, setInputsOpen] = useState(true) + const [outputsOpen, setOutputsOpen] = useState(true) useEffect(() => { if (!id) return - fetchAgent(id) - .then((a) => setAgent(a as unknown as Record)) - .catch(() => setAgent(null)) - .finally(() => setLoading(false)) + setLoading(true) + Promise.all([fetchAgent(id), fetchReviews(id)]) + .then(([agentData, reviewsData]) => { + setAgent(agentData) + setReviews(reviewsData) + + // Initialize inputs inputs default values + const defaults: Record = {} + if (agentData.inputs && agentData.inputs.properties) { + Object.entries(agentData.inputs.properties).forEach(([key, prop]) => { + defaults[key] = prop.enum ? prop.enum[0] : '' + }) + } + setInputsState(defaults) + setError(null) + }) + .catch((err) => { + setError(err.message) + setAgent(null) + }) + .finally(() => { + setTimeout(() => setLoading(false), 500) + }) }, [id]) - const handleRun = async () => { - if (!id) return + // Scroll terminal automatically during simulation steps + useEffect(() => { + if (running && terminalBottomRef.current) { + terminalBottomRef.current.scrollIntoView({ behavior: 'smooth' }) + } + }, [currentStepIndex, running]) + + if (loading) return + if (error || !agent) { + return ( +
+

Agent Not Found

+

+ Could not locate listing ID: {id} +

+ + ← Back to Marketplace + +
+ ) + } + + // Handle run simulation + const handleExecute = async (e: React.FormEvent) => { + e.preventDefault() + if (running) return + + setRunning(true) + setRunTrace(null) + setCurrentStepIndex(-1) + try { - const result = await runAgent(id, {}) - setRunResult(result) + const trace = await runAgent(agent.id, inputsState) + setRunTrace(trace) + + // Animate execution logs stepping + let stepIdx = 0 + const interval = setInterval(() => { + setCurrentStepIndex(stepIdx) + stepIdx++ + if (stepIdx >= trace.steps.length) { + clearInterval(interval) + setRunning(false) + } + }, 700) } catch { - setRunResult({ error: 'Run failed — is the backend running?' }) + setRunning(false) + } + } + + // Handle remix listing + const handleRemixSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!remixName.trim()) return + setRemixing(true) + try { + const result = await forkAgent(agent.id, remixName.trim()) + setTimeout(() => { + setRemixing(false) + setShowRemix(false) + setRemixName('') + navigate(`/agents/${result.id}`) + }, 1000) + } catch (err) { + setRemixing(false) + alert('Remix failed') + } + } + + // Handle simulated install count + const handleInstallToggle = () => { + if (installed) { + setInstalled(false) + } else { + setInstalling(true) + setTimeout(() => { + setInstalling(false) + setInstalled(true) + }, 1200) } } - if (loading) return

Loading...

- if (!agent) return

Agent not found. Back to agents

+ // Handle review submit + const handleReviewSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!reviewComment.trim()) return + setSubmittingReview(true) + try { + const newReview = await addReview(agent.id, { + rating: reviewRating, + review: reviewComment.trim(), + user: reviewName.trim() || 'Anonymous Developer' + }) + + // Update reviews list and recalculate average + const updatedReviews = [newReview, ...reviews] + setReviews(updatedReviews) + + const sum = updatedReviews.reduce((acc, r) => acc + r.rating, 0) + const avg = sum / updatedReviews.length + setAgent({ + ...agent, + rating: avg + }) + + // Reset form + setReviewName('') + setReviewRating(5) + setReviewComment('') + } catch (err) { + alert('Could not submit review') + } finally { + setSubmittingReview(false) + } + } + + // Reviews distribution analytics calculations + const totalReviews = reviews.length + const distribution = [0, 0, 0, 0, 0] // 5, 4, 3, 2, 1 + reviews.forEach((r) => { + const starIdx = 5 - Math.round(r.rating) + if (starIdx >= 0 && starIdx < 5) { + distribution[starIdx]++ + } + }) return ( -
- ← Back to agents -

{String(agent.name)}

-

{String(agent.description)}

- -
- - - +
+ + ← Back to Marketplace + + + {/* Hero Header */} +
+ {/* Glowing avatar ring */} +
+ {agent.category === 'Productivity' ? '⚡' : + agent.category === 'Developer Tools' ? '💻' : + agent.category === 'Career' ? '🎯' : + agent.category === 'Data Analysis' ? '📊' : + agent.category === 'Education' ? '🎓' : + agent.category === 'Travel' ? '✈️' : + agent.category === 'Communication' ? '💬' : '💰'} +
+ +
+
+

{agent.name}

+ {agent.category} +
+

+ {agent.description} +

+
+
+ + {agent.rating.toFixed(1)} + ({totalReviews} reviews) +
+
+ 📥 {agent.downloads.toLocaleString()} downloads +
+
+ ⚡ {agent.runs.toLocaleString()} mock runs +
+
+
-
-

Details

-
    -
  • Category: {String(agent.category)}
  • -
  • Rating: ★ {Number(agent.rating).toFixed(1)}
  • -
  • Tools: {(agent.tools_required as string[])?.join(', ') || 'None'}
  • -
  • Example: {String(agent.example_use_case || 'N/A')}
  • -
-
- - {runResult && ( -
-

Run Result

-
-            {JSON.stringify(runResult, null, 2)}
-          
+ {/* Main Two-Column Layout */} +
+ {/* Left Side: Actions and Details Metadata */} + + + {/* Right Side: Core Content Details */} +
+ + {/* Required Tools Hub */} +
+

Required Tools

+ {agent.tools_required && agent.tools_required.length > 0 ? ( +
+ {agent.tools_required.map((toolId) => ( + { + e.currentTarget.style.borderColor = 'var(--color-primary)' + e.currentTarget.style.background = 'rgba(99, 102, 241, 0.04)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = 'var(--glass-border)' + e.currentTarget.style.background = 'rgba(255, 255, 255, 0.02)' + }} + > + ⚙️ +
+
+ {toolId.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())} +
+
+ View schema details → +
+
+ + ))} +
+ ) : ( +
This agent doesn't require any external registry tools.
+ )} +
+ + {/* Interactive JSON Schema Inspector */} +
+

Data Schemas

+ + {/* Input Schema */} +
+ + {inputsOpen && ( +
+                  {JSON.stringify(agent.inputs, null, 2)}
+                
+ )} +
+ + {/* Output Schema */} +
+ + {outputsOpen && ( +
+                  {JSON.stringify(agent.outputs, null, 2)}
+                
+ )} +
+
+ + {/* Running Terminal Simulator */} +
+ {/* Terminal Header */} +
+
+ + + + SIMULATOR_TERMINAL.SH +
+ RUNNING ONLINE +
+ + {/* Inputs Config Forms */} +
+

Setup Custom Parameters

+ +
+ {agent.inputs && agent.inputs.properties && ( + Object.entries(agent.inputs.properties).map(([key, prop]) => ( +
+ + {prop.enum ? ( + + ) : ( + setInputsState({ ...inputsState, [key]: e.target.value })} + className="form-input" + style={{ background: '#0f172a', borderColor: '#1e293b' }} + /> + )} +
+ )) + )} +
+
+ 💡 Select or type parameters above to customize execution outcomes. +
+ +
+
+
+ + {/* Terminal Log Console */} +
+ {/* Startup text */} +
// AgentStore simulated runtime debugger. Ready.
+ {agent.example_prompts && agent.example_prompts.length > 0 && ( +
+ Example Prompts: {agent.example_prompts.map(p => `"${p}"`).join(' | ')} +
+ )} + + {/* Trace steps */} + {runTrace && runTrace.steps && ( +
+
$ agentstore-cli --run {agent.id} --input '{JSON.stringify(inputsState)}'
+ {runTrace.steps.map((step, idx) => { + const show = idx <= currentStepIndex + if (!show) return null + return ( +
+ [{new Date(runTrace.started_at).toLocaleTimeString()}]{' '} + [Step {step.step}] {step.action} + {step.details && — {step.details}} + {step.tool && (Target Tool: {step.tool})} + {step.output_summary &&
↳ Tool Result: {step.output_summary}
} +
+ ) + })} +
+ )} + + {/* Show running state dots loader */} + {running && ( +
+ ⏳ Processing remote node vectors + +
+ )} + + {/* Output block display */} + {runTrace && currentStepIndex >= runTrace.steps.length - 1 && ( +
+
🟢 RUN COMPLETED [STATUS: 200 OK]
+
+                    {JSON.stringify(runTrace.final_output, null, 2)}
+                  
+
+ )} +
+
+
+ + {/* Interactive Reviews Hub */} +
+

Ratings & Reviews

+ +
+ + {/* Ratings Distribution graph */} +
+
+ {agent.rating.toFixed(1)} + / 5.0 +
+
+ +
+ Based on {totalReviews} reviews +
+ + {/* Graphical bars */} +
+ {distribution.map((count, idx) => { + const stars = 5 - idx + const pct = totalReviews > 0 ? (count / totalReviews) * 100 : 0 + return ( +
+ {stars} Star +
+
+
+ {count} +
+ ) + })} +
+
+ + {/* Write a review form */} +
+

Write an Agent Review

+
+
+ + setReviewName(e.target.value)} + className="form-input" + /> +
+
+ +
+ +
+
+
+
+ +