diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9c1b03b3460bed432baaab7957b8a2c891c12a5b..e07cc1c9cdaff34806c3fb8048589d00c85590e9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -31,4 +31,4 @@ jobs: - name: Run pre-commit run: | - make pre-commit + uv run pre-commit run --all-files --show-diff-on-failure diff --git a/Makefile b/Makefile index 8631b0a735872f3358de565f777c35138131a6e6..c2917eeef97e3980946345a98c2a0b5490493a30 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: sync setup install dev-backend dev-frontend dev clean docker-build docker-run docker-stop docker-clean docker-logs +.PHONY: sync setup install dev-backend dev-frontend dev clean # Sync all dependencies (Python + Node.js) sync: @@ -23,14 +23,6 @@ dev-frontend: pre-commit: uv run pre-commit run --all-files --show-diff-on-failure - make test - -# Run tests -test: - cd cua2-core && uv run pytest tests/ -v - -test-coverage: - cd cua2-core && uv run pytest tests/ -v --cov=cua2_core --cov-report=html --cov-report=term clean: find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true @@ -38,42 +30,3 @@ clean: find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true cd cua2-front && rm -rf node_modules dist 2>/dev/null || true @echo "✓ Cleaned!" - -# Docker commands -docker-build: - @echo "Building Docker image..." - make docker-stop - docker build -t cua2:latest . - @echo "✓ Docker image built successfully!" - -docker-run: - @echo "Starting CUA2 container..." - @if [ -z "$$E2B_API_KEY" ]; then \ - echo "Error: E2B_API_KEY environment variable is not set"; \ - echo "Please set it with: export E2B_API_KEY=your-key"; \ - exit 1; \ - fi - @if [ -z "$$HF_TOKEN" ]; then \ - echo "Error: HF_TOKEN environment variable is not set"; \ - echo "Please set it with: export HF_TOKEN=your-token"; \ - exit 1; \ - fi - docker run -d --name cua2-app -p 7860:7860 \ - -e E2B_API_KEY="$$E2B_API_KEY" \ - -e HF_TOKEN="$$HF_TOKEN" \ - cua2:latest - @echo "✓ Container started! Access at http://localhost:7860" - -docker-stop: - @echo "Stopping CUA2 container..." - docker stop cua2-app || true - docker rm cua2-app || true - @echo "✓ Container stopped!" - -docker-clean: - @echo "Removing CUA2 Docker images..." - docker rmi cua2:latest || true - @echo "✓ Docker images removed!" - -docker-logs: - docker logs -f cua2-app diff --git a/cua2-core/src/cua2_core/app.py b/cua2-core/src/cua2_core/app.py index 81ce4c7b725b3621920fe2410b10f6190d80e57b..d20f507f00f72fb00410ca56e311f8ea2a43579a 100644 --- a/cua2-core/src/cua2_core/app.py +++ b/cua2-core/src/cua2_core/app.py @@ -1,4 +1,3 @@ -import os from contextlib import asynccontextmanager from cua2_core.services.agent_service import AgentService @@ -18,9 +17,6 @@ async def lifespan(app: FastAPI): # Startup: Initialize services print("Initializing services...") - if not os.getenv("HF_TOKEN"): - raise ValueError("HF_TOKEN is not set") - websocket_manager = WebSocketManager() sandbox_service = SandboxService() diff --git a/cua2-core/src/cua2_core/websocket/websocket_manager.py b/cua2-core/src/cua2_core/websocket/websocket_manager.py index ab7dd622ae6479811c6c052b2513ab731f02d713..f6d64da4f31bb2c307ca865069c154773b982741 100644 --- a/cua2-core/src/cua2_core/websocket/websocket_manager.py +++ b/cua2-core/src/cua2_core/websocket/websocket_manager.py @@ -52,7 +52,7 @@ class WebSocketManager: try: await websocket.send_text( json.dumps( - message.model_dump(mode="json", context={"actions_as_json": False}) + message.model_dump(mode="json", context={"actions_as_json": True}) ) ) except Exception as e: diff --git a/cua2-front/package-lock.json b/cua2-front/package-lock.json index 3dec41c7cf4f73b6b2156cbdf4fd9a053210daff..d6510e87eb00ed29a2b5a90416930267cf589246 100644 --- a/cua2-front/package-lock.json +++ b/cua2-front/package-lock.json @@ -8,10 +8,17 @@ "name": "cua2-front", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.4", + "@mui/lab": "^7.0.1-beta.19", + "@mui/material": "^7.3.4", + "gifshot": "^0.4.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.30.1", - "ulid": "^3.0.1" + "ulid": "^3.0.1", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/js": "^9.38.0", @@ -28,6 +35,291 @@ "vite": "^5.4.19" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@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-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -628,6 +920,318 @@ "url": "https://github.com/sponsors/nzakas" } }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz", + "integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.4.tgz", + "integrity": "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.4", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "7.0.1-beta.19", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-7.0.1-beta.19.tgz", + "integrity": "sha512-Ekxd2mPnr5iKwrMXjN/y2xgpxPX8ithBBcDenjqNdBt/ZQumrmBl0ifVoqAHsL6lxN6DOgRsWTRc4eOdDiB+0Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/system": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^7.3.5", + "@mui/material-pigment-css": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", + "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/core-downloads-tracker": "^7.3.5", + "@mui/system": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz", + "integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz", + "integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz", + "integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/private-theming": "^7.3.5", + "@mui/styled-engine": "^7.3.5", + "@mui/types": "^7.4.8", + "@mui/utils": "^7.3.5", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz", + "integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz", + "integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/types": "^7.4.8", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -666,6 +1270,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -1240,18 +1854,22 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "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.26", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1268,6 +1886,15 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", @@ -1641,6 +2268,21 @@ "postcss": "^8.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1720,7 +2362,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1764,6 +2405,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1791,6 +2441,28 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1810,14 +2482,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "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" @@ -1838,6 +2508,16 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.237", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", @@ -1845,6 +2525,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1898,7 +2587,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2185,6 +2873,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2252,6 +2946,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gifshot": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/gifshot/-/gifshot-0.4.5.tgz", + "integrity": "sha512-oaOTT7patjxFFv7ptR0R0NNhqy3ZAmcLUQCjM/sTsvsQaUAlB2fHirLajcNAKJ6ufoVhdP+ZkXYvmUycHP1FNg==", + "license": "MIT" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2295,6 +3004,33 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2309,7 +3045,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -2332,6 +3067,27 @@ "node": ">=0.8.19" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2391,6 +3147,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2398,6 +3166,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2436,6 +3210,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2512,7 +3292,6 @@ "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": { @@ -2558,6 +3337,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2612,7 +3400,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -2621,6 +3408,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2641,11 +3446,25 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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/picomatch": { @@ -2707,6 +3526,23 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2763,6 +3599,12 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT" + }, "node_modules/react-router": { "version": "6.30.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", @@ -2795,11 +3637,46 @@ "react-dom": ">=16.8" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2927,6 +3804,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2950,6 +3836,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2963,6 +3855,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3184,6 +4088,15 @@ "node": ">=0.10.0" } }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -3196,6 +4109,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/cua2-front/package.json b/cua2-front/package.json index c6b760888606dc1e8a34e12aec1e9f0b6738a928..4804968edfce379a007a1ad17374232efb1529ac 100644 --- a/cua2-front/package.json +++ b/cua2-front/package.json @@ -12,10 +12,17 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.4", + "@mui/lab": "^7.0.1-beta.19", + "@mui/material": "^7.3.4", + "gifshot": "^0.4.5", "react": "^18.3.1", - "react-router-dom": "^6.30.1", "react-dom": "^18.3.1", - "ulid": "^3.0.1" + "react-router-dom": "^6.30.1", + "ulid": "^3.0.1", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/js": "^9.38.0", diff --git a/cua2-front/src/App.tsx b/cua2-front/src/App.tsx index 04acaae7159e60caa8f1adedeca332cf1dd2cafd..7ee68e26a677279a1efbdf1aa1ffef3c49f96044 100644 --- a/cua2-front/src/App.tsx +++ b/cua2-front/src/App.tsx @@ -1,14 +1,31 @@ -import React from 'react'; +import { useMemo } from 'react'; import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; - -const App = () => ( - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - - -); +import { ThemeProvider, CssBaseline } from '@mui/material'; +import getTheme from './theme'; +import Welcome from "./pages/Welcome"; +import Task from "./pages/Task"; +import { useAgentStore, selectIsDarkMode } from './stores/agentStore'; +import { useAgentWebSocket } from './hooks/useAgentWebSocket'; +import { config } from './config'; + +const App = () => { + const isDarkMode = useAgentStore(selectIsDarkMode); + const theme = useMemo(() => getTheme(isDarkMode ? 'dark' : 'light'), [isDarkMode]); + + // Initialize WebSocket connection at app level so it persists across route changes + useAgentWebSocket({ url: config.wsUrl }); + + return ( + + + + + } /> + } /> + + + + ); +}; export default App; diff --git a/cua2-front/src/components/ConnectionStatus.tsx b/cua2-front/src/components/ConnectionStatus.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7bfbd8f28b0fc1e5a4fa4e971a21b847a936b74a --- /dev/null +++ b/cua2-front/src/components/ConnectionStatus.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Box, Chip, keyframes } from '@mui/material'; +import CircleIcon from '@mui/icons-material/Circle'; + +interface ConnectionStatusProps { + isConnected: boolean; +} + +// Pulse animation for connected indicator +const pulse = keyframes` + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +`; + +export const ConnectionStatus: React.FC = ({ isConnected }) => { + return ( + + } + onDelete={() => {}} // Required for deleteIcon to show + size="small" + sx={{ + backgroundColor: 'action.hover', + border: '1px solid', + borderColor: 'divider', + color: 'text.primary', + fontSize: '0.7rem', + fontWeight: 500, + height: 'auto', + '& .MuiChip-label': { + px: 1, + py: 0.5, + }, + '& .MuiChip-deleteIcon': { + color: isConnected ? '#10b981' : '#ef4444', + marginRight: 0.5, + '&:hover': { + color: isConnected ? '#10b981' : '#ef4444', + }, + }, + }} + /> + ); +}; diff --git a/cua2-front/src/components/Header.tsx b/cua2-front/src/components/Header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..14ed56898ebf5dcb51956458f63c2ca81ca8c19a --- /dev/null +++ b/cua2-front/src/components/Header.tsx @@ -0,0 +1,409 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { AppBar, Toolbar, Box, Typography, Chip, IconButton, CircularProgress, keyframes } from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import LightModeOutlined from '@mui/icons-material/LightModeOutlined'; +import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import InputIcon from '@mui/icons-material/Input'; +import OutputIcon from '@mui/icons-material/Output'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; +import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; +import { useAgentStore, selectTrace, selectError, selectIsDarkMode, selectMetadata, selectIsConnectingToE2B, selectFinalStep } from '@/stores/agentStore'; + +interface HeaderProps { + isAgentProcessing: boolean; + onBackToHome?: () => void; +} + +// Animation for the running task border - smooth oscillation (primary) +const borderPulse = keyframes` + 0%, 100% { + border-color: rgba(79, 134, 198, 0.5); + box-shadow: 0 0 0 0 rgba(79, 134, 198, 0.3); + } + 50% { + border-color: rgba(79, 134, 198, 1); + box-shadow: 0 0 8px 2px rgba(79, 134, 198, 0.4); + } +`; + +// Animation for the background glow (primary) +const backgroundPulse = keyframes` + 0%, 100% { + background-color: rgba(79, 134, 198, 0.08); + } + 50% { + background-color: rgba(79, 134, 198, 0.15); + } +`; + +// Animation for token flash - smooth glow effect +const tokenFlash = keyframes` + 0% { + filter: brightness(1); + text-shadow: none; + } + 25% { + filter: brightness(1.4); + text-shadow: 0 0 8px rgba(79, 134, 198, 0.6); + } + 100% { + filter: brightness(1); + text-shadow: none; + } +`; + +// Animation for token icon flash +const iconFlash = keyframes` + 0% { + filter: brightness(1); + transform: scale(1); + } + 25% { + filter: brightness(1.6); + transform: scale(1.15); + } + 100% { + filter: brightness(1); + transform: scale(1); + } +`; + +export const Header: React.FC = ({ isAgentProcessing, onBackToHome }) => { + const trace = useAgentStore(selectTrace); + const error = useAgentStore(selectError); + const finalStep = useAgentStore(selectFinalStep); + const isDarkMode = useAgentStore(selectIsDarkMode); + const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode); + const metadata = useAgentStore(selectMetadata); + const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B); + const [elapsedTime, setElapsedTime] = useState(0); + const [inputTokenFlash, setInputTokenFlash] = useState(false); + const [outputTokenFlash, setOutputTokenFlash] = useState(false); + const prevInputTokens = useRef(0); + const prevOutputTokens = useRef(0); + + // Update elapsed time every 100ms when agent is processing + useEffect(() => { + if (isAgentProcessing && trace?.timestamp) { + const interval = setInterval(() => { + const now = new Date(); + const startTime = new Date(trace.timestamp); + const elapsed = (now.getTime() - startTime.getTime()) / 1000; + setElapsedTime(elapsed); + }, 100); + + return () => clearInterval(interval); + } else if (metadata && metadata.duration > 0) { + setElapsedTime(metadata.duration); + } + }, [isAgentProcessing, trace?.timestamp, metadata]); + + // Detect token changes and trigger flash animation + useEffect(() => { + if (metadata) { + // Input tokens changed + if (metadata.inputTokensUsed > prevInputTokens.current && prevInputTokens.current > 0) { + setInputTokenFlash(true); + setTimeout(() => setInputTokenFlash(false), 800); + } + prevInputTokens.current = metadata.inputTokensUsed; + + // Output tokens changed + if (metadata.outputTokensUsed > prevOutputTokens.current && prevOutputTokens.current > 0) { + setOutputTokenFlash(true); + setTimeout(() => setOutputTokenFlash(false), 800); + } + prevOutputTokens.current = metadata.outputTokensUsed; + } + }, [metadata?.inputTokensUsed, metadata?.outputTokensUsed]); + + // Determine task status - Use finalStep as source of truth + const getTaskStatus = () => { + // If we have a final step, use its type + if (finalStep) { + if (finalStep.type === 'failure') { + return { label: 'Task failed', color: 'error', icon: }; + } + return { label: 'Completed', color: 'success', icon: }; + } + // Otherwise check running states + if (isConnectingToE2B) return { label: 'Connecting to E2B...', color: 'primary', icon: }; + if (isAgentProcessing || trace?.isRunning) return { label: 'Running', color: 'primary', icon: }; + return { label: 'Ready', color: 'default', icon: }; + }; + + const taskStatus = getTaskStatus(); + + // Extract model name from modelId (e.g., "Qwen/Qwen3-VL-8B-Instruct" -> "Qwen3-VL-8B-Instruct") + const modelName = trace?.modelId?.split('/').pop() || 'Unknown Model'; + + return ( + + + {/* First row: Back button + Task info + Connection Status */} + + {/* Left side: Back button + Task info */} + + + + + + {trace?.instruction || 'No task running'} + + + + {/* Right side: Dark Mode */} + + + {isDarkMode ? : } + + + + + {/* Second row: Status + Model + Metadata - Only show when we have trace data */} + {trace && ( + + {/* Status Badge - Compact */} + + {taskStatus.icon} + + {taskStatus.label} + + + + {/* Divider */} + + + {/* Model */} + + + + {modelName} + + + + {/* Steps Count */} + {metadata && ( + <> + + + + {metadata.numberOfSteps} + + + {metadata.numberOfSteps === 1 ? 'Step' : 'Steps'} + + + + )} + + {/* Time */} + {(isAgentProcessing || metadata) && ( + <> + + + + + {elapsedTime.toFixed(1)}s + + + + )} + + {/* Input Tokens */} + {metadata && metadata.inputTokensUsed > 0 && ( + <> + + + + + + {metadata.inputTokensUsed.toLocaleString()} + + + + + )} + + {/* Output Tokens */} + {metadata && metadata.outputTokensUsed > 0 && ( + <> + + + + + + {metadata.outputTokensUsed.toLocaleString()} + + + + + )} + + )} + + + ); +}; diff --git a/cua2-front/src/components/ProcessingIndicator.tsx b/cua2-front/src/components/ProcessingIndicator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0346df6d21405a73abe8b0274e4fcb6888e35e13 --- /dev/null +++ b/cua2-front/src/components/ProcessingIndicator.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Box, CircularProgress, Typography } from '@mui/material'; + +interface ProcessingIndicatorProps { + isAgentProcessing: boolean; +} + +export const ProcessingIndicator: React.FC = ({ isAgentProcessing }) => { + if (!isAgentProcessing) return null; + + return ( + + + + Agent is running... + + + ); +}; diff --git a/cua2-front/src/components/WelcomeScreen.tsx b/cua2-front/src/components/WelcomeScreen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..eb64e22683f1ac546370e0befce7be5daff6b519 --- /dev/null +++ b/cua2-front/src/components/WelcomeScreen.tsx @@ -0,0 +1,452 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Box, Typography, Button, Container, Paper, TextField, IconButton, Select, MenuItem, FormControl, InputLabel, CircularProgress } from '@mui/material'; +import ShuffleIcon from '@mui/icons-material/Shuffle'; +import SendIcon from '@mui/icons-material/Send'; +import LightModeOutlined from '@mui/icons-material/LightModeOutlined'; +import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import { useAgentStore, selectSelectedModelId, selectIsDarkMode, selectAvailableModels, selectIsLoadingModels } from '@/stores/agentStore'; +import { fetchAvailableModels, generateRandomQuestion } from '@/services/api'; + +interface WelcomeScreenProps { + onStartTask: (instruction: string, modelId: string) => void; + isConnected: boolean; +} + +export const WelcomeScreen: React.FC = ({ onStartTask, isConnected }) => { + const [customTask, setCustomTask] = useState(''); + const [isTyping, setIsTyping] = useState(false); + const [isGeneratingQuestion, setIsGeneratingQuestion] = useState(false); + const typingIntervalRef = useRef(null); + + const isDarkMode = useAgentStore(selectIsDarkMode); + const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode); + const selectedModelId = useAgentStore(selectSelectedModelId); + const setSelectedModelId = useAgentStore((state) => state.setSelectedModelId); + const availableModels = useAgentStore(selectAvailableModels); + const isLoadingModels = useAgentStore(selectIsLoadingModels); + const setAvailableModels = useAgentStore((state) => state.setAvailableModels); + const setIsLoadingModels = useAgentStore((state) => state.setIsLoadingModels); + + // Load available models on mount + useEffect(() => { + const loadModels = async () => { + setIsLoadingModels(true); + try { + const models = await fetchAvailableModels(); + setAvailableModels(models); + + // Set first model as default if current selection is not in the list + if (models.length > 0 && !models.includes(selectedModelId)) { + setSelectedModelId(models[0]); + } + } catch (error) { + console.error('Failed to load models:', error); + // Fallback to empty array on error + setAvailableModels([]); + } finally { + setIsLoadingModels(false); + } + }; + + loadModels(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Clean up typing interval on unmount + useEffect(() => { + return () => { + if (typingIntervalRef.current) { + clearInterval(typingIntervalRef.current); + } + }; + }, []); + + const handleWriteRandomTask = async () => { + // Clear any existing typing interval + if (typingIntervalRef.current) { + clearInterval(typingIntervalRef.current); + typingIntervalRef.current = null; + } + + setIsGeneratingQuestion(true); + try { + const randomTask = await generateRandomQuestion(selectedModelId); + + // Clear current text + setCustomTask(''); + setIsTyping(true); + + // Type effect + let currentIndex = 0; + typingIntervalRef.current = setInterval(() => { + if (currentIndex < randomTask.length) { + setCustomTask(randomTask.substring(0, currentIndex + 1)); + currentIndex++; + } else { + if (typingIntervalRef.current) { + clearInterval(typingIntervalRef.current); + typingIntervalRef.current = null; + } + setIsTyping(false); + } + }, 30); // 30ms per character + } catch (error) { + console.error('Failed to generate question:', error); + setIsTyping(false); + } finally { + setIsGeneratingQuestion(false); + } + }; + + const handleCustomTask = () => { + if (customTask.trim() && !isTyping) { + onStartTask(customTask.trim(), selectedModelId); + } + }; + + return ( + <> + {/* Dark Mode Toggle - Top Right (Absolute to viewport) */} + + + {isDarkMode ? : } + + + + + {/* Title */} + + CUA2 Agent + + + {/* Powered by smolagents */} + + + Powered by + + + {/* Hugging Face Official Logo */} + + + + smolagents + + + {/* GitHub stars badge */} + + theme.palette.mode === 'dark' + ? 'rgba(144, 202, 249, 0.08)' + : 'rgba(25, 118, 210, 0.08)', + borderRadius: 1, + border: '1px solid', + borderColor: 'primary.main', + }} + > + + + 23.7k + + + + + + {/* Subtitle */} + + AI-Powered Computer Use Automation + + + {/* Description */} + + Watch in real-time as AI agents write and execute Python code to complete tasks. + Built by Hugging Face, smolagents is LLM-agnostic and uses 30% fewer steps than traditional agents. + + + {/* Task Input Section */} + `0 4px 16px ${theme.palette.mode === 'dark' ? 'rgba(79, 134, 198, 0.3)' : 'rgba(79, 134, 198, 0.15)'}`, + } : {}, + }} + > + {/* Input Field */} + setCustomTask(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter' && !e.shiftKey && isConnected && customTask.trim() && !isTyping) { + handleCustomTask(); + } + }} + disabled={!isConnected || isTyping} + multiline + rows={3} + sx={{ + mb: 2, + '& .MuiOutlinedInput-root': { + borderRadius: 1.5, + backgroundColor: 'action.hover', + color: 'text.primary', + '& fieldset': { + borderColor: 'divider', + }, + '&:hover fieldset': { + borderColor: 'text.secondary', + }, + '&.Mui-focused fieldset': { + borderColor: 'primary.main', + borderWidth: '2px', + }, + }, + '& .MuiInputBase-input': { + color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', + fontWeight: 500, + WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', + }, + '& .MuiInputBase-input.Mui-disabled': { + color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', + WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important', + }, + '& .MuiInputBase-input::placeholder': { + color: 'text.secondary', + opacity: 0.7, + }, + }} + /> + + {/* Model Selection + Buttons Row */} + + {/* Model Select */} + + Model + + + + {/* Buttons on the right */} + + + + + + + + + {/* Connection status hint */} + {!isConnected && ( + + + Make sure the backend is running on port 8000 + + )} + + + ); +}; diff --git a/cua2-front/src/components/index.ts b/cua2-front/src/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1bfc970d05a4af4d8f5c43a5d37f40f9e472d969 --- /dev/null +++ b/cua2-front/src/components/index.ts @@ -0,0 +1,14 @@ +// General components +export { Header } from './Header'; +export { ConnectionStatus } from './ConnectionStatus'; +export { ProcessingIndicator } from './ProcessingIndicator'; +export { WelcomeScreen } from './WelcomeScreen'; + +// Sandbox components +export { SandboxViewer, CompletionView, DownloadGifButton, DownloadJsonButton } from './sandbox'; + +// Timeline components +export { Timeline } from './timeline'; + +// Steps components +export { StepsList, StepCard, FinalStepCard, ThinkingStepCard, ConnectionStepCard } from './steps'; diff --git a/cua2-front/src/components/mock/ConnectionStatus.tsx b/cua2-front/src/components/mock/ConnectionStatus.tsx deleted file mode 100644 index 7c8bc50ea2ca843ea613d7295ba2813c42f9229b..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/ConnectionStatus.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -interface ConnectionStatusProps { - isConnected: boolean; -} - -export const ConnectionStatus: React.FC = ({ isConnected }) => { - return ( -
-
-
- - {isConnected ? 'Connected' : 'Disconnected'} - - - WebSocket - -
-
- ); -}; diff --git a/cua2-front/src/components/mock/Header.tsx b/cua2-front/src/components/mock/Header.tsx deleted file mode 100644 index 9c9067f35206719cd04381903387d6b082bf63f8..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/Header.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { ConnectionStatus } from './ConnectionStatus'; -import { ProcessingIndicator } from './ProcessingIndicator'; -import { TaskButton } from './TaskButton'; - -interface HeaderProps { - isConnected: boolean; - isAgentProcessing: boolean; - onSendTask: (content: string, modelId: string) => void; -} - -export const Header: React.FC = ({ isConnected, isAgentProcessing, onSendTask }) => { - return ( - <> -
-
-
-
- -

- CUA2 Agent -

-
- -
- -
-
- - - - ); -}; diff --git a/cua2-front/src/components/mock/Metadata.tsx b/cua2-front/src/components/mock/Metadata.tsx deleted file mode 100644 index 9bd1d95db52bb7b1879b0d0f2484a054508fd437..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/Metadata.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { AgentTrace } from '@/types/agent'; -import React from 'react'; - -interface MetadataProps { - trace?: AgentTrace; -} - -export const Metadata: React.FC = ({ trace }) => { - return ( -
-

Metadata

- {trace?.metadata ? ( -
-
- Total Time - {trace.metadata.duration.toFixed(2)}s -
-
- In Tokens - {trace.metadata.inputTokensUsed.toLocaleString()} -
-
- Out Tokens - {trace.metadata.outputTokensUsed.toLocaleString()} -
-
- Total Steps - {trace.metadata.numberOfSteps} -
-
- ) : ( -
- {trace ? 'Waiting for completion...' : 'No task started yet'} -
- )} -
- ); -}; diff --git a/cua2-front/src/components/mock/ProcessingIndicator.tsx b/cua2-front/src/components/mock/ProcessingIndicator.tsx deleted file mode 100644 index ad4c0761819d4fe9b47b718d63324cc64b3fb400..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/ProcessingIndicator.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -interface ProcessingIndicatorProps { - isAgentProcessing: boolean; -} - -export const ProcessingIndicator: React.FC = ({ isAgentProcessing }) => { - if (!isAgentProcessing) return null; - - return ( -
- - - PROCESSING... - -
- ); -}; diff --git a/cua2-front/src/components/mock/StackSteps.tsx b/cua2-front/src/components/mock/StackSteps.tsx deleted file mode 100644 index 9fbf768552b20222f0022f35a01981a8290e9c1f..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/StackSteps.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { AgentTrace } from '@/types/agent'; -import { StepCard } from './StepCard'; - -interface StackStepsProps { - trace?: AgentTrace; -} - -export const StackSteps: React.FC = ({ trace }) => { - return ( -
-

Stack Steps

-
- {trace?.steps && trace.steps.length > 0 ? ( -
- {trace.steps.map((step, index) => ( - - ))} -
- ) : ( -
-

No steps yet

-

Steps will appear as agent progresses

-
- )} -
-
- ); -}; diff --git a/cua2-front/src/components/mock/StepCard.tsx b/cua2-front/src/components/mock/StepCard.tsx deleted file mode 100644 index fc74b3eafb1127bf7da424931f077787d02bad73..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/StepCard.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { AgentStep } from '@/types/agent'; -import React from 'react'; - -interface StepCardProps { - step: AgentStep; - index: number; -} - -export const StepCard: React.FC = ({ step, index }) => { - return ( -
- {/* Step Header */} -
- Step {index + 1} -
-
- - {/* Step Image */} - {step.image && ( -
-
- {`Step -
-
-
- )} - - {/* Thought */} -
-
-

- 💭 - Thought -

-

{step.thought}

-
-
-
- - {/* Actions */} -
-
-

- - Actions -

-
    - {step.actions.map((action, actionIndex) => ( -
  • - - {action} -
  • - ))} -
-
-
-
- - {/* Step Metadata Footer */} -
-
- Time - {step.duration.toFixed(2)}s -
-
- In Tokens - {step.inputTokensUsed} -
-
- Out Tokens - {step.outputTokensUsed} -
-
-
- ); -}; diff --git a/cua2-front/src/components/mock/TaskButton.tsx b/cua2-front/src/components/mock/TaskButton.tsx deleted file mode 100644 index 34a8d98854028f26ec021e9ea042e54ceb3dae94..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/TaskButton.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; - -interface TaskButtonProps { - isAgentProcessing: boolean; - isConnected: boolean; - onSendTask: (content: string, modelId: string) => void; -} - -export const TaskButton: React.FC = ({ isAgentProcessing, isConnected, onSendTask }) => { - return ( -
{ - if (!isAgentProcessing && isConnected) { - onSendTask( - "Find the price of a NVIDIA RTX 4090 GPU", - "Qwen/Qwen3-VL-30B-A3B-Instruct" - ); - } - }} - style={{ - marginTop: '16px', - padding: '14px 18px', - background: isAgentProcessing || !isConnected - ? 'rgba(255, 255, 255, 0.1)' - : 'rgba(255, 255, 255, 0.15)', - borderRadius: '10px', - backdropFilter: 'blur(10px)', - border: '2px solid rgba(0, 0, 0, 0.3)', - cursor: isAgentProcessing || !isConnected ? 'not-allowed' : 'pointer', - transition: 'all 0.3s ease', - opacity: isAgentProcessing || !isConnected ? 0.6 : 1, - }} - onMouseEnter={(e) => { - if (!isAgentProcessing && isConnected) { - e.currentTarget.style.background = 'rgba(200, 200, 200, 0.3)'; - e.currentTarget.style.borderColor = 'rgba(0, 0, 0, 0.5)'; - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.2)'; - } - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)'; - e.currentTarget.style.borderColor = 'rgba(0, 0, 0, 0.3)'; - e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = 'none'; - }} - > -
-
-
- Task - {!isAgentProcessing && isConnected && ( - - (click to run) - - )} -
-

- Find the price of a NVIDIA RTX 4090 GPU -

-
-
- Model -

- Qwen/Qwen3-VL-30B-A3B-Instruct -

-
-
-
- ); -}; diff --git a/cua2-front/src/components/mock/VNCStream.tsx b/cua2-front/src/components/mock/VNCStream.tsx deleted file mode 100644 index b69a820fdb5a9e6fccec2fcbcde8d240bd08a43e..0000000000000000000000000000000000000000 --- a/cua2-front/src/components/mock/VNCStream.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -interface VNCStreamProps { - vncUrl: string; -} - -export const VNCStream: React.FC = ({ vncUrl }) => { - return ( -
-

VNC Stream

-
- {vncUrl ? ( -