Spaces:
Running
Front update (#5)
Browse files* Initial commit - sauvegarde du frontend actuel
* Update backend (cua2-core) from origin/main - frontend preserved
* first front commit
* update spinner in timeline
* update front
* update front
* Fix linting issues: ESLint TypeScript errors and codespell warnings
- Fix lexical declaration in case block (useAgentWebSocket)
- Replace 'any' types with proper types (Theme, unknown, Window interface)
- Translate French comments to English for codespell compliance
- Remove trailing whitespace and fix end of files
* Refactor: rename components for better clarity
- Rename StackSteps → StepsList (more descriptive)
- Rename TraceMetadata → Timeline (reflects actual purpose)
- Rename OSStream → SandboxViewer (clearer naming)
- Remove unused mock/ directory
- Update all imports and exports accordingly
* Refactor: reorganize component structure
- Rename stream/ → sandbox/ (more descriptive)
- Create completionview/ subdirectory
- Move CompletionView and its buttons into sandbox/completionview/
- Update all imports and exports accordingly
* Fix: backend sends actions as JSON objects
- Change actions_as_json from False to True in websocket_manager
- This ensures frontend receives actions as JSON objects instead of strings
- Fixes action display in StepCard component
- .github/workflows/pre-commit.yml +1 -1
- Makefile +1 -48
- cua2-core/src/cua2_core/app.py +0 -4
- cua2-core/src/cua2_core/websocket/websocket_manager.py +1 -1
- cua2-front/package-lock.json +954 -12
- cua2-front/package.json +9 -2
- cua2-front/src/App.tsx +28 -11
- cua2-front/src/components/ConnectionStatus.tsx +55 -0
- cua2-front/src/components/Header.tsx +409 -0
- cua2-front/src/components/ProcessingIndicator.tsx +31 -0
- cua2-front/src/components/WelcomeScreen.tsx +452 -0
- cua2-front/src/components/index.ts +14 -0
- cua2-front/src/components/mock/ConnectionStatus.tsx +0 -37
- cua2-front/src/components/mock/Header.tsx +0 -47
- cua2-front/src/components/mock/Metadata.tsx +0 -38
- cua2-front/src/components/mock/ProcessingIndicator.tsx +0 -34
- cua2-front/src/components/mock/StackSteps.tsx +0 -29
- cua2-front/src/components/mock/StepCard.tsx +0 -84
- cua2-front/src/components/mock/TaskButton.tsx +0 -76
- cua2-front/src/components/mock/VNCStream.tsx +0 -30
- cua2-front/src/components/mock/index.ts +0 -8
- cua2-front/src/components/sandbox/SandboxViewer.tsx +367 -0
- cua2-front/src/components/sandbox/completionview/CompletionView.tsx +368 -0
- cua2-front/src/components/sandbox/completionview/DownloadGifButton.tsx +64 -0
- cua2-front/src/components/sandbox/completionview/DownloadJsonButton.tsx +56 -0
- cua2-front/src/components/sandbox/completionview/index.ts +3 -0
- cua2-front/src/components/sandbox/index.ts +2 -0
- cua2-front/src/components/steps/ConnectionStepCard.tsx +110 -0
- cua2-front/src/components/steps/FinalStepCard.tsx +70 -0
- cua2-front/src/components/steps/StepCard.tsx +358 -0
- cua2-front/src/components/steps/StepsList.tsx +388 -0
- cua2-front/src/components/steps/ThinkingStepCard.tsx +98 -0
- cua2-front/src/components/steps/index.ts +5 -0
- cua2-front/src/components/timeline/Timeline.tsx +413 -0
- cua2-front/src/components/timeline/index.ts +1 -0
- cua2-front/src/config.ts +11 -0
- cua2-front/src/hooks/index.ts +5 -0
- cua2-front/src/hooks/useAgentWebSocket.ts +165 -0
- cua2-front/src/hooks/useGifGenerator.ts +86 -0
- cua2-front/src/hooks/useJsonExporter.ts +41 -0
- cua2-front/src/hooks/useSendTask.ts +14 -0
- cua2-front/src/index.css +1 -10
- cua2-front/src/pages/Task.tsx +123 -0
- cua2-front/src/pages/Welcome.tsx +35 -0
- cua2-front/src/services/api.ts +56 -0
- cua2-front/src/services/gifGenerator.ts +168 -0
- cua2-front/src/services/index.ts +3 -0
- cua2-front/src/services/jsonExporter.ts +58 -0
- cua2-front/src/stores/agentStore.ts +251 -0
- cua2-front/src/theme.ts +397 -0
|
@@ -31,4 +31,4 @@ jobs:
|
|
| 31 |
|
| 32 |
- name: Run pre-commit
|
| 33 |
run: |
|
| 34 |
-
|
|
|
|
| 31 |
|
| 32 |
- name: Run pre-commit
|
| 33 |
run: |
|
| 34 |
+
uv run pre-commit run --all-files --show-diff-on-failure
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
.PHONY: sync setup install dev-backend dev-frontend dev clean
|
| 2 |
|
| 3 |
# Sync all dependencies (Python + Node.js)
|
| 4 |
sync:
|
|
@@ -23,14 +23,6 @@ dev-frontend:
|
|
| 23 |
|
| 24 |
pre-commit:
|
| 25 |
uv run pre-commit run --all-files --show-diff-on-failure
|
| 26 |
-
make test
|
| 27 |
-
|
| 28 |
-
# Run tests
|
| 29 |
-
test:
|
| 30 |
-
cd cua2-core && uv run pytest tests/ -v
|
| 31 |
-
|
| 32 |
-
test-coverage:
|
| 33 |
-
cd cua2-core && uv run pytest tests/ -v --cov=cua2_core --cov-report=html --cov-report=term
|
| 34 |
|
| 35 |
clean:
|
| 36 |
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
|
@@ -38,42 +30,3 @@ clean:
|
|
| 38 |
find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
|
| 39 |
cd cua2-front && rm -rf node_modules dist 2>/dev/null || true
|
| 40 |
@echo "✓ Cleaned!"
|
| 41 |
-
|
| 42 |
-
# Docker commands
|
| 43 |
-
docker-build:
|
| 44 |
-
@echo "Building Docker image..."
|
| 45 |
-
make docker-stop
|
| 46 |
-
docker build -t cua2:latest .
|
| 47 |
-
@echo "✓ Docker image built successfully!"
|
| 48 |
-
|
| 49 |
-
docker-run:
|
| 50 |
-
@echo "Starting CUA2 container..."
|
| 51 |
-
@if [ -z "$$E2B_API_KEY" ]; then \
|
| 52 |
-
echo "Error: E2B_API_KEY environment variable is not set"; \
|
| 53 |
-
echo "Please set it with: export E2B_API_KEY=your-key"; \
|
| 54 |
-
exit 1; \
|
| 55 |
-
fi
|
| 56 |
-
@if [ -z "$$HF_TOKEN" ]; then \
|
| 57 |
-
echo "Error: HF_TOKEN environment variable is not set"; \
|
| 58 |
-
echo "Please set it with: export HF_TOKEN=your-token"; \
|
| 59 |
-
exit 1; \
|
| 60 |
-
fi
|
| 61 |
-
docker run -d --name cua2-app -p 7860:7860 \
|
| 62 |
-
-e E2B_API_KEY="$$E2B_API_KEY" \
|
| 63 |
-
-e HF_TOKEN="$$HF_TOKEN" \
|
| 64 |
-
cua2:latest
|
| 65 |
-
@echo "✓ Container started! Access at http://localhost:7860"
|
| 66 |
-
|
| 67 |
-
docker-stop:
|
| 68 |
-
@echo "Stopping CUA2 container..."
|
| 69 |
-
docker stop cua2-app || true
|
| 70 |
-
docker rm cua2-app || true
|
| 71 |
-
@echo "✓ Container stopped!"
|
| 72 |
-
|
| 73 |
-
docker-clean:
|
| 74 |
-
@echo "Removing CUA2 Docker images..."
|
| 75 |
-
docker rmi cua2:latest || true
|
| 76 |
-
@echo "✓ Docker images removed!"
|
| 77 |
-
|
| 78 |
-
docker-logs:
|
| 79 |
-
docker logs -f cua2-app
|
|
|
|
| 1 |
+
.PHONY: sync setup install dev-backend dev-frontend dev clean
|
| 2 |
|
| 3 |
# Sync all dependencies (Python + Node.js)
|
| 4 |
sync:
|
|
|
|
| 23 |
|
| 24 |
pre-commit:
|
| 25 |
uv run pre-commit run --all-files --show-diff-on-failure
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
clean:
|
| 28 |
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
|
|
|
| 30 |
find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
|
| 31 |
cd cua2-front && rm -rf node_modules dist 2>/dev/null || true
|
| 32 |
@echo "✓ Cleaned!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import os
|
| 2 |
from contextlib import asynccontextmanager
|
| 3 |
|
| 4 |
from cua2_core.services.agent_service import AgentService
|
|
@@ -18,9 +17,6 @@ async def lifespan(app: FastAPI):
|
|
| 18 |
# Startup: Initialize services
|
| 19 |
print("Initializing services...")
|
| 20 |
|
| 21 |
-
if not os.getenv("HF_TOKEN"):
|
| 22 |
-
raise ValueError("HF_TOKEN is not set")
|
| 23 |
-
|
| 24 |
websocket_manager = WebSocketManager()
|
| 25 |
|
| 26 |
sandbox_service = SandboxService()
|
|
|
|
|
|
|
| 1 |
from contextlib import asynccontextmanager
|
| 2 |
|
| 3 |
from cua2_core.services.agent_service import AgentService
|
|
|
|
| 17 |
# Startup: Initialize services
|
| 18 |
print("Initializing services...")
|
| 19 |
|
|
|
|
|
|
|
|
|
|
| 20 |
websocket_manager = WebSocketManager()
|
| 21 |
|
| 22 |
sandbox_service = SandboxService()
|
|
@@ -52,7 +52,7 @@ class WebSocketManager:
|
|
| 52 |
try:
|
| 53 |
await websocket.send_text(
|
| 54 |
json.dumps(
|
| 55 |
-
message.model_dump(mode="json", context={"actions_as_json":
|
| 56 |
)
|
| 57 |
)
|
| 58 |
except Exception as e:
|
|
|
|
| 52 |
try:
|
| 53 |
await websocket.send_text(
|
| 54 |
json.dumps(
|
| 55 |
+
message.model_dump(mode="json", context={"actions_as_json": True})
|
| 56 |
)
|
| 57 |
)
|
| 58 |
except Exception as e:
|
|
@@ -8,10 +8,17 @@
|
|
| 8 |
"name": "cua2-front",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"react": "^18.3.1",
|
| 12 |
"react-dom": "^18.3.1",
|
| 13 |
"react-router-dom": "^6.30.1",
|
| 14 |
-
"ulid": "^3.0.1"
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@eslint/js": "^9.38.0",
|
|
@@ -28,6 +35,291 @@
|
|
| 28 |
"vite": "^5.4.19"
|
| 29 |
}
|
| 30 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
"node_modules/@esbuild/aix-ppc64": {
|
| 32 |
"version": "0.21.5",
|
| 33 |
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
|
@@ -628,6 +920,318 @@
|
|
| 628 |
"url": "https://github.com/sponsors/nzakas"
|
| 629 |
}
|
| 630 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 631 |
"node_modules/@nodelib/fs.scandir": {
|
| 632 |
"version": "2.1.5",
|
| 633 |
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
|
@@ -666,6 +1270,16 @@
|
|
| 666 |
"node": ">= 8"
|
| 667 |
}
|
| 668 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
"node_modules/@remix-run/router": {
|
| 670 |
"version": "1.23.0",
|
| 671 |
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
|
@@ -1240,18 +1854,22 @@
|
|
| 1240 |
"undici-types": "~6.21.0"
|
| 1241 |
}
|
| 1242 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1243 |
"node_modules/@types/prop-types": {
|
| 1244 |
"version": "15.7.15",
|
| 1245 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
| 1246 |
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
| 1247 |
-
"dev": true,
|
| 1248 |
"license": "MIT"
|
| 1249 |
},
|
| 1250 |
"node_modules/@types/react": {
|
| 1251 |
"version": "18.3.26",
|
| 1252 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
| 1253 |
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
| 1254 |
-
"dev": true,
|
| 1255 |
"license": "MIT",
|
| 1256 |
"dependencies": {
|
| 1257 |
"@types/prop-types": "*",
|
|
@@ -1268,6 +1886,15 @@
|
|
| 1268 |
"@types/react": "^18.0.0"
|
| 1269 |
}
|
| 1270 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1271 |
"node_modules/@typescript-eslint/eslint-plugin": {
|
| 1272 |
"version": "8.46.1",
|
| 1273 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz",
|
|
@@ -1641,6 +2268,21 @@
|
|
| 1641 |
"postcss": "^8.1.0"
|
| 1642 |
}
|
| 1643 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1644 |
"node_modules/balanced-match": {
|
| 1645 |
"version": "1.0.2",
|
| 1646 |
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
@@ -1720,7 +2362,6 @@
|
|
| 1720 |
"version": "3.1.0",
|
| 1721 |
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
| 1722 |
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
| 1723 |
-
"dev": true,
|
| 1724 |
"license": "MIT",
|
| 1725 |
"engines": {
|
| 1726 |
"node": ">=6"
|
|
@@ -1764,6 +2405,15 @@
|
|
| 1764 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 1765 |
}
|
| 1766 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1767 |
"node_modules/color-convert": {
|
| 1768 |
"version": "2.0.1",
|
| 1769 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
@@ -1791,6 +2441,28 @@
|
|
| 1791 |
"dev": true,
|
| 1792 |
"license": "MIT"
|
| 1793 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1794 |
"node_modules/cross-spawn": {
|
| 1795 |
"version": "7.0.6",
|
| 1796 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
|
@@ -1810,14 +2482,12 @@
|
|
| 1810 |
"version": "3.1.3",
|
| 1811 |
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 1812 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
| 1813 |
-
"dev": true,
|
| 1814 |
"license": "MIT"
|
| 1815 |
},
|
| 1816 |
"node_modules/debug": {
|
| 1817 |
"version": "4.4.3",
|
| 1818 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1819 |
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1820 |
-
"dev": true,
|
| 1821 |
"license": "MIT",
|
| 1822 |
"dependencies": {
|
| 1823 |
"ms": "^2.1.3"
|
|
@@ -1838,6 +2508,16 @@
|
|
| 1838 |
"dev": true,
|
| 1839 |
"license": "MIT"
|
| 1840 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1841 |
"node_modules/electron-to-chromium": {
|
| 1842 |
"version": "1.5.237",
|
| 1843 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
|
|
@@ -1845,6 +2525,15 @@
|
|
| 1845 |
"dev": true,
|
| 1846 |
"license": "ISC"
|
| 1847 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1848 |
"node_modules/esbuild": {
|
| 1849 |
"version": "0.21.5",
|
| 1850 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
@@ -1898,7 +2587,6 @@
|
|
| 1898 |
"version": "4.0.0",
|
| 1899 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 1900 |
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
| 1901 |
-
"dev": true,
|
| 1902 |
"license": "MIT",
|
| 1903 |
"engines": {
|
| 1904 |
"node": ">=10"
|
|
@@ -2185,6 +2873,12 @@
|
|
| 2185 |
"node": ">=8"
|
| 2186 |
}
|
| 2187 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2188 |
"node_modules/find-up": {
|
| 2189 |
"version": "5.0.0",
|
| 2190 |
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
|
@@ -2252,6 +2946,21 @@
|
|
| 2252 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 2253 |
}
|
| 2254 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2255 |
"node_modules/glob-parent": {
|
| 2256 |
"version": "6.0.2",
|
| 2257 |
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
|
@@ -2295,6 +3004,33 @@
|
|
| 2295 |
"node": ">=8"
|
| 2296 |
}
|
| 2297 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2298 |
"node_modules/ignore": {
|
| 2299 |
"version": "5.3.2",
|
| 2300 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
@@ -2309,7 +3045,6 @@
|
|
| 2309 |
"version": "3.3.1",
|
| 2310 |
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
| 2311 |
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
| 2312 |
-
"dev": true,
|
| 2313 |
"license": "MIT",
|
| 2314 |
"dependencies": {
|
| 2315 |
"parent-module": "^1.0.0",
|
|
@@ -2332,6 +3067,27 @@
|
|
| 2332 |
"node": ">=0.8.19"
|
| 2333 |
}
|
| 2334 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2335 |
"node_modules/is-extglob": {
|
| 2336 |
"version": "2.1.1",
|
| 2337 |
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
|
@@ -2391,6 +3147,18 @@
|
|
| 2391 |
"js-yaml": "bin/js-yaml.js"
|
| 2392 |
}
|
| 2393 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2394 |
"node_modules/json-buffer": {
|
| 2395 |
"version": "3.0.1",
|
| 2396 |
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
|
@@ -2398,6 +3166,12 @@
|
|
| 2398 |
"dev": true,
|
| 2399 |
"license": "MIT"
|
| 2400 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2401 |
"node_modules/json-schema-traverse": {
|
| 2402 |
"version": "0.4.1",
|
| 2403 |
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
@@ -2436,6 +3210,12 @@
|
|
| 2436 |
"node": ">= 0.8.0"
|
| 2437 |
}
|
| 2438 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2439 |
"node_modules/locate-path": {
|
| 2440 |
"version": "6.0.0",
|
| 2441 |
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
|
@@ -2512,7 +3292,6 @@
|
|
| 2512 |
"version": "2.1.3",
|
| 2513 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2514 |
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2515 |
-
"dev": true,
|
| 2516 |
"license": "MIT"
|
| 2517 |
},
|
| 2518 |
"node_modules/nanoid": {
|
|
@@ -2558,6 +3337,15 @@
|
|
| 2558 |
"node": ">=0.10.0"
|
| 2559 |
}
|
| 2560 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2561 |
"node_modules/optionator": {
|
| 2562 |
"version": "0.9.4",
|
| 2563 |
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
|
@@ -2612,7 +3400,6 @@
|
|
| 2612 |
"version": "1.0.1",
|
| 2613 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
| 2614 |
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
| 2615 |
-
"dev": true,
|
| 2616 |
"license": "MIT",
|
| 2617 |
"dependencies": {
|
| 2618 |
"callsites": "^3.0.0"
|
|
@@ -2621,6 +3408,24 @@
|
|
| 2621 |
"node": ">=6"
|
| 2622 |
}
|
| 2623 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2624 |
"node_modules/path-exists": {
|
| 2625 |
"version": "4.0.0",
|
| 2626 |
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
@@ -2641,11 +3446,25 @@
|
|
| 2641 |
"node": ">=8"
|
| 2642 |
}
|
| 2643 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2644 |
"node_modules/picocolors": {
|
| 2645 |
"version": "1.1.1",
|
| 2646 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2647 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2648 |
-
"dev": true,
|
| 2649 |
"license": "ISC"
|
| 2650 |
},
|
| 2651 |
"node_modules/picomatch": {
|
|
@@ -2707,6 +3526,23 @@
|
|
| 2707 |
"node": ">= 0.8.0"
|
| 2708 |
}
|
| 2709 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2710 |
"node_modules/punycode": {
|
| 2711 |
"version": "2.3.1",
|
| 2712 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
@@ -2763,6 +3599,12 @@
|
|
| 2763 |
"react": "^18.3.1"
|
| 2764 |
}
|
| 2765 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2766 |
"node_modules/react-router": {
|
| 2767 |
"version": "6.30.1",
|
| 2768 |
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
|
@@ -2795,11 +3637,46 @@
|
|
| 2795 |
"react-dom": ">=16.8"
|
| 2796 |
}
|
| 2797 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2798 |
"node_modules/resolve-from": {
|
| 2799 |
"version": "4.0.0",
|
| 2800 |
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
| 2801 |
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
| 2802 |
-
"dev": true,
|
| 2803 |
"license": "MIT",
|
| 2804 |
"engines": {
|
| 2805 |
"node": ">=4"
|
|
@@ -2927,6 +3804,15 @@
|
|
| 2927 |
"node": ">=8"
|
| 2928 |
}
|
| 2929 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2930 |
"node_modules/source-map-js": {
|
| 2931 |
"version": "1.2.1",
|
| 2932 |
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
|
@@ -2950,6 +3836,12 @@
|
|
| 2950 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 2951 |
}
|
| 2952 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2953 |
"node_modules/supports-color": {
|
| 2954 |
"version": "7.2.0",
|
| 2955 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
@@ -2963,6 +3855,18 @@
|
|
| 2963 |
"node": ">=8"
|
| 2964 |
}
|
| 2965 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2966 |
"node_modules/to-regex-range": {
|
| 2967 |
"version": "5.0.1",
|
| 2968 |
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
|
@@ -3184,6 +4088,15 @@
|
|
| 3184 |
"node": ">=0.10.0"
|
| 3185 |
}
|
| 3186 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3187 |
"node_modules/yocto-queue": {
|
| 3188 |
"version": "0.1.0",
|
| 3189 |
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
|
@@ -3196,6 +4109,35 @@
|
|
| 3196 |
"funding": {
|
| 3197 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 3198 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3199 |
}
|
| 3200 |
}
|
| 3201 |
}
|
|
|
|
| 8 |
"name": "cua2-front",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@emotion/react": "^11.14.0",
|
| 12 |
+
"@emotion/styled": "^11.14.1",
|
| 13 |
+
"@mui/icons-material": "^7.3.4",
|
| 14 |
+
"@mui/lab": "^7.0.1-beta.19",
|
| 15 |
+
"@mui/material": "^7.3.4",
|
| 16 |
+
"gifshot": "^0.4.5",
|
| 17 |
"react": "^18.3.1",
|
| 18 |
"react-dom": "^18.3.1",
|
| 19 |
"react-router-dom": "^6.30.1",
|
| 20 |
+
"ulid": "^3.0.1",
|
| 21 |
+
"zustand": "^5.0.8"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
| 24 |
"@eslint/js": "^9.38.0",
|
|
|
|
| 35 |
"vite": "^5.4.19"
|
| 36 |
}
|
| 37 |
},
|
| 38 |
+
"node_modules/@babel/code-frame": {
|
| 39 |
+
"version": "7.27.1",
|
| 40 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
| 41 |
+
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
| 42 |
+
"license": "MIT",
|
| 43 |
+
"dependencies": {
|
| 44 |
+
"@babel/helper-validator-identifier": "^7.27.1",
|
| 45 |
+
"js-tokens": "^4.0.0",
|
| 46 |
+
"picocolors": "^1.1.1"
|
| 47 |
+
},
|
| 48 |
+
"engines": {
|
| 49 |
+
"node": ">=6.9.0"
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"node_modules/@babel/generator": {
|
| 53 |
+
"version": "7.28.5",
|
| 54 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
|
| 55 |
+
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
|
| 56 |
+
"license": "MIT",
|
| 57 |
+
"dependencies": {
|
| 58 |
+
"@babel/parser": "^7.28.5",
|
| 59 |
+
"@babel/types": "^7.28.5",
|
| 60 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 61 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 62 |
+
"jsesc": "^3.0.2"
|
| 63 |
+
},
|
| 64 |
+
"engines": {
|
| 65 |
+
"node": ">=6.9.0"
|
| 66 |
+
}
|
| 67 |
+
},
|
| 68 |
+
"node_modules/@babel/helper-globals": {
|
| 69 |
+
"version": "7.28.0",
|
| 70 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 71 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 72 |
+
"license": "MIT",
|
| 73 |
+
"engines": {
|
| 74 |
+
"node": ">=6.9.0"
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
"node_modules/@babel/helper-module-imports": {
|
| 78 |
+
"version": "7.27.1",
|
| 79 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
| 80 |
+
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
| 81 |
+
"license": "MIT",
|
| 82 |
+
"dependencies": {
|
| 83 |
+
"@babel/traverse": "^7.27.1",
|
| 84 |
+
"@babel/types": "^7.27.1"
|
| 85 |
+
},
|
| 86 |
+
"engines": {
|
| 87 |
+
"node": ">=6.9.0"
|
| 88 |
+
}
|
| 89 |
+
},
|
| 90 |
+
"node_modules/@babel/helper-string-parser": {
|
| 91 |
+
"version": "7.27.1",
|
| 92 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 93 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 94 |
+
"license": "MIT",
|
| 95 |
+
"engines": {
|
| 96 |
+
"node": ">=6.9.0"
|
| 97 |
+
}
|
| 98 |
+
},
|
| 99 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 100 |
+
"version": "7.28.5",
|
| 101 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 102 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 103 |
+
"license": "MIT",
|
| 104 |
+
"engines": {
|
| 105 |
+
"node": ">=6.9.0"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"node_modules/@babel/parser": {
|
| 109 |
+
"version": "7.28.5",
|
| 110 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
|
| 111 |
+
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
|
| 112 |
+
"license": "MIT",
|
| 113 |
+
"dependencies": {
|
| 114 |
+
"@babel/types": "^7.28.5"
|
| 115 |
+
},
|
| 116 |
+
"bin": {
|
| 117 |
+
"parser": "bin/babel-parser.js"
|
| 118 |
+
},
|
| 119 |
+
"engines": {
|
| 120 |
+
"node": ">=6.0.0"
|
| 121 |
+
}
|
| 122 |
+
},
|
| 123 |
+
"node_modules/@babel/runtime": {
|
| 124 |
+
"version": "7.28.4",
|
| 125 |
+
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
| 126 |
+
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
| 127 |
+
"license": "MIT",
|
| 128 |
+
"engines": {
|
| 129 |
+
"node": ">=6.9.0"
|
| 130 |
+
}
|
| 131 |
+
},
|
| 132 |
+
"node_modules/@babel/template": {
|
| 133 |
+
"version": "7.27.2",
|
| 134 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
| 135 |
+
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
| 136 |
+
"license": "MIT",
|
| 137 |
+
"dependencies": {
|
| 138 |
+
"@babel/code-frame": "^7.27.1",
|
| 139 |
+
"@babel/parser": "^7.27.2",
|
| 140 |
+
"@babel/types": "^7.27.1"
|
| 141 |
+
},
|
| 142 |
+
"engines": {
|
| 143 |
+
"node": ">=6.9.0"
|
| 144 |
+
}
|
| 145 |
+
},
|
| 146 |
+
"node_modules/@babel/traverse": {
|
| 147 |
+
"version": "7.28.5",
|
| 148 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
|
| 149 |
+
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
|
| 150 |
+
"license": "MIT",
|
| 151 |
+
"dependencies": {
|
| 152 |
+
"@babel/code-frame": "^7.27.1",
|
| 153 |
+
"@babel/generator": "^7.28.5",
|
| 154 |
+
"@babel/helper-globals": "^7.28.0",
|
| 155 |
+
"@babel/parser": "^7.28.5",
|
| 156 |
+
"@babel/template": "^7.27.2",
|
| 157 |
+
"@babel/types": "^7.28.5",
|
| 158 |
+
"debug": "^4.3.1"
|
| 159 |
+
},
|
| 160 |
+
"engines": {
|
| 161 |
+
"node": ">=6.9.0"
|
| 162 |
+
}
|
| 163 |
+
},
|
| 164 |
+
"node_modules/@babel/types": {
|
| 165 |
+
"version": "7.28.5",
|
| 166 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
|
| 167 |
+
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
|
| 168 |
+
"license": "MIT",
|
| 169 |
+
"dependencies": {
|
| 170 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 171 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 172 |
+
},
|
| 173 |
+
"engines": {
|
| 174 |
+
"node": ">=6.9.0"
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"node_modules/@emotion/babel-plugin": {
|
| 178 |
+
"version": "11.13.5",
|
| 179 |
+
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
| 180 |
+
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
| 181 |
+
"license": "MIT",
|
| 182 |
+
"dependencies": {
|
| 183 |
+
"@babel/helper-module-imports": "^7.16.7",
|
| 184 |
+
"@babel/runtime": "^7.18.3",
|
| 185 |
+
"@emotion/hash": "^0.9.2",
|
| 186 |
+
"@emotion/memoize": "^0.9.0",
|
| 187 |
+
"@emotion/serialize": "^1.3.3",
|
| 188 |
+
"babel-plugin-macros": "^3.1.0",
|
| 189 |
+
"convert-source-map": "^1.5.0",
|
| 190 |
+
"escape-string-regexp": "^4.0.0",
|
| 191 |
+
"find-root": "^1.1.0",
|
| 192 |
+
"source-map": "^0.5.7",
|
| 193 |
+
"stylis": "4.2.0"
|
| 194 |
+
}
|
| 195 |
+
},
|
| 196 |
+
"node_modules/@emotion/cache": {
|
| 197 |
+
"version": "11.14.0",
|
| 198 |
+
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
| 199 |
+
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
| 200 |
+
"license": "MIT",
|
| 201 |
+
"dependencies": {
|
| 202 |
+
"@emotion/memoize": "^0.9.0",
|
| 203 |
+
"@emotion/sheet": "^1.4.0",
|
| 204 |
+
"@emotion/utils": "^1.4.2",
|
| 205 |
+
"@emotion/weak-memoize": "^0.4.0",
|
| 206 |
+
"stylis": "4.2.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"node_modules/@emotion/hash": {
|
| 210 |
+
"version": "0.9.2",
|
| 211 |
+
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
| 212 |
+
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
| 213 |
+
"license": "MIT"
|
| 214 |
+
},
|
| 215 |
+
"node_modules/@emotion/is-prop-valid": {
|
| 216 |
+
"version": "1.4.0",
|
| 217 |
+
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
|
| 218 |
+
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
|
| 219 |
+
"license": "MIT",
|
| 220 |
+
"dependencies": {
|
| 221 |
+
"@emotion/memoize": "^0.9.0"
|
| 222 |
+
}
|
| 223 |
+
},
|
| 224 |
+
"node_modules/@emotion/memoize": {
|
| 225 |
+
"version": "0.9.0",
|
| 226 |
+
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
| 227 |
+
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
| 228 |
+
"license": "MIT"
|
| 229 |
+
},
|
| 230 |
+
"node_modules/@emotion/react": {
|
| 231 |
+
"version": "11.14.0",
|
| 232 |
+
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
| 233 |
+
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
| 234 |
+
"license": "MIT",
|
| 235 |
+
"dependencies": {
|
| 236 |
+
"@babel/runtime": "^7.18.3",
|
| 237 |
+
"@emotion/babel-plugin": "^11.13.5",
|
| 238 |
+
"@emotion/cache": "^11.14.0",
|
| 239 |
+
"@emotion/serialize": "^1.3.3",
|
| 240 |
+
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
| 241 |
+
"@emotion/utils": "^1.4.2",
|
| 242 |
+
"@emotion/weak-memoize": "^0.4.0",
|
| 243 |
+
"hoist-non-react-statics": "^3.3.1"
|
| 244 |
+
},
|
| 245 |
+
"peerDependencies": {
|
| 246 |
+
"react": ">=16.8.0"
|
| 247 |
+
},
|
| 248 |
+
"peerDependenciesMeta": {
|
| 249 |
+
"@types/react": {
|
| 250 |
+
"optional": true
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
},
|
| 254 |
+
"node_modules/@emotion/serialize": {
|
| 255 |
+
"version": "1.3.3",
|
| 256 |
+
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
| 257 |
+
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
| 258 |
+
"license": "MIT",
|
| 259 |
+
"dependencies": {
|
| 260 |
+
"@emotion/hash": "^0.9.2",
|
| 261 |
+
"@emotion/memoize": "^0.9.0",
|
| 262 |
+
"@emotion/unitless": "^0.10.0",
|
| 263 |
+
"@emotion/utils": "^1.4.2",
|
| 264 |
+
"csstype": "^3.0.2"
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
"node_modules/@emotion/sheet": {
|
| 268 |
+
"version": "1.4.0",
|
| 269 |
+
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
| 270 |
+
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
| 271 |
+
"license": "MIT"
|
| 272 |
+
},
|
| 273 |
+
"node_modules/@emotion/styled": {
|
| 274 |
+
"version": "11.14.1",
|
| 275 |
+
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
| 276 |
+
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
| 277 |
+
"license": "MIT",
|
| 278 |
+
"dependencies": {
|
| 279 |
+
"@babel/runtime": "^7.18.3",
|
| 280 |
+
"@emotion/babel-plugin": "^11.13.5",
|
| 281 |
+
"@emotion/is-prop-valid": "^1.3.0",
|
| 282 |
+
"@emotion/serialize": "^1.3.3",
|
| 283 |
+
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
| 284 |
+
"@emotion/utils": "^1.4.2"
|
| 285 |
+
},
|
| 286 |
+
"peerDependencies": {
|
| 287 |
+
"@emotion/react": "^11.0.0-rc.0",
|
| 288 |
+
"react": ">=16.8.0"
|
| 289 |
+
},
|
| 290 |
+
"peerDependenciesMeta": {
|
| 291 |
+
"@types/react": {
|
| 292 |
+
"optional": true
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
},
|
| 296 |
+
"node_modules/@emotion/unitless": {
|
| 297 |
+
"version": "0.10.0",
|
| 298 |
+
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
| 299 |
+
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
| 300 |
+
"license": "MIT"
|
| 301 |
+
},
|
| 302 |
+
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
| 303 |
+
"version": "1.2.0",
|
| 304 |
+
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
| 305 |
+
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
| 306 |
+
"license": "MIT",
|
| 307 |
+
"peerDependencies": {
|
| 308 |
+
"react": ">=16.8.0"
|
| 309 |
+
}
|
| 310 |
+
},
|
| 311 |
+
"node_modules/@emotion/utils": {
|
| 312 |
+
"version": "1.4.2",
|
| 313 |
+
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
| 314 |
+
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
| 315 |
+
"license": "MIT"
|
| 316 |
+
},
|
| 317 |
+
"node_modules/@emotion/weak-memoize": {
|
| 318 |
+
"version": "0.4.0",
|
| 319 |
+
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
| 320 |
+
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
| 321 |
+
"license": "MIT"
|
| 322 |
+
},
|
| 323 |
"node_modules/@esbuild/aix-ppc64": {
|
| 324 |
"version": "0.21.5",
|
| 325 |
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
|
|
|
| 920 |
"url": "https://github.com/sponsors/nzakas"
|
| 921 |
}
|
| 922 |
},
|
| 923 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 924 |
+
"version": "0.3.13",
|
| 925 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 926 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 927 |
+
"license": "MIT",
|
| 928 |
+
"dependencies": {
|
| 929 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 930 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 931 |
+
}
|
| 932 |
+
},
|
| 933 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 934 |
+
"version": "3.1.2",
|
| 935 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 936 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 937 |
+
"license": "MIT",
|
| 938 |
+
"engines": {
|
| 939 |
+
"node": ">=6.0.0"
|
| 940 |
+
}
|
| 941 |
+
},
|
| 942 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 943 |
+
"version": "1.5.5",
|
| 944 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 945 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 946 |
+
"license": "MIT"
|
| 947 |
+
},
|
| 948 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 949 |
+
"version": "0.3.31",
|
| 950 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 951 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 952 |
+
"license": "MIT",
|
| 953 |
+
"dependencies": {
|
| 954 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 955 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 956 |
+
}
|
| 957 |
+
},
|
| 958 |
+
"node_modules/@mui/core-downloads-tracker": {
|
| 959 |
+
"version": "7.3.5",
|
| 960 |
+
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz",
|
| 961 |
+
"integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==",
|
| 962 |
+
"license": "MIT",
|
| 963 |
+
"funding": {
|
| 964 |
+
"type": "opencollective",
|
| 965 |
+
"url": "https://opencollective.com/mui-org"
|
| 966 |
+
}
|
| 967 |
+
},
|
| 968 |
+
"node_modules/@mui/icons-material": {
|
| 969 |
+
"version": "7.3.4",
|
| 970 |
+
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.4.tgz",
|
| 971 |
+
"integrity": "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw==",
|
| 972 |
+
"license": "MIT",
|
| 973 |
+
"dependencies": {
|
| 974 |
+
"@babel/runtime": "^7.28.4"
|
| 975 |
+
},
|
| 976 |
+
"engines": {
|
| 977 |
+
"node": ">=14.0.0"
|
| 978 |
+
},
|
| 979 |
+
"funding": {
|
| 980 |
+
"type": "opencollective",
|
| 981 |
+
"url": "https://opencollective.com/mui-org"
|
| 982 |
+
},
|
| 983 |
+
"peerDependencies": {
|
| 984 |
+
"@mui/material": "^7.3.4",
|
| 985 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 986 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 987 |
+
},
|
| 988 |
+
"peerDependenciesMeta": {
|
| 989 |
+
"@types/react": {
|
| 990 |
+
"optional": true
|
| 991 |
+
}
|
| 992 |
+
}
|
| 993 |
+
},
|
| 994 |
+
"node_modules/@mui/lab": {
|
| 995 |
+
"version": "7.0.1-beta.19",
|
| 996 |
+
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-7.0.1-beta.19.tgz",
|
| 997 |
+
"integrity": "sha512-Ekxd2mPnr5iKwrMXjN/y2xgpxPX8ithBBcDenjqNdBt/ZQumrmBl0ifVoqAHsL6lxN6DOgRsWTRc4eOdDiB+0Q==",
|
| 998 |
+
"license": "MIT",
|
| 999 |
+
"dependencies": {
|
| 1000 |
+
"@babel/runtime": "^7.28.4",
|
| 1001 |
+
"@mui/system": "^7.3.5",
|
| 1002 |
+
"@mui/types": "^7.4.8",
|
| 1003 |
+
"@mui/utils": "^7.3.5",
|
| 1004 |
+
"clsx": "^2.1.1",
|
| 1005 |
+
"prop-types": "^15.8.1"
|
| 1006 |
+
},
|
| 1007 |
+
"engines": {
|
| 1008 |
+
"node": ">=14.0.0"
|
| 1009 |
+
},
|
| 1010 |
+
"funding": {
|
| 1011 |
+
"type": "opencollective",
|
| 1012 |
+
"url": "https://opencollective.com/mui-org"
|
| 1013 |
+
},
|
| 1014 |
+
"peerDependencies": {
|
| 1015 |
+
"@emotion/react": "^11.5.0",
|
| 1016 |
+
"@emotion/styled": "^11.3.0",
|
| 1017 |
+
"@mui/material": "^7.3.5",
|
| 1018 |
+
"@mui/material-pigment-css": "^7.3.5",
|
| 1019 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1020 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1021 |
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1022 |
+
},
|
| 1023 |
+
"peerDependenciesMeta": {
|
| 1024 |
+
"@emotion/react": {
|
| 1025 |
+
"optional": true
|
| 1026 |
+
},
|
| 1027 |
+
"@emotion/styled": {
|
| 1028 |
+
"optional": true
|
| 1029 |
+
},
|
| 1030 |
+
"@mui/material-pigment-css": {
|
| 1031 |
+
"optional": true
|
| 1032 |
+
},
|
| 1033 |
+
"@types/react": {
|
| 1034 |
+
"optional": true
|
| 1035 |
+
}
|
| 1036 |
+
}
|
| 1037 |
+
},
|
| 1038 |
+
"node_modules/@mui/material": {
|
| 1039 |
+
"version": "7.3.5",
|
| 1040 |
+
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
|
| 1041 |
+
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
|
| 1042 |
+
"license": "MIT",
|
| 1043 |
+
"dependencies": {
|
| 1044 |
+
"@babel/runtime": "^7.28.4",
|
| 1045 |
+
"@mui/core-downloads-tracker": "^7.3.5",
|
| 1046 |
+
"@mui/system": "^7.3.5",
|
| 1047 |
+
"@mui/types": "^7.4.8",
|
| 1048 |
+
"@mui/utils": "^7.3.5",
|
| 1049 |
+
"@popperjs/core": "^2.11.8",
|
| 1050 |
+
"@types/react-transition-group": "^4.4.12",
|
| 1051 |
+
"clsx": "^2.1.1",
|
| 1052 |
+
"csstype": "^3.1.3",
|
| 1053 |
+
"prop-types": "^15.8.1",
|
| 1054 |
+
"react-is": "^19.2.0",
|
| 1055 |
+
"react-transition-group": "^4.4.5"
|
| 1056 |
+
},
|
| 1057 |
+
"engines": {
|
| 1058 |
+
"node": ">=14.0.0"
|
| 1059 |
+
},
|
| 1060 |
+
"funding": {
|
| 1061 |
+
"type": "opencollective",
|
| 1062 |
+
"url": "https://opencollective.com/mui-org"
|
| 1063 |
+
},
|
| 1064 |
+
"peerDependencies": {
|
| 1065 |
+
"@emotion/react": "^11.5.0",
|
| 1066 |
+
"@emotion/styled": "^11.3.0",
|
| 1067 |
+
"@mui/material-pigment-css": "^7.3.5",
|
| 1068 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1069 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1070 |
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1071 |
+
},
|
| 1072 |
+
"peerDependenciesMeta": {
|
| 1073 |
+
"@emotion/react": {
|
| 1074 |
+
"optional": true
|
| 1075 |
+
},
|
| 1076 |
+
"@emotion/styled": {
|
| 1077 |
+
"optional": true
|
| 1078 |
+
},
|
| 1079 |
+
"@mui/material-pigment-css": {
|
| 1080 |
+
"optional": true
|
| 1081 |
+
},
|
| 1082 |
+
"@types/react": {
|
| 1083 |
+
"optional": true
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
},
|
| 1087 |
+
"node_modules/@mui/private-theming": {
|
| 1088 |
+
"version": "7.3.5",
|
| 1089 |
+
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz",
|
| 1090 |
+
"integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==",
|
| 1091 |
+
"license": "MIT",
|
| 1092 |
+
"dependencies": {
|
| 1093 |
+
"@babel/runtime": "^7.28.4",
|
| 1094 |
+
"@mui/utils": "^7.3.5",
|
| 1095 |
+
"prop-types": "^15.8.1"
|
| 1096 |
+
},
|
| 1097 |
+
"engines": {
|
| 1098 |
+
"node": ">=14.0.0"
|
| 1099 |
+
},
|
| 1100 |
+
"funding": {
|
| 1101 |
+
"type": "opencollective",
|
| 1102 |
+
"url": "https://opencollective.com/mui-org"
|
| 1103 |
+
},
|
| 1104 |
+
"peerDependencies": {
|
| 1105 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1106 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1107 |
+
},
|
| 1108 |
+
"peerDependenciesMeta": {
|
| 1109 |
+
"@types/react": {
|
| 1110 |
+
"optional": true
|
| 1111 |
+
}
|
| 1112 |
+
}
|
| 1113 |
+
},
|
| 1114 |
+
"node_modules/@mui/styled-engine": {
|
| 1115 |
+
"version": "7.3.5",
|
| 1116 |
+
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz",
|
| 1117 |
+
"integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==",
|
| 1118 |
+
"license": "MIT",
|
| 1119 |
+
"dependencies": {
|
| 1120 |
+
"@babel/runtime": "^7.28.4",
|
| 1121 |
+
"@emotion/cache": "^11.14.0",
|
| 1122 |
+
"@emotion/serialize": "^1.3.3",
|
| 1123 |
+
"@emotion/sheet": "^1.4.0",
|
| 1124 |
+
"csstype": "^3.1.3",
|
| 1125 |
+
"prop-types": "^15.8.1"
|
| 1126 |
+
},
|
| 1127 |
+
"engines": {
|
| 1128 |
+
"node": ">=14.0.0"
|
| 1129 |
+
},
|
| 1130 |
+
"funding": {
|
| 1131 |
+
"type": "opencollective",
|
| 1132 |
+
"url": "https://opencollective.com/mui-org"
|
| 1133 |
+
},
|
| 1134 |
+
"peerDependencies": {
|
| 1135 |
+
"@emotion/react": "^11.4.1",
|
| 1136 |
+
"@emotion/styled": "^11.3.0",
|
| 1137 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1138 |
+
},
|
| 1139 |
+
"peerDependenciesMeta": {
|
| 1140 |
+
"@emotion/react": {
|
| 1141 |
+
"optional": true
|
| 1142 |
+
},
|
| 1143 |
+
"@emotion/styled": {
|
| 1144 |
+
"optional": true
|
| 1145 |
+
}
|
| 1146 |
+
}
|
| 1147 |
+
},
|
| 1148 |
+
"node_modules/@mui/system": {
|
| 1149 |
+
"version": "7.3.5",
|
| 1150 |
+
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz",
|
| 1151 |
+
"integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==",
|
| 1152 |
+
"license": "MIT",
|
| 1153 |
+
"dependencies": {
|
| 1154 |
+
"@babel/runtime": "^7.28.4",
|
| 1155 |
+
"@mui/private-theming": "^7.3.5",
|
| 1156 |
+
"@mui/styled-engine": "^7.3.5",
|
| 1157 |
+
"@mui/types": "^7.4.8",
|
| 1158 |
+
"@mui/utils": "^7.3.5",
|
| 1159 |
+
"clsx": "^2.1.1",
|
| 1160 |
+
"csstype": "^3.1.3",
|
| 1161 |
+
"prop-types": "^15.8.1"
|
| 1162 |
+
},
|
| 1163 |
+
"engines": {
|
| 1164 |
+
"node": ">=14.0.0"
|
| 1165 |
+
},
|
| 1166 |
+
"funding": {
|
| 1167 |
+
"type": "opencollective",
|
| 1168 |
+
"url": "https://opencollective.com/mui-org"
|
| 1169 |
+
},
|
| 1170 |
+
"peerDependencies": {
|
| 1171 |
+
"@emotion/react": "^11.5.0",
|
| 1172 |
+
"@emotion/styled": "^11.3.0",
|
| 1173 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1174 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1175 |
+
},
|
| 1176 |
+
"peerDependenciesMeta": {
|
| 1177 |
+
"@emotion/react": {
|
| 1178 |
+
"optional": true
|
| 1179 |
+
},
|
| 1180 |
+
"@emotion/styled": {
|
| 1181 |
+
"optional": true
|
| 1182 |
+
},
|
| 1183 |
+
"@types/react": {
|
| 1184 |
+
"optional": true
|
| 1185 |
+
}
|
| 1186 |
+
}
|
| 1187 |
+
},
|
| 1188 |
+
"node_modules/@mui/types": {
|
| 1189 |
+
"version": "7.4.8",
|
| 1190 |
+
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz",
|
| 1191 |
+
"integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==",
|
| 1192 |
+
"license": "MIT",
|
| 1193 |
+
"dependencies": {
|
| 1194 |
+
"@babel/runtime": "^7.28.4"
|
| 1195 |
+
},
|
| 1196 |
+
"peerDependencies": {
|
| 1197 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1198 |
+
},
|
| 1199 |
+
"peerDependenciesMeta": {
|
| 1200 |
+
"@types/react": {
|
| 1201 |
+
"optional": true
|
| 1202 |
+
}
|
| 1203 |
+
}
|
| 1204 |
+
},
|
| 1205 |
+
"node_modules/@mui/utils": {
|
| 1206 |
+
"version": "7.3.5",
|
| 1207 |
+
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz",
|
| 1208 |
+
"integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==",
|
| 1209 |
+
"license": "MIT",
|
| 1210 |
+
"dependencies": {
|
| 1211 |
+
"@babel/runtime": "^7.28.4",
|
| 1212 |
+
"@mui/types": "^7.4.8",
|
| 1213 |
+
"@types/prop-types": "^15.7.15",
|
| 1214 |
+
"clsx": "^2.1.1",
|
| 1215 |
+
"prop-types": "^15.8.1",
|
| 1216 |
+
"react-is": "^19.2.0"
|
| 1217 |
+
},
|
| 1218 |
+
"engines": {
|
| 1219 |
+
"node": ">=14.0.0"
|
| 1220 |
+
},
|
| 1221 |
+
"funding": {
|
| 1222 |
+
"type": "opencollective",
|
| 1223 |
+
"url": "https://opencollective.com/mui-org"
|
| 1224 |
+
},
|
| 1225 |
+
"peerDependencies": {
|
| 1226 |
+
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 1227 |
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1228 |
+
},
|
| 1229 |
+
"peerDependenciesMeta": {
|
| 1230 |
+
"@types/react": {
|
| 1231 |
+
"optional": true
|
| 1232 |
+
}
|
| 1233 |
+
}
|
| 1234 |
+
},
|
| 1235 |
"node_modules/@nodelib/fs.scandir": {
|
| 1236 |
"version": "2.1.5",
|
| 1237 |
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
|
|
|
| 1270 |
"node": ">= 8"
|
| 1271 |
}
|
| 1272 |
},
|
| 1273 |
+
"node_modules/@popperjs/core": {
|
| 1274 |
+
"version": "2.11.8",
|
| 1275 |
+
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
| 1276 |
+
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
| 1277 |
+
"license": "MIT",
|
| 1278 |
+
"funding": {
|
| 1279 |
+
"type": "opencollective",
|
| 1280 |
+
"url": "https://opencollective.com/popperjs"
|
| 1281 |
+
}
|
| 1282 |
+
},
|
| 1283 |
"node_modules/@remix-run/router": {
|
| 1284 |
"version": "1.23.0",
|
| 1285 |
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
|
|
|
| 1854 |
"undici-types": "~6.21.0"
|
| 1855 |
}
|
| 1856 |
},
|
| 1857 |
+
"node_modules/@types/parse-json": {
|
| 1858 |
+
"version": "4.0.2",
|
| 1859 |
+
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
| 1860 |
+
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
| 1861 |
+
"license": "MIT"
|
| 1862 |
+
},
|
| 1863 |
"node_modules/@types/prop-types": {
|
| 1864 |
"version": "15.7.15",
|
| 1865 |
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
| 1866 |
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
|
|
|
| 1867 |
"license": "MIT"
|
| 1868 |
},
|
| 1869 |
"node_modules/@types/react": {
|
| 1870 |
"version": "18.3.26",
|
| 1871 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
| 1872 |
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
|
|
|
| 1873 |
"license": "MIT",
|
| 1874 |
"dependencies": {
|
| 1875 |
"@types/prop-types": "*",
|
|
|
|
| 1886 |
"@types/react": "^18.0.0"
|
| 1887 |
}
|
| 1888 |
},
|
| 1889 |
+
"node_modules/@types/react-transition-group": {
|
| 1890 |
+
"version": "4.4.12",
|
| 1891 |
+
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
| 1892 |
+
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
| 1893 |
+
"license": "MIT",
|
| 1894 |
+
"peerDependencies": {
|
| 1895 |
+
"@types/react": "*"
|
| 1896 |
+
}
|
| 1897 |
+
},
|
| 1898 |
"node_modules/@typescript-eslint/eslint-plugin": {
|
| 1899 |
"version": "8.46.1",
|
| 1900 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz",
|
|
|
|
| 2268 |
"postcss": "^8.1.0"
|
| 2269 |
}
|
| 2270 |
},
|
| 2271 |
+
"node_modules/babel-plugin-macros": {
|
| 2272 |
+
"version": "3.1.0",
|
| 2273 |
+
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
| 2274 |
+
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
| 2275 |
+
"license": "MIT",
|
| 2276 |
+
"dependencies": {
|
| 2277 |
+
"@babel/runtime": "^7.12.5",
|
| 2278 |
+
"cosmiconfig": "^7.0.0",
|
| 2279 |
+
"resolve": "^1.19.0"
|
| 2280 |
+
},
|
| 2281 |
+
"engines": {
|
| 2282 |
+
"node": ">=10",
|
| 2283 |
+
"npm": ">=6"
|
| 2284 |
+
}
|
| 2285 |
+
},
|
| 2286 |
"node_modules/balanced-match": {
|
| 2287 |
"version": "1.0.2",
|
| 2288 |
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
|
|
| 2362 |
"version": "3.1.0",
|
| 2363 |
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
| 2364 |
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
|
|
|
| 2365 |
"license": "MIT",
|
| 2366 |
"engines": {
|
| 2367 |
"node": ">=6"
|
|
|
|
| 2405 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 2406 |
}
|
| 2407 |
},
|
| 2408 |
+
"node_modules/clsx": {
|
| 2409 |
+
"version": "2.1.1",
|
| 2410 |
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
| 2411 |
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
| 2412 |
+
"license": "MIT",
|
| 2413 |
+
"engines": {
|
| 2414 |
+
"node": ">=6"
|
| 2415 |
+
}
|
| 2416 |
+
},
|
| 2417 |
"node_modules/color-convert": {
|
| 2418 |
"version": "2.0.1",
|
| 2419 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
|
|
| 2441 |
"dev": true,
|
| 2442 |
"license": "MIT"
|
| 2443 |
},
|
| 2444 |
+
"node_modules/convert-source-map": {
|
| 2445 |
+
"version": "1.9.0",
|
| 2446 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
| 2447 |
+
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
| 2448 |
+
"license": "MIT"
|
| 2449 |
+
},
|
| 2450 |
+
"node_modules/cosmiconfig": {
|
| 2451 |
+
"version": "7.1.0",
|
| 2452 |
+
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
| 2453 |
+
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
| 2454 |
+
"license": "MIT",
|
| 2455 |
+
"dependencies": {
|
| 2456 |
+
"@types/parse-json": "^4.0.0",
|
| 2457 |
+
"import-fresh": "^3.2.1",
|
| 2458 |
+
"parse-json": "^5.0.0",
|
| 2459 |
+
"path-type": "^4.0.0",
|
| 2460 |
+
"yaml": "^1.10.0"
|
| 2461 |
+
},
|
| 2462 |
+
"engines": {
|
| 2463 |
+
"node": ">=10"
|
| 2464 |
+
}
|
| 2465 |
+
},
|
| 2466 |
"node_modules/cross-spawn": {
|
| 2467 |
"version": "7.0.6",
|
| 2468 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
|
|
|
| 2482 |
"version": "3.1.3",
|
| 2483 |
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 2484 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
|
|
|
| 2485 |
"license": "MIT"
|
| 2486 |
},
|
| 2487 |
"node_modules/debug": {
|
| 2488 |
"version": "4.4.3",
|
| 2489 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 2490 |
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
|
|
|
| 2491 |
"license": "MIT",
|
| 2492 |
"dependencies": {
|
| 2493 |
"ms": "^2.1.3"
|
|
|
|
| 2508 |
"dev": true,
|
| 2509 |
"license": "MIT"
|
| 2510 |
},
|
| 2511 |
+
"node_modules/dom-helpers": {
|
| 2512 |
+
"version": "5.2.1",
|
| 2513 |
+
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
| 2514 |
+
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
| 2515 |
+
"license": "MIT",
|
| 2516 |
+
"dependencies": {
|
| 2517 |
+
"@babel/runtime": "^7.8.7",
|
| 2518 |
+
"csstype": "^3.0.2"
|
| 2519 |
+
}
|
| 2520 |
+
},
|
| 2521 |
"node_modules/electron-to-chromium": {
|
| 2522 |
"version": "1.5.237",
|
| 2523 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
|
|
|
|
| 2525 |
"dev": true,
|
| 2526 |
"license": "ISC"
|
| 2527 |
},
|
| 2528 |
+
"node_modules/error-ex": {
|
| 2529 |
+
"version": "1.3.4",
|
| 2530 |
+
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
| 2531 |
+
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
| 2532 |
+
"license": "MIT",
|
| 2533 |
+
"dependencies": {
|
| 2534 |
+
"is-arrayish": "^0.2.1"
|
| 2535 |
+
}
|
| 2536 |
+
},
|
| 2537 |
"node_modules/esbuild": {
|
| 2538 |
"version": "0.21.5",
|
| 2539 |
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
|
|
|
| 2587 |
"version": "4.0.0",
|
| 2588 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 2589 |
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
|
|
|
| 2590 |
"license": "MIT",
|
| 2591 |
"engines": {
|
| 2592 |
"node": ">=10"
|
|
|
|
| 2873 |
"node": ">=8"
|
| 2874 |
}
|
| 2875 |
},
|
| 2876 |
+
"node_modules/find-root": {
|
| 2877 |
+
"version": "1.1.0",
|
| 2878 |
+
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
| 2879 |
+
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
| 2880 |
+
"license": "MIT"
|
| 2881 |
+
},
|
| 2882 |
"node_modules/find-up": {
|
| 2883 |
"version": "5.0.0",
|
| 2884 |
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
|
|
|
| 2946 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 2947 |
}
|
| 2948 |
},
|
| 2949 |
+
"node_modules/function-bind": {
|
| 2950 |
+
"version": "1.1.2",
|
| 2951 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 2952 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 2953 |
+
"license": "MIT",
|
| 2954 |
+
"funding": {
|
| 2955 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2956 |
+
}
|
| 2957 |
+
},
|
| 2958 |
+
"node_modules/gifshot": {
|
| 2959 |
+
"version": "0.4.5",
|
| 2960 |
+
"resolved": "https://registry.npmjs.org/gifshot/-/gifshot-0.4.5.tgz",
|
| 2961 |
+
"integrity": "sha512-oaOTT7patjxFFv7ptR0R0NNhqy3ZAmcLUQCjM/sTsvsQaUAlB2fHirLajcNAKJ6ufoVhdP+ZkXYvmUycHP1FNg==",
|
| 2962 |
+
"license": "MIT"
|
| 2963 |
+
},
|
| 2964 |
"node_modules/glob-parent": {
|
| 2965 |
"version": "6.0.2",
|
| 2966 |
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
|
|
|
| 3004 |
"node": ">=8"
|
| 3005 |
}
|
| 3006 |
},
|
| 3007 |
+
"node_modules/hasown": {
|
| 3008 |
+
"version": "2.0.2",
|
| 3009 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 3010 |
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 3011 |
+
"license": "MIT",
|
| 3012 |
+
"dependencies": {
|
| 3013 |
+
"function-bind": "^1.1.2"
|
| 3014 |
+
},
|
| 3015 |
+
"engines": {
|
| 3016 |
+
"node": ">= 0.4"
|
| 3017 |
+
}
|
| 3018 |
+
},
|
| 3019 |
+
"node_modules/hoist-non-react-statics": {
|
| 3020 |
+
"version": "3.3.2",
|
| 3021 |
+
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
| 3022 |
+
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
| 3023 |
+
"license": "BSD-3-Clause",
|
| 3024 |
+
"dependencies": {
|
| 3025 |
+
"react-is": "^16.7.0"
|
| 3026 |
+
}
|
| 3027 |
+
},
|
| 3028 |
+
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
| 3029 |
+
"version": "16.13.1",
|
| 3030 |
+
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
| 3031 |
+
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
| 3032 |
+
"license": "MIT"
|
| 3033 |
+
},
|
| 3034 |
"node_modules/ignore": {
|
| 3035 |
"version": "5.3.2",
|
| 3036 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
|
|
| 3045 |
"version": "3.3.1",
|
| 3046 |
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
| 3047 |
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
|
|
|
| 3048 |
"license": "MIT",
|
| 3049 |
"dependencies": {
|
| 3050 |
"parent-module": "^1.0.0",
|
|
|
|
| 3067 |
"node": ">=0.8.19"
|
| 3068 |
}
|
| 3069 |
},
|
| 3070 |
+
"node_modules/is-arrayish": {
|
| 3071 |
+
"version": "0.2.1",
|
| 3072 |
+
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
| 3073 |
+
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
| 3074 |
+
"license": "MIT"
|
| 3075 |
+
},
|
| 3076 |
+
"node_modules/is-core-module": {
|
| 3077 |
+
"version": "2.16.1",
|
| 3078 |
+
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
| 3079 |
+
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
| 3080 |
+
"license": "MIT",
|
| 3081 |
+
"dependencies": {
|
| 3082 |
+
"hasown": "^2.0.2"
|
| 3083 |
+
},
|
| 3084 |
+
"engines": {
|
| 3085 |
+
"node": ">= 0.4"
|
| 3086 |
+
},
|
| 3087 |
+
"funding": {
|
| 3088 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 3089 |
+
}
|
| 3090 |
+
},
|
| 3091 |
"node_modules/is-extglob": {
|
| 3092 |
"version": "2.1.1",
|
| 3093 |
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
|
|
|
| 3147 |
"js-yaml": "bin/js-yaml.js"
|
| 3148 |
}
|
| 3149 |
},
|
| 3150 |
+
"node_modules/jsesc": {
|
| 3151 |
+
"version": "3.1.0",
|
| 3152 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 3153 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 3154 |
+
"license": "MIT",
|
| 3155 |
+
"bin": {
|
| 3156 |
+
"jsesc": "bin/jsesc"
|
| 3157 |
+
},
|
| 3158 |
+
"engines": {
|
| 3159 |
+
"node": ">=6"
|
| 3160 |
+
}
|
| 3161 |
+
},
|
| 3162 |
"node_modules/json-buffer": {
|
| 3163 |
"version": "3.0.1",
|
| 3164 |
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
|
|
|
| 3166 |
"dev": true,
|
| 3167 |
"license": "MIT"
|
| 3168 |
},
|
| 3169 |
+
"node_modules/json-parse-even-better-errors": {
|
| 3170 |
+
"version": "2.3.1",
|
| 3171 |
+
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
| 3172 |
+
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
| 3173 |
+
"license": "MIT"
|
| 3174 |
+
},
|
| 3175 |
"node_modules/json-schema-traverse": {
|
| 3176 |
"version": "0.4.1",
|
| 3177 |
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
|
|
| 3210 |
"node": ">= 0.8.0"
|
| 3211 |
}
|
| 3212 |
},
|
| 3213 |
+
"node_modules/lines-and-columns": {
|
| 3214 |
+
"version": "1.2.4",
|
| 3215 |
+
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
| 3216 |
+
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
| 3217 |
+
"license": "MIT"
|
| 3218 |
+
},
|
| 3219 |
"node_modules/locate-path": {
|
| 3220 |
"version": "6.0.0",
|
| 3221 |
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
|
|
|
| 3292 |
"version": "2.1.3",
|
| 3293 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 3294 |
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
|
|
|
| 3295 |
"license": "MIT"
|
| 3296 |
},
|
| 3297 |
"node_modules/nanoid": {
|
|
|
|
| 3337 |
"node": ">=0.10.0"
|
| 3338 |
}
|
| 3339 |
},
|
| 3340 |
+
"node_modules/object-assign": {
|
| 3341 |
+
"version": "4.1.1",
|
| 3342 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 3343 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 3344 |
+
"license": "MIT",
|
| 3345 |
+
"engines": {
|
| 3346 |
+
"node": ">=0.10.0"
|
| 3347 |
+
}
|
| 3348 |
+
},
|
| 3349 |
"node_modules/optionator": {
|
| 3350 |
"version": "0.9.4",
|
| 3351 |
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
|
|
|
| 3400 |
"version": "1.0.1",
|
| 3401 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
| 3402 |
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
|
|
|
| 3403 |
"license": "MIT",
|
| 3404 |
"dependencies": {
|
| 3405 |
"callsites": "^3.0.0"
|
|
|
|
| 3408 |
"node": ">=6"
|
| 3409 |
}
|
| 3410 |
},
|
| 3411 |
+
"node_modules/parse-json": {
|
| 3412 |
+
"version": "5.2.0",
|
| 3413 |
+
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
| 3414 |
+
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
| 3415 |
+
"license": "MIT",
|
| 3416 |
+
"dependencies": {
|
| 3417 |
+
"@babel/code-frame": "^7.0.0",
|
| 3418 |
+
"error-ex": "^1.3.1",
|
| 3419 |
+
"json-parse-even-better-errors": "^2.3.0",
|
| 3420 |
+
"lines-and-columns": "^1.1.6"
|
| 3421 |
+
},
|
| 3422 |
+
"engines": {
|
| 3423 |
+
"node": ">=8"
|
| 3424 |
+
},
|
| 3425 |
+
"funding": {
|
| 3426 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 3427 |
+
}
|
| 3428 |
+
},
|
| 3429 |
"node_modules/path-exists": {
|
| 3430 |
"version": "4.0.0",
|
| 3431 |
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
|
|
| 3446 |
"node": ">=8"
|
| 3447 |
}
|
| 3448 |
},
|
| 3449 |
+
"node_modules/path-parse": {
|
| 3450 |
+
"version": "1.0.7",
|
| 3451 |
+
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
| 3452 |
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
| 3453 |
+
"license": "MIT"
|
| 3454 |
+
},
|
| 3455 |
+
"node_modules/path-type": {
|
| 3456 |
+
"version": "4.0.0",
|
| 3457 |
+
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
| 3458 |
+
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
| 3459 |
+
"license": "MIT",
|
| 3460 |
+
"engines": {
|
| 3461 |
+
"node": ">=8"
|
| 3462 |
+
}
|
| 3463 |
+
},
|
| 3464 |
"node_modules/picocolors": {
|
| 3465 |
"version": "1.1.1",
|
| 3466 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 3467 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
|
|
|
| 3468 |
"license": "ISC"
|
| 3469 |
},
|
| 3470 |
"node_modules/picomatch": {
|
|
|
|
| 3526 |
"node": ">= 0.8.0"
|
| 3527 |
}
|
| 3528 |
},
|
| 3529 |
+
"node_modules/prop-types": {
|
| 3530 |
+
"version": "15.8.1",
|
| 3531 |
+
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
| 3532 |
+
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
| 3533 |
+
"license": "MIT",
|
| 3534 |
+
"dependencies": {
|
| 3535 |
+
"loose-envify": "^1.4.0",
|
| 3536 |
+
"object-assign": "^4.1.1",
|
| 3537 |
+
"react-is": "^16.13.1"
|
| 3538 |
+
}
|
| 3539 |
+
},
|
| 3540 |
+
"node_modules/prop-types/node_modules/react-is": {
|
| 3541 |
+
"version": "16.13.1",
|
| 3542 |
+
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
| 3543 |
+
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
| 3544 |
+
"license": "MIT"
|
| 3545 |
+
},
|
| 3546 |
"node_modules/punycode": {
|
| 3547 |
"version": "2.3.1",
|
| 3548 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
|
|
| 3599 |
"react": "^18.3.1"
|
| 3600 |
}
|
| 3601 |
},
|
| 3602 |
+
"node_modules/react-is": {
|
| 3603 |
+
"version": "19.2.0",
|
| 3604 |
+
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz",
|
| 3605 |
+
"integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
|
| 3606 |
+
"license": "MIT"
|
| 3607 |
+
},
|
| 3608 |
"node_modules/react-router": {
|
| 3609 |
"version": "6.30.1",
|
| 3610 |
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
|
|
|
| 3637 |
"react-dom": ">=16.8"
|
| 3638 |
}
|
| 3639 |
},
|
| 3640 |
+
"node_modules/react-transition-group": {
|
| 3641 |
+
"version": "4.4.5",
|
| 3642 |
+
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
| 3643 |
+
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
| 3644 |
+
"license": "BSD-3-Clause",
|
| 3645 |
+
"dependencies": {
|
| 3646 |
+
"@babel/runtime": "^7.5.5",
|
| 3647 |
+
"dom-helpers": "^5.0.1",
|
| 3648 |
+
"loose-envify": "^1.4.0",
|
| 3649 |
+
"prop-types": "^15.6.2"
|
| 3650 |
+
},
|
| 3651 |
+
"peerDependencies": {
|
| 3652 |
+
"react": ">=16.6.0",
|
| 3653 |
+
"react-dom": ">=16.6.0"
|
| 3654 |
+
}
|
| 3655 |
+
},
|
| 3656 |
+
"node_modules/resolve": {
|
| 3657 |
+
"version": "1.22.11",
|
| 3658 |
+
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
| 3659 |
+
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
| 3660 |
+
"license": "MIT",
|
| 3661 |
+
"dependencies": {
|
| 3662 |
+
"is-core-module": "^2.16.1",
|
| 3663 |
+
"path-parse": "^1.0.7",
|
| 3664 |
+
"supports-preserve-symlinks-flag": "^1.0.0"
|
| 3665 |
+
},
|
| 3666 |
+
"bin": {
|
| 3667 |
+
"resolve": "bin/resolve"
|
| 3668 |
+
},
|
| 3669 |
+
"engines": {
|
| 3670 |
+
"node": ">= 0.4"
|
| 3671 |
+
},
|
| 3672 |
+
"funding": {
|
| 3673 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 3674 |
+
}
|
| 3675 |
+
},
|
| 3676 |
"node_modules/resolve-from": {
|
| 3677 |
"version": "4.0.0",
|
| 3678 |
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
| 3679 |
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
|
|
|
| 3680 |
"license": "MIT",
|
| 3681 |
"engines": {
|
| 3682 |
"node": ">=4"
|
|
|
|
| 3804 |
"node": ">=8"
|
| 3805 |
}
|
| 3806 |
},
|
| 3807 |
+
"node_modules/source-map": {
|
| 3808 |
+
"version": "0.5.7",
|
| 3809 |
+
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
| 3810 |
+
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
| 3811 |
+
"license": "BSD-3-Clause",
|
| 3812 |
+
"engines": {
|
| 3813 |
+
"node": ">=0.10.0"
|
| 3814 |
+
}
|
| 3815 |
+
},
|
| 3816 |
"node_modules/source-map-js": {
|
| 3817 |
"version": "1.2.1",
|
| 3818 |
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
|
|
|
| 3836 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 3837 |
}
|
| 3838 |
},
|
| 3839 |
+
"node_modules/stylis": {
|
| 3840 |
+
"version": "4.2.0",
|
| 3841 |
+
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
| 3842 |
+
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
| 3843 |
+
"license": "MIT"
|
| 3844 |
+
},
|
| 3845 |
"node_modules/supports-color": {
|
| 3846 |
"version": "7.2.0",
|
| 3847 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
|
|
| 3855 |
"node": ">=8"
|
| 3856 |
}
|
| 3857 |
},
|
| 3858 |
+
"node_modules/supports-preserve-symlinks-flag": {
|
| 3859 |
+
"version": "1.0.0",
|
| 3860 |
+
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
| 3861 |
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
| 3862 |
+
"license": "MIT",
|
| 3863 |
+
"engines": {
|
| 3864 |
+
"node": ">= 0.4"
|
| 3865 |
+
},
|
| 3866 |
+
"funding": {
|
| 3867 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 3868 |
+
}
|
| 3869 |
+
},
|
| 3870 |
"node_modules/to-regex-range": {
|
| 3871 |
"version": "5.0.1",
|
| 3872 |
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
|
|
|
| 4088 |
"node": ">=0.10.0"
|
| 4089 |
}
|
| 4090 |
},
|
| 4091 |
+
"node_modules/yaml": {
|
| 4092 |
+
"version": "1.10.2",
|
| 4093 |
+
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
| 4094 |
+
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
| 4095 |
+
"license": "ISC",
|
| 4096 |
+
"engines": {
|
| 4097 |
+
"node": ">= 6"
|
| 4098 |
+
}
|
| 4099 |
+
},
|
| 4100 |
"node_modules/yocto-queue": {
|
| 4101 |
"version": "0.1.0",
|
| 4102 |
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
|
|
|
| 4109 |
"funding": {
|
| 4110 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 4111 |
}
|
| 4112 |
+
},
|
| 4113 |
+
"node_modules/zustand": {
|
| 4114 |
+
"version": "5.0.8",
|
| 4115 |
+
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
| 4116 |
+
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
| 4117 |
+
"license": "MIT",
|
| 4118 |
+
"engines": {
|
| 4119 |
+
"node": ">=12.20.0"
|
| 4120 |
+
},
|
| 4121 |
+
"peerDependencies": {
|
| 4122 |
+
"@types/react": ">=18.0.0",
|
| 4123 |
+
"immer": ">=9.0.6",
|
| 4124 |
+
"react": ">=18.0.0",
|
| 4125 |
+
"use-sync-external-store": ">=1.2.0"
|
| 4126 |
+
},
|
| 4127 |
+
"peerDependenciesMeta": {
|
| 4128 |
+
"@types/react": {
|
| 4129 |
+
"optional": true
|
| 4130 |
+
},
|
| 4131 |
+
"immer": {
|
| 4132 |
+
"optional": true
|
| 4133 |
+
},
|
| 4134 |
+
"react": {
|
| 4135 |
+
"optional": true
|
| 4136 |
+
},
|
| 4137 |
+
"use-sync-external-store": {
|
| 4138 |
+
"optional": true
|
| 4139 |
+
}
|
| 4140 |
+
}
|
| 4141 |
}
|
| 4142 |
}
|
| 4143 |
}
|
|
@@ -12,10 +12,17 @@
|
|
| 12 |
"preview": "vite preview"
|
| 13 |
},
|
| 14 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
"react": "^18.3.1",
|
| 16 |
-
"react-router-dom": "^6.30.1",
|
| 17 |
"react-dom": "^18.3.1",
|
| 18 |
-
"
|
|
|
|
|
|
|
| 19 |
},
|
| 20 |
"devDependencies": {
|
| 21 |
"@eslint/js": "^9.38.0",
|
|
|
|
| 12 |
"preview": "vite preview"
|
| 13 |
},
|
| 14 |
"dependencies": {
|
| 15 |
+
"@emotion/react": "^11.14.0",
|
| 16 |
+
"@emotion/styled": "^11.14.1",
|
| 17 |
+
"@mui/icons-material": "^7.3.4",
|
| 18 |
+
"@mui/lab": "^7.0.1-beta.19",
|
| 19 |
+
"@mui/material": "^7.3.4",
|
| 20 |
+
"gifshot": "^0.4.5",
|
| 21 |
"react": "^18.3.1",
|
|
|
|
| 22 |
"react-dom": "^18.3.1",
|
| 23 |
+
"react-router-dom": "^6.30.1",
|
| 24 |
+
"ulid": "^3.0.1",
|
| 25 |
+
"zustand": "^5.0.8"
|
| 26 |
},
|
| 27 |
"devDependencies": {
|
| 28 |
"@eslint/js": "^9.38.0",
|
|
@@ -1,14 +1,31 @@
|
|
| 1 |
-
import
|
| 2 |
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
| 3 |
-
import
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
export default App;
|
|
|
|
| 1 |
+
import { useMemo } from 'react';
|
| 2 |
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
| 3 |
+
import { ThemeProvider, CssBaseline } from '@mui/material';
|
| 4 |
+
import getTheme from './theme';
|
| 5 |
+
import Welcome from "./pages/Welcome";
|
| 6 |
+
import Task from "./pages/Task";
|
| 7 |
+
import { useAgentStore, selectIsDarkMode } from './stores/agentStore';
|
| 8 |
+
import { useAgentWebSocket } from './hooks/useAgentWebSocket';
|
| 9 |
+
import { config } from './config';
|
| 10 |
+
|
| 11 |
+
const App = () => {
|
| 12 |
+
const isDarkMode = useAgentStore(selectIsDarkMode);
|
| 13 |
+
const theme = useMemo(() => getTheme(isDarkMode ? 'dark' : 'light'), [isDarkMode]);
|
| 14 |
+
|
| 15 |
+
// Initialize WebSocket connection at app level so it persists across route changes
|
| 16 |
+
useAgentWebSocket({ url: config.wsUrl });
|
| 17 |
+
|
| 18 |
+
return (
|
| 19 |
+
<ThemeProvider theme={theme}>
|
| 20 |
+
<CssBaseline />
|
| 21 |
+
<BrowserRouter>
|
| 22 |
+
<Routes>
|
| 23 |
+
<Route path="/" element={<Welcome />} />
|
| 24 |
+
<Route path="/task" element={<Task />} />
|
| 25 |
+
</Routes>
|
| 26 |
+
</BrowserRouter>
|
| 27 |
+
</ThemeProvider>
|
| 28 |
+
);
|
| 29 |
+
};
|
| 30 |
|
| 31 |
export default App;
|
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Box, Chip, keyframes } from '@mui/material';
|
| 3 |
+
import CircleIcon from '@mui/icons-material/Circle';
|
| 4 |
+
|
| 5 |
+
interface ConnectionStatusProps {
|
| 6 |
+
isConnected: boolean;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
// Pulse animation for connected indicator
|
| 10 |
+
const pulse = keyframes`
|
| 11 |
+
0%, 100% {
|
| 12 |
+
opacity: 1;
|
| 13 |
+
}
|
| 14 |
+
50% {
|
| 15 |
+
opacity: 0.5;
|
| 16 |
+
}
|
| 17 |
+
`;
|
| 18 |
+
|
| 19 |
+
export const ConnectionStatus: React.FC<ConnectionStatusProps> = ({ isConnected }) => {
|
| 20 |
+
return (
|
| 21 |
+
<Chip
|
| 22 |
+
label={isConnected ? 'Backend Online' : 'Backend Offline'}
|
| 23 |
+
deleteIcon={
|
| 24 |
+
<CircleIcon
|
| 25 |
+
sx={{
|
| 26 |
+
fontSize: 6,
|
| 27 |
+
animation: isConnected ? `${pulse} 2s ease-in-out infinite` : 'none',
|
| 28 |
+
}}
|
| 29 |
+
/>
|
| 30 |
+
}
|
| 31 |
+
onDelete={() => {}} // Required for deleteIcon to show
|
| 32 |
+
size="small"
|
| 33 |
+
sx={{
|
| 34 |
+
backgroundColor: 'action.hover',
|
| 35 |
+
border: '1px solid',
|
| 36 |
+
borderColor: 'divider',
|
| 37 |
+
color: 'text.primary',
|
| 38 |
+
fontSize: '0.7rem',
|
| 39 |
+
fontWeight: 500,
|
| 40 |
+
height: 'auto',
|
| 41 |
+
'& .MuiChip-label': {
|
| 42 |
+
px: 1,
|
| 43 |
+
py: 0.5,
|
| 44 |
+
},
|
| 45 |
+
'& .MuiChip-deleteIcon': {
|
| 46 |
+
color: isConnected ? '#10b981' : '#ef4444',
|
| 47 |
+
marginRight: 0.5,
|
| 48 |
+
'&:hover': {
|
| 49 |
+
color: isConnected ? '#10b981' : '#ef4444',
|
| 50 |
+
},
|
| 51 |
+
},
|
| 52 |
+
}}
|
| 53 |
+
/>
|
| 54 |
+
);
|
| 55 |
+
};
|
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useRef } from 'react';
|
| 2 |
+
import { AppBar, Toolbar, Box, Typography, Chip, IconButton, CircularProgress, keyframes } from '@mui/material';
|
| 3 |
+
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
| 4 |
+
import LightModeOutlined from '@mui/icons-material/LightModeOutlined';
|
| 5 |
+
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined';
|
| 6 |
+
import CheckIcon from '@mui/icons-material/Check';
|
| 7 |
+
import CloseIcon from '@mui/icons-material/Close';
|
| 8 |
+
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
| 9 |
+
import InputIcon from '@mui/icons-material/Input';
|
| 10 |
+
import OutputIcon from '@mui/icons-material/Output';
|
| 11 |
+
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
| 12 |
+
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
| 13 |
+
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
|
| 14 |
+
import { useAgentStore, selectTrace, selectError, selectIsDarkMode, selectMetadata, selectIsConnectingToE2B, selectFinalStep } from '@/stores/agentStore';
|
| 15 |
+
|
| 16 |
+
interface HeaderProps {
|
| 17 |
+
isAgentProcessing: boolean;
|
| 18 |
+
onBackToHome?: () => void;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
// Animation for the running task border - smooth oscillation (primary)
|
| 22 |
+
const borderPulse = keyframes`
|
| 23 |
+
0%, 100% {
|
| 24 |
+
border-color: rgba(79, 134, 198, 0.5);
|
| 25 |
+
box-shadow: 0 0 0 0 rgba(79, 134, 198, 0.3);
|
| 26 |
+
}
|
| 27 |
+
50% {
|
| 28 |
+
border-color: rgba(79, 134, 198, 1);
|
| 29 |
+
box-shadow: 0 0 8px 2px rgba(79, 134, 198, 0.4);
|
| 30 |
+
}
|
| 31 |
+
`;
|
| 32 |
+
|
| 33 |
+
// Animation for the background glow (primary)
|
| 34 |
+
const backgroundPulse = keyframes`
|
| 35 |
+
0%, 100% {
|
| 36 |
+
background-color: rgba(79, 134, 198, 0.08);
|
| 37 |
+
}
|
| 38 |
+
50% {
|
| 39 |
+
background-color: rgba(79, 134, 198, 0.15);
|
| 40 |
+
}
|
| 41 |
+
`;
|
| 42 |
+
|
| 43 |
+
// Animation for token flash - smooth glow effect
|
| 44 |
+
const tokenFlash = keyframes`
|
| 45 |
+
0% {
|
| 46 |
+
filter: brightness(1);
|
| 47 |
+
text-shadow: none;
|
| 48 |
+
}
|
| 49 |
+
25% {
|
| 50 |
+
filter: brightness(1.4);
|
| 51 |
+
text-shadow: 0 0 8px rgba(79, 134, 198, 0.6);
|
| 52 |
+
}
|
| 53 |
+
100% {
|
| 54 |
+
filter: brightness(1);
|
| 55 |
+
text-shadow: none;
|
| 56 |
+
}
|
| 57 |
+
`;
|
| 58 |
+
|
| 59 |
+
// Animation for token icon flash
|
| 60 |
+
const iconFlash = keyframes`
|
| 61 |
+
0% {
|
| 62 |
+
filter: brightness(1);
|
| 63 |
+
transform: scale(1);
|
| 64 |
+
}
|
| 65 |
+
25% {
|
| 66 |
+
filter: brightness(1.6);
|
| 67 |
+
transform: scale(1.15);
|
| 68 |
+
}
|
| 69 |
+
100% {
|
| 70 |
+
filter: brightness(1);
|
| 71 |
+
transform: scale(1);
|
| 72 |
+
}
|
| 73 |
+
`;
|
| 74 |
+
|
| 75 |
+
export const Header: React.FC<HeaderProps> = ({ isAgentProcessing, onBackToHome }) => {
|
| 76 |
+
const trace = useAgentStore(selectTrace);
|
| 77 |
+
const error = useAgentStore(selectError);
|
| 78 |
+
const finalStep = useAgentStore(selectFinalStep);
|
| 79 |
+
const isDarkMode = useAgentStore(selectIsDarkMode);
|
| 80 |
+
const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode);
|
| 81 |
+
const metadata = useAgentStore(selectMetadata);
|
| 82 |
+
const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B);
|
| 83 |
+
const [elapsedTime, setElapsedTime] = useState(0);
|
| 84 |
+
const [inputTokenFlash, setInputTokenFlash] = useState(false);
|
| 85 |
+
const [outputTokenFlash, setOutputTokenFlash] = useState(false);
|
| 86 |
+
const prevInputTokens = useRef(0);
|
| 87 |
+
const prevOutputTokens = useRef(0);
|
| 88 |
+
|
| 89 |
+
// Update elapsed time every 100ms when agent is processing
|
| 90 |
+
useEffect(() => {
|
| 91 |
+
if (isAgentProcessing && trace?.timestamp) {
|
| 92 |
+
const interval = setInterval(() => {
|
| 93 |
+
const now = new Date();
|
| 94 |
+
const startTime = new Date(trace.timestamp);
|
| 95 |
+
const elapsed = (now.getTime() - startTime.getTime()) / 1000;
|
| 96 |
+
setElapsedTime(elapsed);
|
| 97 |
+
}, 100);
|
| 98 |
+
|
| 99 |
+
return () => clearInterval(interval);
|
| 100 |
+
} else if (metadata && metadata.duration > 0) {
|
| 101 |
+
setElapsedTime(metadata.duration);
|
| 102 |
+
}
|
| 103 |
+
}, [isAgentProcessing, trace?.timestamp, metadata]);
|
| 104 |
+
|
| 105 |
+
// Detect token changes and trigger flash animation
|
| 106 |
+
useEffect(() => {
|
| 107 |
+
if (metadata) {
|
| 108 |
+
// Input tokens changed
|
| 109 |
+
if (metadata.inputTokensUsed > prevInputTokens.current && prevInputTokens.current > 0) {
|
| 110 |
+
setInputTokenFlash(true);
|
| 111 |
+
setTimeout(() => setInputTokenFlash(false), 800);
|
| 112 |
+
}
|
| 113 |
+
prevInputTokens.current = metadata.inputTokensUsed;
|
| 114 |
+
|
| 115 |
+
// Output tokens changed
|
| 116 |
+
if (metadata.outputTokensUsed > prevOutputTokens.current && prevOutputTokens.current > 0) {
|
| 117 |
+
setOutputTokenFlash(true);
|
| 118 |
+
setTimeout(() => setOutputTokenFlash(false), 800);
|
| 119 |
+
}
|
| 120 |
+
prevOutputTokens.current = metadata.outputTokensUsed;
|
| 121 |
+
}
|
| 122 |
+
}, [metadata?.inputTokensUsed, metadata?.outputTokensUsed]);
|
| 123 |
+
|
| 124 |
+
// Determine task status - Use finalStep as source of truth
|
| 125 |
+
const getTaskStatus = () => {
|
| 126 |
+
// If we have a final step, use its type
|
| 127 |
+
if (finalStep) {
|
| 128 |
+
if (finalStep.type === 'failure') {
|
| 129 |
+
return { label: 'Task failed', color: 'error', icon: <CloseIcon sx={{ fontSize: 16, color: 'error.main' }} /> };
|
| 130 |
+
}
|
| 131 |
+
return { label: 'Completed', color: 'success', icon: <CheckIcon sx={{ fontSize: 16, color: 'success.main' }} /> };
|
| 132 |
+
}
|
| 133 |
+
// Otherwise check running states
|
| 134 |
+
if (isConnectingToE2B) return { label: 'Connecting to E2B...', color: 'primary', icon: <CircularProgress size={16} thickness={5} sx={{ color: 'primary.main' }} /> };
|
| 135 |
+
if (isAgentProcessing || trace?.isRunning) return { label: 'Running', color: 'primary', icon: <CircularProgress size={16} thickness={5} sx={{ color: 'primary.main' }} /> };
|
| 136 |
+
return { label: 'Ready', color: 'default', icon: <CheckIcon sx={{ fontSize: 16, color: 'text.secondary' }} /> };
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
const taskStatus = getTaskStatus();
|
| 140 |
+
|
| 141 |
+
// Extract model name from modelId (e.g., "Qwen/Qwen3-VL-8B-Instruct" -> "Qwen3-VL-8B-Instruct")
|
| 142 |
+
const modelName = trace?.modelId?.split('/').pop() || 'Unknown Model';
|
| 143 |
+
|
| 144 |
+
return (
|
| 145 |
+
<AppBar
|
| 146 |
+
position="static"
|
| 147 |
+
elevation={0}
|
| 148 |
+
sx={{
|
| 149 |
+
backgroundColor: 'background.paper',
|
| 150 |
+
borderBottom: '1px solid',
|
| 151 |
+
borderColor: 'divider',
|
| 152 |
+
}}
|
| 153 |
+
>
|
| 154 |
+
<Toolbar disableGutters sx={{ px: 2, py: 2.5, flexDirection: 'column', alignItems: 'stretch', gap: 0 }}>
|
| 155 |
+
{/* First row: Back button + Task info + Connection Status */}
|
| 156 |
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', gap: 3 }}>
|
| 157 |
+
{/* Left side: Back button + Task info */}
|
| 158 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 1, minWidth: 0 }}>
|
| 159 |
+
<IconButton
|
| 160 |
+
onClick={onBackToHome}
|
| 161 |
+
size="small"
|
| 162 |
+
sx={{
|
| 163 |
+
color: 'primary.main',
|
| 164 |
+
backgroundColor: 'primary.50',
|
| 165 |
+
border: '1px solid',
|
| 166 |
+
borderColor: 'primary.200',
|
| 167 |
+
cursor: 'pointer',
|
| 168 |
+
'&:hover': {
|
| 169 |
+
backgroundColor: 'primary.100',
|
| 170 |
+
borderColor: 'primary.main',
|
| 171 |
+
},
|
| 172 |
+
}}
|
| 173 |
+
>
|
| 174 |
+
<ArrowBackIcon fontSize="small" />
|
| 175 |
+
</IconButton>
|
| 176 |
+
<Typography
|
| 177 |
+
variant="body2"
|
| 178 |
+
sx={{
|
| 179 |
+
color: 'text.primary',
|
| 180 |
+
fontWeight: 700,
|
| 181 |
+
fontSize: '1rem',
|
| 182 |
+
overflow: 'hidden',
|
| 183 |
+
textOverflow: 'ellipsis',
|
| 184 |
+
whiteSpace: 'nowrap',
|
| 185 |
+
}}
|
| 186 |
+
>
|
| 187 |
+
{trace?.instruction || 'No task running'}
|
| 188 |
+
</Typography>
|
| 189 |
+
</Box>
|
| 190 |
+
|
| 191 |
+
{/* Right side: Dark Mode */}
|
| 192 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
| 193 |
+
<IconButton
|
| 194 |
+
onClick={toggleDarkMode}
|
| 195 |
+
size="small"
|
| 196 |
+
sx={{
|
| 197 |
+
color: 'primary.main',
|
| 198 |
+
backgroundColor: 'primary.50',
|
| 199 |
+
border: '1px solid',
|
| 200 |
+
borderColor: 'primary.200',
|
| 201 |
+
'&:hover': {
|
| 202 |
+
backgroundColor: 'primary.100',
|
| 203 |
+
borderColor: 'primary.main',
|
| 204 |
+
},
|
| 205 |
+
}}
|
| 206 |
+
>
|
| 207 |
+
{isDarkMode ? <LightModeOutlined fontSize="small" /> : <DarkModeOutlined fontSize="small" />}
|
| 208 |
+
</IconButton>
|
| 209 |
+
</Box>
|
| 210 |
+
</Box>
|
| 211 |
+
|
| 212 |
+
{/* Second row: Status + Model + Metadata - Only show when we have trace data */}
|
| 213 |
+
{trace && (
|
| 214 |
+
<Box
|
| 215 |
+
sx={{
|
| 216 |
+
display: 'flex',
|
| 217 |
+
alignItems: 'center',
|
| 218 |
+
gap: 1.5,
|
| 219 |
+
pl: 5.5,
|
| 220 |
+
pr: 1,
|
| 221 |
+
pt: .5,
|
| 222 |
+
mt: .5,
|
| 223 |
+
}}
|
| 224 |
+
>
|
| 225 |
+
{/* Status Badge - Compact */}
|
| 226 |
+
<Box
|
| 227 |
+
sx={{
|
| 228 |
+
display: 'flex',
|
| 229 |
+
alignItems: 'center',
|
| 230 |
+
gap: 0.5,
|
| 231 |
+
px: 1,
|
| 232 |
+
py: 0.25,
|
| 233 |
+
borderRadius: 1,
|
| 234 |
+
backgroundColor:
|
| 235 |
+
taskStatus.color === 'primary' ? 'primary.50' :
|
| 236 |
+
taskStatus.color === 'success' ? 'success.50' :
|
| 237 |
+
taskStatus.color === 'error' ? 'error.50' :
|
| 238 |
+
taskStatus.color === 'warning' ? 'warning.50' :
|
| 239 |
+
'action.hover',
|
| 240 |
+
border: '1px solid',
|
| 241 |
+
borderColor:
|
| 242 |
+
taskStatus.color === 'primary' ? 'primary.main' :
|
| 243 |
+
taskStatus.color === 'success' ? 'success.main' :
|
| 244 |
+
taskStatus.color === 'error' ? 'error.main' :
|
| 245 |
+
taskStatus.color === 'warning' ? 'warning.main' :
|
| 246 |
+
'divider',
|
| 247 |
+
}}
|
| 248 |
+
>
|
| 249 |
+
{taskStatus.icon}
|
| 250 |
+
<Typography
|
| 251 |
+
variant="caption"
|
| 252 |
+
sx={{
|
| 253 |
+
fontSize: '0.7rem',
|
| 254 |
+
fontWeight: 700,
|
| 255 |
+
color:
|
| 256 |
+
taskStatus.color === 'primary' ? 'primary.main' :
|
| 257 |
+
taskStatus.color === 'success' ? 'success.main' :
|
| 258 |
+
taskStatus.color === 'error' ? 'error.main' :
|
| 259 |
+
taskStatus.color === 'warning' ? 'warning.main' :
|
| 260 |
+
'text.primary',
|
| 261 |
+
}}
|
| 262 |
+
>
|
| 263 |
+
{taskStatus.label}
|
| 264 |
+
</Typography>
|
| 265 |
+
</Box>
|
| 266 |
+
|
| 267 |
+
{/* Divider */}
|
| 268 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 269 |
+
|
| 270 |
+
{/* Model */}
|
| 271 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 272 |
+
<SmartToyIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 273 |
+
<Typography
|
| 274 |
+
variant="caption"
|
| 275 |
+
sx={{
|
| 276 |
+
fontSize: '0.75rem',
|
| 277 |
+
fontWeight: 600,
|
| 278 |
+
color: 'text.primary',
|
| 279 |
+
}}
|
| 280 |
+
>
|
| 281 |
+
{modelName}
|
| 282 |
+
</Typography>
|
| 283 |
+
</Box>
|
| 284 |
+
|
| 285 |
+
{/* Steps Count */}
|
| 286 |
+
{metadata && (
|
| 287 |
+
<>
|
| 288 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 289 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 290 |
+
<Typography
|
| 291 |
+
variant="caption"
|
| 292 |
+
sx={{
|
| 293 |
+
fontSize: '0.75rem',
|
| 294 |
+
fontWeight: 700,
|
| 295 |
+
color: 'text.primary',
|
| 296 |
+
mr: 0.5,
|
| 297 |
+
}}
|
| 298 |
+
>
|
| 299 |
+
{metadata.numberOfSteps}
|
| 300 |
+
</Typography>
|
| 301 |
+
<Typography
|
| 302 |
+
variant="caption"
|
| 303 |
+
sx={{
|
| 304 |
+
fontSize: '0.7rem',
|
| 305 |
+
fontWeight: 400,
|
| 306 |
+
color: 'text.secondary',
|
| 307 |
+
}}
|
| 308 |
+
>
|
| 309 |
+
{metadata.numberOfSteps === 1 ? 'Step' : 'Steps'}
|
| 310 |
+
</Typography>
|
| 311 |
+
</Box>
|
| 312 |
+
</>
|
| 313 |
+
)}
|
| 314 |
+
|
| 315 |
+
{/* Time */}
|
| 316 |
+
{(isAgentProcessing || metadata) && (
|
| 317 |
+
<>
|
| 318 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 319 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 320 |
+
<AccessTimeIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 321 |
+
<Typography
|
| 322 |
+
variant="caption"
|
| 323 |
+
sx={{
|
| 324 |
+
fontSize: '0.75rem',
|
| 325 |
+
fontWeight: 700,
|
| 326 |
+
color: 'text.primary',
|
| 327 |
+
minWidth: '45px',
|
| 328 |
+
textAlign: 'left',
|
| 329 |
+
}}
|
| 330 |
+
>
|
| 331 |
+
{elapsedTime.toFixed(1)}s
|
| 332 |
+
</Typography>
|
| 333 |
+
</Box>
|
| 334 |
+
</>
|
| 335 |
+
)}
|
| 336 |
+
|
| 337 |
+
{/* Input Tokens */}
|
| 338 |
+
{metadata && metadata.inputTokensUsed > 0 && (
|
| 339 |
+
<>
|
| 340 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 341 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 342 |
+
<InputIcon
|
| 343 |
+
sx={{
|
| 344 |
+
fontSize: '0.85rem',
|
| 345 |
+
color: 'primary.main',
|
| 346 |
+
transition: 'all 0.2s ease',
|
| 347 |
+
animation: inputTokenFlash ? `${iconFlash} 0.8s ease-out` : 'none',
|
| 348 |
+
}}
|
| 349 |
+
/>
|
| 350 |
+
<Box
|
| 351 |
+
sx={{
|
| 352 |
+
transition: 'all 0.2s ease',
|
| 353 |
+
animation: inputTokenFlash ? `${tokenFlash} 0.8s ease-out` : 'none',
|
| 354 |
+
}}
|
| 355 |
+
>
|
| 356 |
+
<Typography
|
| 357 |
+
variant="caption"
|
| 358 |
+
sx={{
|
| 359 |
+
fontSize: '0.75rem',
|
| 360 |
+
fontWeight: 700,
|
| 361 |
+
color: 'text.primary',
|
| 362 |
+
}}
|
| 363 |
+
>
|
| 364 |
+
{metadata.inputTokensUsed.toLocaleString()}
|
| 365 |
+
</Typography>
|
| 366 |
+
</Box>
|
| 367 |
+
</Box>
|
| 368 |
+
</>
|
| 369 |
+
)}
|
| 370 |
+
|
| 371 |
+
{/* Output Tokens */}
|
| 372 |
+
{metadata && metadata.outputTokensUsed > 0 && (
|
| 373 |
+
<>
|
| 374 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 375 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 376 |
+
<OutputIcon
|
| 377 |
+
sx={{
|
| 378 |
+
fontSize: '0.85rem',
|
| 379 |
+
color: 'primary.main',
|
| 380 |
+
transition: 'all 0.2s ease',
|
| 381 |
+
animation: outputTokenFlash ? `${iconFlash} 0.8s ease-out` : 'none',
|
| 382 |
+
}}
|
| 383 |
+
/>
|
| 384 |
+
<Box
|
| 385 |
+
sx={{
|
| 386 |
+
transition: 'all 0.2s ease',
|
| 387 |
+
animation: outputTokenFlash ? `${tokenFlash} 0.8s ease-out` : 'none',
|
| 388 |
+
}}
|
| 389 |
+
>
|
| 390 |
+
<Typography
|
| 391 |
+
variant="caption"
|
| 392 |
+
sx={{
|
| 393 |
+
fontSize: '0.75rem',
|
| 394 |
+
fontWeight: 700,
|
| 395 |
+
color: 'text.primary',
|
| 396 |
+
}}
|
| 397 |
+
>
|
| 398 |
+
{metadata.outputTokensUsed.toLocaleString()}
|
| 399 |
+
</Typography>
|
| 400 |
+
</Box>
|
| 401 |
+
</Box>
|
| 402 |
+
</>
|
| 403 |
+
)}
|
| 404 |
+
</Box>
|
| 405 |
+
)}
|
| 406 |
+
</Toolbar>
|
| 407 |
+
</AppBar>
|
| 408 |
+
);
|
| 409 |
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Box, CircularProgress, Typography } from '@mui/material';
|
| 3 |
+
|
| 4 |
+
interface ProcessingIndicatorProps {
|
| 5 |
+
isAgentProcessing: boolean;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export const ProcessingIndicator: React.FC<ProcessingIndicatorProps> = ({ isAgentProcessing }) => {
|
| 9 |
+
if (!isAgentProcessing) return null;
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<Box
|
| 13 |
+
sx={{
|
| 14 |
+
display: 'flex',
|
| 15 |
+
alignItems: 'center',
|
| 16 |
+
gap: 2,
|
| 17 |
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
| 18 |
+
px: 2,
|
| 19 |
+
py: 1,
|
| 20 |
+
borderRadius: 2,
|
| 21 |
+
backdropFilter: 'blur(10px)',
|
| 22 |
+
border: '1px solid rgba(0, 0, 0, 0.1)',
|
| 23 |
+
}}
|
| 24 |
+
>
|
| 25 |
+
<CircularProgress size={20} thickness={4} />
|
| 26 |
+
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
| 27 |
+
Agent is running...
|
| 28 |
+
</Typography>
|
| 29 |
+
</Box>
|
| 30 |
+
);
|
| 31 |
+
};
|
|
@@ -0,0 +1,452 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect, useRef } from 'react';
|
| 2 |
+
import { Box, Typography, Button, Container, Paper, TextField, IconButton, Select, MenuItem, FormControl, InputLabel, CircularProgress } from '@mui/material';
|
| 3 |
+
import ShuffleIcon from '@mui/icons-material/Shuffle';
|
| 4 |
+
import SendIcon from '@mui/icons-material/Send';
|
| 5 |
+
import LightModeOutlined from '@mui/icons-material/LightModeOutlined';
|
| 6 |
+
import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined';
|
| 7 |
+
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
| 8 |
+
import { useAgentStore, selectSelectedModelId, selectIsDarkMode, selectAvailableModels, selectIsLoadingModels } from '@/stores/agentStore';
|
| 9 |
+
import { fetchAvailableModels, generateRandomQuestion } from '@/services/api';
|
| 10 |
+
|
| 11 |
+
interface WelcomeScreenProps {
|
| 12 |
+
onStartTask: (instruction: string, modelId: string) => void;
|
| 13 |
+
isConnected: boolean;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({ onStartTask, isConnected }) => {
|
| 17 |
+
const [customTask, setCustomTask] = useState('');
|
| 18 |
+
const [isTyping, setIsTyping] = useState(false);
|
| 19 |
+
const [isGeneratingQuestion, setIsGeneratingQuestion] = useState(false);
|
| 20 |
+
const typingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
| 21 |
+
|
| 22 |
+
const isDarkMode = useAgentStore(selectIsDarkMode);
|
| 23 |
+
const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode);
|
| 24 |
+
const selectedModelId = useAgentStore(selectSelectedModelId);
|
| 25 |
+
const setSelectedModelId = useAgentStore((state) => state.setSelectedModelId);
|
| 26 |
+
const availableModels = useAgentStore(selectAvailableModels);
|
| 27 |
+
const isLoadingModels = useAgentStore(selectIsLoadingModels);
|
| 28 |
+
const setAvailableModels = useAgentStore((state) => state.setAvailableModels);
|
| 29 |
+
const setIsLoadingModels = useAgentStore((state) => state.setIsLoadingModels);
|
| 30 |
+
|
| 31 |
+
// Load available models on mount
|
| 32 |
+
useEffect(() => {
|
| 33 |
+
const loadModels = async () => {
|
| 34 |
+
setIsLoadingModels(true);
|
| 35 |
+
try {
|
| 36 |
+
const models = await fetchAvailableModels();
|
| 37 |
+
setAvailableModels(models);
|
| 38 |
+
|
| 39 |
+
// Set first model as default if current selection is not in the list
|
| 40 |
+
if (models.length > 0 && !models.includes(selectedModelId)) {
|
| 41 |
+
setSelectedModelId(models[0]);
|
| 42 |
+
}
|
| 43 |
+
} catch (error) {
|
| 44 |
+
console.error('Failed to load models:', error);
|
| 45 |
+
// Fallback to empty array on error
|
| 46 |
+
setAvailableModels([]);
|
| 47 |
+
} finally {
|
| 48 |
+
setIsLoadingModels(false);
|
| 49 |
+
}
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
loadModels();
|
| 53 |
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
| 54 |
+
|
| 55 |
+
// Clean up typing interval on unmount
|
| 56 |
+
useEffect(() => {
|
| 57 |
+
return () => {
|
| 58 |
+
if (typingIntervalRef.current) {
|
| 59 |
+
clearInterval(typingIntervalRef.current);
|
| 60 |
+
}
|
| 61 |
+
};
|
| 62 |
+
}, []);
|
| 63 |
+
|
| 64 |
+
const handleWriteRandomTask = async () => {
|
| 65 |
+
// Clear any existing typing interval
|
| 66 |
+
if (typingIntervalRef.current) {
|
| 67 |
+
clearInterval(typingIntervalRef.current);
|
| 68 |
+
typingIntervalRef.current = null;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
setIsGeneratingQuestion(true);
|
| 72 |
+
try {
|
| 73 |
+
const randomTask = await generateRandomQuestion(selectedModelId);
|
| 74 |
+
|
| 75 |
+
// Clear current text
|
| 76 |
+
setCustomTask('');
|
| 77 |
+
setIsTyping(true);
|
| 78 |
+
|
| 79 |
+
// Type effect
|
| 80 |
+
let currentIndex = 0;
|
| 81 |
+
typingIntervalRef.current = setInterval(() => {
|
| 82 |
+
if (currentIndex < randomTask.length) {
|
| 83 |
+
setCustomTask(randomTask.substring(0, currentIndex + 1));
|
| 84 |
+
currentIndex++;
|
| 85 |
+
} else {
|
| 86 |
+
if (typingIntervalRef.current) {
|
| 87 |
+
clearInterval(typingIntervalRef.current);
|
| 88 |
+
typingIntervalRef.current = null;
|
| 89 |
+
}
|
| 90 |
+
setIsTyping(false);
|
| 91 |
+
}
|
| 92 |
+
}, 30); // 30ms per character
|
| 93 |
+
} catch (error) {
|
| 94 |
+
console.error('Failed to generate question:', error);
|
| 95 |
+
setIsTyping(false);
|
| 96 |
+
} finally {
|
| 97 |
+
setIsGeneratingQuestion(false);
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
const handleCustomTask = () => {
|
| 102 |
+
if (customTask.trim() && !isTyping) {
|
| 103 |
+
onStartTask(customTask.trim(), selectedModelId);
|
| 104 |
+
}
|
| 105 |
+
};
|
| 106 |
+
|
| 107 |
+
return (
|
| 108 |
+
<>
|
| 109 |
+
{/* Dark Mode Toggle - Top Right (Absolute to viewport) */}
|
| 110 |
+
<Box sx={{ position: 'absolute', top: 24, right: 24, zIndex: 1000 }}>
|
| 111 |
+
<IconButton
|
| 112 |
+
onClick={toggleDarkMode}
|
| 113 |
+
size="medium"
|
| 114 |
+
sx={{
|
| 115 |
+
color: 'text.primary',
|
| 116 |
+
backgroundColor: 'background.paper',
|
| 117 |
+
border: '1px solid',
|
| 118 |
+
borderColor: 'divider',
|
| 119 |
+
'&:hover': {
|
| 120 |
+
backgroundColor: 'action.hover',
|
| 121 |
+
borderColor: 'primary.main',
|
| 122 |
+
},
|
| 123 |
+
}}
|
| 124 |
+
>
|
| 125 |
+
{isDarkMode ? <LightModeOutlined /> : <DarkModeOutlined />}
|
| 126 |
+
</IconButton>
|
| 127 |
+
</Box>
|
| 128 |
+
|
| 129 |
+
<Container
|
| 130 |
+
maxWidth="md"
|
| 131 |
+
sx={{
|
| 132 |
+
display: 'flex',
|
| 133 |
+
flexDirection: 'column',
|
| 134 |
+
alignItems: 'center',
|
| 135 |
+
justifyContent: 'center',
|
| 136 |
+
minHeight: '100vh',
|
| 137 |
+
textAlign: 'center',
|
| 138 |
+
py: 8,
|
| 139 |
+
}}
|
| 140 |
+
>
|
| 141 |
+
{/* Title */}
|
| 142 |
+
<Typography
|
| 143 |
+
variant="h2"
|
| 144 |
+
sx={{
|
| 145 |
+
fontWeight: 800,
|
| 146 |
+
mb: 1,
|
| 147 |
+
color: 'text.primary',
|
| 148 |
+
}}
|
| 149 |
+
>
|
| 150 |
+
CUA2 Agent
|
| 151 |
+
</Typography>
|
| 152 |
+
|
| 153 |
+
{/* Powered by smolagents */}
|
| 154 |
+
<Box
|
| 155 |
+
sx={{
|
| 156 |
+
display: 'flex',
|
| 157 |
+
alignItems: 'center',
|
| 158 |
+
gap: 1,
|
| 159 |
+
mb: 2,
|
| 160 |
+
}}
|
| 161 |
+
>
|
| 162 |
+
<Typography
|
| 163 |
+
variant="body2"
|
| 164 |
+
sx={{
|
| 165 |
+
color: 'text.secondary',
|
| 166 |
+
fontWeight: 500,
|
| 167 |
+
}}
|
| 168 |
+
>
|
| 169 |
+
Powered by
|
| 170 |
+
</Typography>
|
| 171 |
+
<Box
|
| 172 |
+
component="a"
|
| 173 |
+
href="https://github.com/huggingface/smolagents"
|
| 174 |
+
target="_blank"
|
| 175 |
+
rel="noopener noreferrer"
|
| 176 |
+
sx={{
|
| 177 |
+
display: 'flex',
|
| 178 |
+
alignItems: 'center',
|
| 179 |
+
gap: 0.75,
|
| 180 |
+
textDecoration: 'none',
|
| 181 |
+
transition: 'all 0.2s ease',
|
| 182 |
+
'&:hover': {
|
| 183 |
+
'& .smolagents-text': {
|
| 184 |
+
textDecoration: 'underline',
|
| 185 |
+
},
|
| 186 |
+
},
|
| 187 |
+
}}
|
| 188 |
+
>
|
| 189 |
+
{/* Hugging Face Official Logo */}
|
| 190 |
+
<Box
|
| 191 |
+
component="img"
|
| 192 |
+
src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
|
| 193 |
+
alt="Hugging Face"
|
| 194 |
+
sx={{
|
| 195 |
+
width: 24,
|
| 196 |
+
height: 24,
|
| 197 |
+
flexShrink: 0,
|
| 198 |
+
}}
|
| 199 |
+
/>
|
| 200 |
+
|
| 201 |
+
<Typography
|
| 202 |
+
className="smolagents-text"
|
| 203 |
+
sx={{
|
| 204 |
+
color: 'primary.main',
|
| 205 |
+
fontWeight: 700,
|
| 206 |
+
fontSize: '1rem',
|
| 207 |
+
}}
|
| 208 |
+
>
|
| 209 |
+
smolagents
|
| 210 |
+
</Typography>
|
| 211 |
+
|
| 212 |
+
{/* GitHub stars badge */}
|
| 213 |
+
<Box
|
| 214 |
+
sx={{
|
| 215 |
+
display: 'flex',
|
| 216 |
+
alignItems: 'center',
|
| 217 |
+
gap: 0.5,
|
| 218 |
+
px: 1,
|
| 219 |
+
py: 0.25,
|
| 220 |
+
backgroundColor: (theme) =>
|
| 221 |
+
theme.palette.mode === 'dark'
|
| 222 |
+
? 'rgba(144, 202, 249, 0.08)'
|
| 223 |
+
: 'rgba(25, 118, 210, 0.08)',
|
| 224 |
+
borderRadius: 1,
|
| 225 |
+
border: '1px solid',
|
| 226 |
+
borderColor: 'primary.main',
|
| 227 |
+
}}
|
| 228 |
+
>
|
| 229 |
+
<Box component="span" sx={{ fontSize: '0.75rem' }}>⭐</Box>
|
| 230 |
+
<Typography
|
| 231 |
+
variant="caption"
|
| 232 |
+
sx={{
|
| 233 |
+
fontWeight: 700,
|
| 234 |
+
color: 'primary.main',
|
| 235 |
+
fontSize: '0.75rem',
|
| 236 |
+
}}
|
| 237 |
+
>
|
| 238 |
+
23.7k
|
| 239 |
+
</Typography>
|
| 240 |
+
</Box>
|
| 241 |
+
</Box>
|
| 242 |
+
</Box>
|
| 243 |
+
|
| 244 |
+
{/* Subtitle */}
|
| 245 |
+
<Typography
|
| 246 |
+
variant="h6"
|
| 247 |
+
sx={{
|
| 248 |
+
color: 'text.secondary',
|
| 249 |
+
fontWeight: 500,
|
| 250 |
+
mb: 1,
|
| 251 |
+
}}
|
| 252 |
+
>
|
| 253 |
+
AI-Powered Computer Use Automation
|
| 254 |
+
</Typography>
|
| 255 |
+
|
| 256 |
+
{/* Description */}
|
| 257 |
+
<Typography
|
| 258 |
+
variant="body1"
|
| 259 |
+
sx={{
|
| 260 |
+
color: 'text.secondary',
|
| 261 |
+
maxWidth: '650px',
|
| 262 |
+
mb: 6,
|
| 263 |
+
lineHeight: 1.7,
|
| 264 |
+
}}
|
| 265 |
+
>
|
| 266 |
+
Watch in real-time as AI agents write and execute Python code to complete tasks.
|
| 267 |
+
Built by Hugging Face, <strong>smolagents</strong> is LLM-agnostic and uses <strong>30% fewer steps</strong> than traditional agents.
|
| 268 |
+
</Typography>
|
| 269 |
+
|
| 270 |
+
{/* Task Input Section */}
|
| 271 |
+
<Paper
|
| 272 |
+
elevation={0}
|
| 273 |
+
sx={{
|
| 274 |
+
maxWidth: '700px',
|
| 275 |
+
width: '100%',
|
| 276 |
+
p: 2.5,
|
| 277 |
+
border: '2px solid',
|
| 278 |
+
borderColor: isConnected ? 'primary.main' : 'divider',
|
| 279 |
+
borderRadius: 2,
|
| 280 |
+
backgroundColor: 'background.paper',
|
| 281 |
+
transition: 'all 0.2s ease',
|
| 282 |
+
'&:hover': isConnected ? {
|
| 283 |
+
borderColor: 'primary.dark',
|
| 284 |
+
boxShadow: (theme) => `0 4px 16px ${theme.palette.mode === 'dark' ? 'rgba(79, 134, 198, 0.3)' : 'rgba(79, 134, 198, 0.15)'}`,
|
| 285 |
+
} : {},
|
| 286 |
+
}}
|
| 287 |
+
>
|
| 288 |
+
{/* Input Field */}
|
| 289 |
+
<TextField
|
| 290 |
+
fullWidth
|
| 291 |
+
placeholder="Describe your task here..."
|
| 292 |
+
value={customTask}
|
| 293 |
+
onChange={(e) => setCustomTask(e.target.value)}
|
| 294 |
+
onKeyPress={(e) => {
|
| 295 |
+
if (e.key === 'Enter' && !e.shiftKey && isConnected && customTask.trim() && !isTyping) {
|
| 296 |
+
handleCustomTask();
|
| 297 |
+
}
|
| 298 |
+
}}
|
| 299 |
+
disabled={!isConnected || isTyping}
|
| 300 |
+
multiline
|
| 301 |
+
rows={3}
|
| 302 |
+
sx={{
|
| 303 |
+
mb: 2,
|
| 304 |
+
'& .MuiOutlinedInput-root': {
|
| 305 |
+
borderRadius: 1.5,
|
| 306 |
+
backgroundColor: 'action.hover',
|
| 307 |
+
color: 'text.primary',
|
| 308 |
+
'& fieldset': {
|
| 309 |
+
borderColor: 'divider',
|
| 310 |
+
},
|
| 311 |
+
'&:hover fieldset': {
|
| 312 |
+
borderColor: 'text.secondary',
|
| 313 |
+
},
|
| 314 |
+
'&.Mui-focused fieldset': {
|
| 315 |
+
borderColor: 'primary.main',
|
| 316 |
+
borderWidth: '2px',
|
| 317 |
+
},
|
| 318 |
+
},
|
| 319 |
+
'& .MuiInputBase-input': {
|
| 320 |
+
color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important',
|
| 321 |
+
fontWeight: 500,
|
| 322 |
+
WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important',
|
| 323 |
+
},
|
| 324 |
+
'& .MuiInputBase-input.Mui-disabled': {
|
| 325 |
+
color: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important',
|
| 326 |
+
WebkitTextFillColor: (theme) => theme.palette.mode === 'dark' ? '#FFFFFF !important' : '#000000 !important',
|
| 327 |
+
},
|
| 328 |
+
'& .MuiInputBase-input::placeholder': {
|
| 329 |
+
color: 'text.secondary',
|
| 330 |
+
opacity: 0.7,
|
| 331 |
+
},
|
| 332 |
+
}}
|
| 333 |
+
/>
|
| 334 |
+
|
| 335 |
+
{/* Model Selection + Buttons Row */}
|
| 336 |
+
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', justifyContent: 'space-between' }}>
|
| 337 |
+
{/* Model Select */}
|
| 338 |
+
<FormControl size="small" sx={{ minWidth: 240 }}>
|
| 339 |
+
<InputLabel id="model-select-label">Model</InputLabel>
|
| 340 |
+
<Select
|
| 341 |
+
labelId="model-select-label"
|
| 342 |
+
value={availableModels.length > 0 && availableModels.includes(selectedModelId) ? selectedModelId : ''}
|
| 343 |
+
label="Model"
|
| 344 |
+
onChange={(e) => setSelectedModelId(e.target.value)}
|
| 345 |
+
disabled={!isConnected || isTyping || isLoadingModels}
|
| 346 |
+
sx={{
|
| 347 |
+
borderRadius: 1.5,
|
| 348 |
+
'& .MuiOutlinedInput-notchedOutline': {
|
| 349 |
+
borderWidth: 2,
|
| 350 |
+
},
|
| 351 |
+
}}
|
| 352 |
+
>
|
| 353 |
+
{isLoadingModels ? (
|
| 354 |
+
<MenuItem disabled>
|
| 355 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
| 356 |
+
<CircularProgress size={16} />
|
| 357 |
+
<Typography variant="body2">Loading models...</Typography>
|
| 358 |
+
</Box>
|
| 359 |
+
</MenuItem>
|
| 360 |
+
) : availableModels.length === 0 ? (
|
| 361 |
+
<MenuItem disabled>
|
| 362 |
+
<Typography variant="body2" sx={{ color: 'error.main' }}>
|
| 363 |
+
No models available
|
| 364 |
+
</Typography>
|
| 365 |
+
</MenuItem>
|
| 366 |
+
) : (
|
| 367 |
+
availableModels.map((modelId) => (
|
| 368 |
+
<MenuItem key={modelId} value={modelId}>
|
| 369 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
| 370 |
+
<SmartToyIcon sx={{ fontSize: '0.9rem', color: 'primary.main' }} />
|
| 371 |
+
<Typography variant="body2" sx={{ fontWeight: 600, fontSize: '0.875rem' }}>
|
| 372 |
+
{modelId.split('/').pop()}
|
| 373 |
+
</Typography>
|
| 374 |
+
</Box>
|
| 375 |
+
</MenuItem>
|
| 376 |
+
))
|
| 377 |
+
)}
|
| 378 |
+
</Select>
|
| 379 |
+
</FormControl>
|
| 380 |
+
|
| 381 |
+
{/* Buttons on the right */}
|
| 382 |
+
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
| 383 |
+
<Button
|
| 384 |
+
variant="outlined"
|
| 385 |
+
onClick={handleWriteRandomTask}
|
| 386 |
+
disabled={!isConnected || isTyping || isGeneratingQuestion}
|
| 387 |
+
startIcon={isGeneratingQuestion ? <CircularProgress size={16} /> : <ShuffleIcon />}
|
| 388 |
+
sx={{
|
| 389 |
+
borderRadius: 1.5,
|
| 390 |
+
textTransform: 'none',
|
| 391 |
+
fontWeight: 600,
|
| 392 |
+
borderWidth: 2,
|
| 393 |
+
px: 3,
|
| 394 |
+
'&:hover': {
|
| 395 |
+
borderWidth: 2,
|
| 396 |
+
},
|
| 397 |
+
}}
|
| 398 |
+
>
|
| 399 |
+
{isGeneratingQuestion ? 'Generating...' : isTyping ? 'Writing...' : 'Write random task'}
|
| 400 |
+
</Button>
|
| 401 |
+
|
| 402 |
+
<Button
|
| 403 |
+
variant="contained"
|
| 404 |
+
onClick={handleCustomTask}
|
| 405 |
+
disabled={!isConnected || !customTask.trim() || isTyping}
|
| 406 |
+
sx={{
|
| 407 |
+
borderRadius: 1.5,
|
| 408 |
+
textTransform: 'none',
|
| 409 |
+
fontWeight: 600,
|
| 410 |
+
px: 4,
|
| 411 |
+
background: 'linear-gradient(135deg, #4F86C6 0%, #2B5C94 100%)',
|
| 412 |
+
}}
|
| 413 |
+
endIcon={<SendIcon />}
|
| 414 |
+
>
|
| 415 |
+
Run Task
|
| 416 |
+
</Button>
|
| 417 |
+
</Box>
|
| 418 |
+
</Box>
|
| 419 |
+
</Paper>
|
| 420 |
+
|
| 421 |
+
{/* Connection status hint */}
|
| 422 |
+
{!isConnected && (
|
| 423 |
+
<Typography
|
| 424 |
+
variant="caption"
|
| 425 |
+
sx={{
|
| 426 |
+
mt: 2,
|
| 427 |
+
color: 'text.secondary',
|
| 428 |
+
display: 'flex',
|
| 429 |
+
alignItems: 'center',
|
| 430 |
+
gap: 1,
|
| 431 |
+
}}
|
| 432 |
+
>
|
| 433 |
+
<Box
|
| 434 |
+
sx={{
|
| 435 |
+
width: 8,
|
| 436 |
+
height: 8,
|
| 437 |
+
borderRadius: '50%',
|
| 438 |
+
backgroundColor: 'warning.main',
|
| 439 |
+
animation: 'pulse 2s ease-in-out infinite',
|
| 440 |
+
'@keyframes pulse': {
|
| 441 |
+
'0%, 100%': { opacity: 1 },
|
| 442 |
+
'50%': { opacity: 0.5 },
|
| 443 |
+
},
|
| 444 |
+
}}
|
| 445 |
+
/>
|
| 446 |
+
Make sure the backend is running on port 8000
|
| 447 |
+
</Typography>
|
| 448 |
+
)}
|
| 449 |
+
</Container>
|
| 450 |
+
</>
|
| 451 |
+
);
|
| 452 |
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// General components
|
| 2 |
+
export { Header } from './Header';
|
| 3 |
+
export { ConnectionStatus } from './ConnectionStatus';
|
| 4 |
+
export { ProcessingIndicator } from './ProcessingIndicator';
|
| 5 |
+
export { WelcomeScreen } from './WelcomeScreen';
|
| 6 |
+
|
| 7 |
+
// Sandbox components
|
| 8 |
+
export { SandboxViewer, CompletionView, DownloadGifButton, DownloadJsonButton } from './sandbox';
|
| 9 |
+
|
| 10 |
+
// Timeline components
|
| 11 |
+
export { Timeline } from './timeline';
|
| 12 |
+
|
| 13 |
+
// Steps components
|
| 14 |
+
export { StepsList, StepCard, FinalStepCard, ThinkingStepCard, ConnectionStepCard } from './steps';
|
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
|
| 3 |
-
interface ConnectionStatusProps {
|
| 4 |
-
isConnected: boolean;
|
| 5 |
-
}
|
| 6 |
-
|
| 7 |
-
export const ConnectionStatus: React.FC<ConnectionStatusProps> = ({ isConnected }) => {
|
| 8 |
-
return (
|
| 9 |
-
<div style={{
|
| 10 |
-
display: 'flex',
|
| 11 |
-
alignItems: 'center',
|
| 12 |
-
gap: '8px',
|
| 13 |
-
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
| 14 |
-
padding: '8px 16px',
|
| 15 |
-
borderRadius: '20px',
|
| 16 |
-
backdropFilter: 'blur(10px)',
|
| 17 |
-
border: '1px solid rgba(255, 255, 255, 0.3)'
|
| 18 |
-
}}>
|
| 19 |
-
<div style={{
|
| 20 |
-
width: '8px',
|
| 21 |
-
height: '8px',
|
| 22 |
-
borderRadius: '50%',
|
| 23 |
-
backgroundColor: isConnected ? '#10b981' : '#ef4444',
|
| 24 |
-
boxShadow: isConnected ? '0 0 8px #10b981' : '0 0 8px #ef4444',
|
| 25 |
-
animation: isConnected ? 'pulse 2s infinite' : 'none'
|
| 26 |
-
}}></div>
|
| 27 |
-
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
| 28 |
-
<span className="text-xs font-semibold text-white" style={{ lineHeight: '1.2' }}>
|
| 29 |
-
{isConnected ? 'Connected' : 'Disconnected'}
|
| 30 |
-
</span>
|
| 31 |
-
<span className="text-xs text-white" style={{ opacity: 0.7, fontSize: '10px', lineHeight: '1.2' }}>
|
| 32 |
-
WebSocket
|
| 33 |
-
</span>
|
| 34 |
-
</div>
|
| 35 |
-
</div>
|
| 36 |
-
);
|
| 37 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,47 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
import { ConnectionStatus } from './ConnectionStatus';
|
| 3 |
-
import { ProcessingIndicator } from './ProcessingIndicator';
|
| 4 |
-
import { TaskButton } from './TaskButton';
|
| 5 |
-
|
| 6 |
-
interface HeaderProps {
|
| 7 |
-
isConnected: boolean;
|
| 8 |
-
isAgentProcessing: boolean;
|
| 9 |
-
onSendTask: (content: string, modelId: string) => void;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
export const Header: React.FC<HeaderProps> = ({ isConnected, isAgentProcessing, onSendTask }) => {
|
| 13 |
-
return (
|
| 14 |
-
<>
|
| 15 |
-
<div style={{
|
| 16 |
-
flexShrink: 0,
|
| 17 |
-
}}>
|
| 18 |
-
<div style={{ maxWidth: '1400px', margin: '0 auto', padding: '20px 32px' }}>
|
| 19 |
-
<div className="flex items-center justify-between">
|
| 20 |
-
<div className="flex items-center gap-6">
|
| 21 |
-
<ConnectionStatus isConnected={isConnected} />
|
| 22 |
-
<h1 className="text-3xl font-bold text-white" style={{ textShadow: '0 2px 4px rgba(0, 0, 0, 0.2)' }}>
|
| 23 |
-
CUA2 Agent
|
| 24 |
-
</h1>
|
| 25 |
-
</div>
|
| 26 |
-
<ProcessingIndicator isAgentProcessing={isAgentProcessing} />
|
| 27 |
-
</div>
|
| 28 |
-
<TaskButton
|
| 29 |
-
isAgentProcessing={isAgentProcessing}
|
| 30 |
-
isConnected={isConnected}
|
| 31 |
-
onSendTask={onSendTask}
|
| 32 |
-
/>
|
| 33 |
-
</div>
|
| 34 |
-
</div>
|
| 35 |
-
|
| 36 |
-
<style>{`
|
| 37 |
-
@keyframes spin {
|
| 38 |
-
to { transform: rotate(360deg); }
|
| 39 |
-
}
|
| 40 |
-
@keyframes pulse {
|
| 41 |
-
0%, 100% { opacity: 1; }
|
| 42 |
-
50% { opacity: 0.5; }
|
| 43 |
-
}
|
| 44 |
-
`}</style>
|
| 45 |
-
</>
|
| 46 |
-
);
|
| 47 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import { AgentTrace } from '@/types/agent';
|
| 2 |
-
import React from 'react';
|
| 3 |
-
|
| 4 |
-
interface MetadataProps {
|
| 5 |
-
trace?: AgentTrace;
|
| 6 |
-
}
|
| 7 |
-
|
| 8 |
-
export const Metadata: React.FC<MetadataProps> = ({ trace }) => {
|
| 9 |
-
return (
|
| 10 |
-
<div style={{ flexShrink: 0 }} className="bg-white rounded-lg shadow-md border border-gray-200 p-5">
|
| 11 |
-
<h3 className="text-lg font-semibold text-gray-800 mb-4">Metadata</h3>
|
| 12 |
-
{trace?.metadata ? (
|
| 13 |
-
<div style={{ display: 'flex', gap: '12px' }}>
|
| 14 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#eff6ff', borderRadius: '8px', padding: '12px', border: '1px solid #bfdbfe', boxShadow: '0 2px 4px rgba(59, 130, 246, 0.1)' }}>
|
| 15 |
-
<span style={{ fontSize: '10px', fontWeight: 600, color: '#2563eb', textTransform: 'uppercase', letterSpacing: '0.8px', marginBottom: '6px' }}>Total Time</span>
|
| 16 |
-
<span style={{ fontSize: '24px', fontWeight: 700, color: '#1e40af' }}>{trace.metadata.duration.toFixed(2)}s</span>
|
| 17 |
-
</div>
|
| 18 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0fdf4', borderRadius: '8px', padding: '12px', border: '1px solid #bbf7d0', boxShadow: '0 2px 4px rgba(16, 185, 129, 0.1)' }}>
|
| 19 |
-
<span style={{ fontSize: '10px', fontWeight: 600, color: '#059669', textTransform: 'uppercase', letterSpacing: '0.8px', marginBottom: '6px' }}>In Tokens</span>
|
| 20 |
-
<span style={{ fontSize: '24px', fontWeight: 700, color: '#166534' }}>{trace.metadata.inputTokensUsed.toLocaleString()}</span>
|
| 21 |
-
</div>
|
| 22 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#faf5ff', borderRadius: '8px', padding: '12px', border: '1px solid #e9d5ff', boxShadow: '0 2px 4px rgba(139, 92, 246, 0.1)' }}>
|
| 23 |
-
<span style={{ fontSize: '10px', fontWeight: 600, color: '#9333ea', textTransform: 'uppercase', letterSpacing: '0.8px', marginBottom: '6px' }}>Out Tokens</span>
|
| 24 |
-
<span style={{ fontSize: '24px', fontWeight: 700, color: '#6b21a8' }}>{trace.metadata.outputTokensUsed.toLocaleString()}</span>
|
| 25 |
-
</div>
|
| 26 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#fff7ed', borderRadius: '8px', padding: '12px', border: '1px solid #fed7aa', boxShadow: '0 2px 4px rgba(249, 115, 22, 0.1)' }}>
|
| 27 |
-
<span style={{ fontSize: '10px', fontWeight: 600, color: '#ea580c', textTransform: 'uppercase', letterSpacing: '0.8px', marginBottom: '6px' }}>Total Steps</span>
|
| 28 |
-
<span style={{ fontSize: '24px', fontWeight: 700, color: '#c2410c' }}>{trace.metadata.numberOfSteps}</span>
|
| 29 |
-
</div>
|
| 30 |
-
</div>
|
| 31 |
-
) : (
|
| 32 |
-
<div className="text-gray-400 text-sm py-2">
|
| 33 |
-
{trace ? 'Waiting for completion...' : 'No task started yet'}
|
| 34 |
-
</div>
|
| 35 |
-
)}
|
| 36 |
-
</div>
|
| 37 |
-
);
|
| 38 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
|
| 3 |
-
interface ProcessingIndicatorProps {
|
| 4 |
-
isAgentProcessing: boolean;
|
| 5 |
-
}
|
| 6 |
-
|
| 7 |
-
export const ProcessingIndicator: React.FC<ProcessingIndicatorProps> = ({ isAgentProcessing }) => {
|
| 8 |
-
if (!isAgentProcessing) return null;
|
| 9 |
-
|
| 10 |
-
return (
|
| 11 |
-
<div style={{
|
| 12 |
-
display: 'flex',
|
| 13 |
-
alignItems: 'center',
|
| 14 |
-
gap: '10px',
|
| 15 |
-
padding: '10px 20px',
|
| 16 |
-
backgroundColor: 'rgba(251, 191, 36, 0.2)',
|
| 17 |
-
borderRadius: '10px',
|
| 18 |
-
border: '1px solid rgba(251, 191, 36, 0.4)'
|
| 19 |
-
}}>
|
| 20 |
-
<span style={{
|
| 21 |
-
width: '16px',
|
| 22 |
-
height: '16px',
|
| 23 |
-
border: '2px solid #fbbf24',
|
| 24 |
-
borderTopColor: 'transparent',
|
| 25 |
-
borderRadius: '50%',
|
| 26 |
-
animation: 'spin 1s linear infinite',
|
| 27 |
-
display: 'inline-block'
|
| 28 |
-
}}></span>
|
| 29 |
-
<span style={{ fontSize: '14px', fontWeight: 600, color: '#fbbf24', letterSpacing: '0.5px' }}>
|
| 30 |
-
PROCESSING...
|
| 31 |
-
</span>
|
| 32 |
-
</div>
|
| 33 |
-
);
|
| 34 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
import { AgentTrace } from '@/types/agent';
|
| 3 |
-
import { StepCard } from './StepCard';
|
| 4 |
-
|
| 5 |
-
interface StackStepsProps {
|
| 6 |
-
trace?: AgentTrace;
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
export const StackSteps: React.FC<StackStepsProps> = ({ trace }) => {
|
| 10 |
-
return (
|
| 11 |
-
<div style={{ width: '360px', flexShrink: 0, display: 'flex', flexDirection: 'column', backgroundColor: 'white', borderRadius: '10px', marginLeft: '12px', marginTop: '20px', marginBottom: '20px', boxShadow: '0 2px 12px rgba(0, 0, 0, 0.08)', border: '1px solid #e5e7eb' }}>
|
| 12 |
-
<h3 className="text-lg font-semibold text-gray-800 mb-4">Stack Steps</h3>
|
| 13 |
-
<div style={{ flex: 1, overflowY: 'auto', minHeight: 0, padding: '16px' }}>
|
| 14 |
-
{trace?.steps && trace.steps.length > 0 ? (
|
| 15 |
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
| 16 |
-
{trace.steps.map((step, index) => (
|
| 17 |
-
<StepCard key={step.stepId} step={step} index={index} />
|
| 18 |
-
))}
|
| 19 |
-
</div>
|
| 20 |
-
) : (
|
| 21 |
-
<div className="flex flex-col items-center justify-center h-full text-gray-400 p-6 text-center">
|
| 22 |
-
<p className="font-medium">No steps yet</p>
|
| 23 |
-
<p className="text-xs mt-1">Steps will appear as agent progresses</p>
|
| 24 |
-
</div>
|
| 25 |
-
)}
|
| 26 |
-
</div>
|
| 27 |
-
</div>
|
| 28 |
-
);
|
| 29 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,84 +0,0 @@
|
|
| 1 |
-
import { AgentStep } from '@/types/agent';
|
| 2 |
-
import React from 'react';
|
| 3 |
-
|
| 4 |
-
interface StepCardProps {
|
| 5 |
-
step: AgentStep;
|
| 6 |
-
index: number;
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
export const StepCard: React.FC<StepCardProps> = ({ step, index }) => {
|
| 10 |
-
return (
|
| 11 |
-
<div
|
| 12 |
-
key={step.stepId}
|
| 13 |
-
style={{ backgroundColor: '#f9fafb', borderRadius: '8px', border: '1px solid #d1d5db', padding: '12px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)' }}
|
| 14 |
-
className="hover:border-blue-400 transition-all"
|
| 15 |
-
>
|
| 16 |
-
{/* Step Header */}
|
| 17 |
-
<div className="mb-6">
|
| 18 |
-
<span className="text-xs font-bold text-blue-600 uppercase tracking-wide">Step {index + 1}</span>
|
| 19 |
-
<hr style={{ margin: '12px 0', border: 'none', borderTop: '2px solid #d1d5db' }} />
|
| 20 |
-
</div>
|
| 21 |
-
|
| 22 |
-
{/* Step Image */}
|
| 23 |
-
{step.image && (
|
| 24 |
-
<div className="mb-6">
|
| 25 |
-
<div className="rounded-md overflow-hidden border border-gray-300 bg-white" style={{ maxHeight: '140px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
| 26 |
-
<img
|
| 27 |
-
src={step.image}
|
| 28 |
-
alt={`Step ${index + 1}`}
|
| 29 |
-
style={{ width: '100%', height: 'auto', maxHeight: '140px', objectFit: 'contain' }}
|
| 30 |
-
/>
|
| 31 |
-
</div>
|
| 32 |
-
<hr style={{ margin: '20px 0', border: 'none', borderTop: '1px solid #e5e7eb' }} />
|
| 33 |
-
</div>
|
| 34 |
-
)}
|
| 35 |
-
|
| 36 |
-
{/* Thought */}
|
| 37 |
-
<div className="mb-6">
|
| 38 |
-
<div className="bg-white rounded-md p-2.5 border border-gray-200">
|
| 39 |
-
<h4 className="text-xs font-semibold text-gray-700 mb-1.5 flex items-center gap-1">
|
| 40 |
-
<span>💭</span>
|
| 41 |
-
<span>Thought</span>
|
| 42 |
-
</h4>
|
| 43 |
-
<p className="text-xs text-gray-600 leading-relaxed">{step.thought}</p>
|
| 44 |
-
</div>
|
| 45 |
-
<hr style={{ margin: '20px 0', border: 'none', borderTop: '1px solid #e5e7eb' }} />
|
| 46 |
-
</div>
|
| 47 |
-
|
| 48 |
-
{/* Actions */}
|
| 49 |
-
<div className="mb-6">
|
| 50 |
-
<div className="bg-white rounded-md p-2.5 border border-gray-200">
|
| 51 |
-
<h4 className="text-xs font-semibold text-gray-700 mb-1.5 flex items-center gap-1">
|
| 52 |
-
<span>⚡</span>
|
| 53 |
-
<span>Actions</span>
|
| 54 |
-
</h4>
|
| 55 |
-
<ul className="space-y-1" style={{ listStyle: 'none', padding: 0, margin: 0 }}>
|
| 56 |
-
{step.actions.map((action, actionIndex) => (
|
| 57 |
-
<li key={actionIndex} className="text-xs text-gray-600 flex items-start leading-snug">
|
| 58 |
-
<span className="mr-1.5 text-blue-500 flex-shrink-0">→</span>
|
| 59 |
-
<span className="break-words">{action}</span>
|
| 60 |
-
</li>
|
| 61 |
-
))}
|
| 62 |
-
</ul>
|
| 63 |
-
</div>
|
| 64 |
-
<hr style={{ margin: '20px 0', border: 'none', borderTop: '1px solid #e5e7eb' }} />
|
| 65 |
-
</div>
|
| 66 |
-
|
| 67 |
-
{/* Step Metadata Footer */}
|
| 68 |
-
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
| 69 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#eff6ff', borderRadius: '6px', padding: '6px 8px', border: '1px solid #bfdbfe' }}>
|
| 70 |
-
<span style={{ fontSize: '9px', fontWeight: 500, color: '#2563eb', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Time</span>
|
| 71 |
-
<span style={{ fontSize: '12px', fontWeight: 700, color: '#1e40af' }}>{step.duration.toFixed(2)}s</span>
|
| 72 |
-
</div>
|
| 73 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0fdf4', borderRadius: '6px', padding: '6px 8px', border: '1px solid #bbf7d0' }}>
|
| 74 |
-
<span style={{ fontSize: '9px', fontWeight: 500, color: '#059669', textTransform: 'uppercase', letterSpacing: '0.5px' }}>In Tokens</span>
|
| 75 |
-
<span style={{ fontSize: '12px', fontWeight: 700, color: '#166534' }}>{step.inputTokensUsed}</span>
|
| 76 |
-
</div>
|
| 77 |
-
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: '#faf5ff', borderRadius: '6px', padding: '6px 8px', border: '1px solid #e9d5ff' }}>
|
| 78 |
-
<span style={{ fontSize: '9px', fontWeight: 500, color: '#9333ea', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Out Tokens</span>
|
| 79 |
-
<span style={{ fontSize: '12px', fontWeight: 700, color: '#6b21a8' }}>{step.outputTokensUsed}</span>
|
| 80 |
-
</div>
|
| 81 |
-
</div>
|
| 82 |
-
</div>
|
| 83 |
-
);
|
| 84 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
|
| 3 |
-
interface TaskButtonProps {
|
| 4 |
-
isAgentProcessing: boolean;
|
| 5 |
-
isConnected: boolean;
|
| 6 |
-
onSendTask: (content: string, modelId: string) => void;
|
| 7 |
-
}
|
| 8 |
-
|
| 9 |
-
export const TaskButton: React.FC<TaskButtonProps> = ({ isAgentProcessing, isConnected, onSendTask }) => {
|
| 10 |
-
return (
|
| 11 |
-
<div
|
| 12 |
-
onClick={() => {
|
| 13 |
-
if (!isAgentProcessing && isConnected) {
|
| 14 |
-
onSendTask(
|
| 15 |
-
"Find the price of a NVIDIA RTX 4090 GPU",
|
| 16 |
-
"Qwen/Qwen3-VL-30B-A3B-Instruct"
|
| 17 |
-
);
|
| 18 |
-
}
|
| 19 |
-
}}
|
| 20 |
-
style={{
|
| 21 |
-
marginTop: '16px',
|
| 22 |
-
padding: '14px 18px',
|
| 23 |
-
background: isAgentProcessing || !isConnected
|
| 24 |
-
? 'rgba(255, 255, 255, 0.1)'
|
| 25 |
-
: 'rgba(255, 255, 255, 0.15)',
|
| 26 |
-
borderRadius: '10px',
|
| 27 |
-
backdropFilter: 'blur(10px)',
|
| 28 |
-
border: '2px solid rgba(0, 0, 0, 0.3)',
|
| 29 |
-
cursor: isAgentProcessing || !isConnected ? 'not-allowed' : 'pointer',
|
| 30 |
-
transition: 'all 0.3s ease',
|
| 31 |
-
opacity: isAgentProcessing || !isConnected ? 0.6 : 1,
|
| 32 |
-
}}
|
| 33 |
-
onMouseEnter={(e) => {
|
| 34 |
-
if (!isAgentProcessing && isConnected) {
|
| 35 |
-
e.currentTarget.style.background = 'rgba(200, 200, 200, 0.3)';
|
| 36 |
-
e.currentTarget.style.borderColor = 'rgba(0, 0, 0, 0.5)';
|
| 37 |
-
e.currentTarget.style.transform = 'translateY(-2px)';
|
| 38 |
-
e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.2)';
|
| 39 |
-
}
|
| 40 |
-
}}
|
| 41 |
-
onMouseLeave={(e) => {
|
| 42 |
-
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
| 43 |
-
e.currentTarget.style.borderColor = 'rgba(0, 0, 0, 0.3)';
|
| 44 |
-
e.currentTarget.style.transform = 'translateY(0)';
|
| 45 |
-
e.currentTarget.style.boxShadow = 'none';
|
| 46 |
-
}}
|
| 47 |
-
>
|
| 48 |
-
<div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
|
| 49 |
-
<div style={{ flex: 1 }}>
|
| 50 |
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
| 51 |
-
<span style={{ fontSize: '11px', fontWeight: 600, color: 'rgba(0, 0, 0, 0.7)', textTransform: 'uppercase', letterSpacing: '1px' }}>Task</span>
|
| 52 |
-
{!isAgentProcessing && isConnected && (
|
| 53 |
-
<span style={{ fontSize: '10px', color: 'rgba(0, 0, 0, 0.5)', fontStyle: 'italic' }}>
|
| 54 |
-
(click to run)
|
| 55 |
-
</span>
|
| 56 |
-
)}
|
| 57 |
-
</div>
|
| 58 |
-
<p style={{ fontSize: '15px', fontWeight: 500, color: '#1f2937' }}>
|
| 59 |
-
Find the price of a NVIDIA RTX 4090 GPU
|
| 60 |
-
</p>
|
| 61 |
-
</div>
|
| 62 |
-
<div style={{
|
| 63 |
-
padding: '8px 16px',
|
| 64 |
-
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
| 65 |
-
borderRadius: '6px',
|
| 66 |
-
border: '1px solid rgba(0, 0, 0, 0.2)'
|
| 67 |
-
}}>
|
| 68 |
-
<span style={{ fontSize: '11px', fontWeight: 600, color: 'rgba(0, 0, 0, 0.6)', textTransform: 'uppercase', letterSpacing: '1px' }}>Model</span>
|
| 69 |
-
<p style={{ fontSize: '12px', fontWeight: 600, color: '#1f2937', marginTop: '2px', whiteSpace: 'nowrap' }}>
|
| 70 |
-
Qwen/Qwen3-VL-30B-A3B-Instruct
|
| 71 |
-
</p>
|
| 72 |
-
</div>
|
| 73 |
-
</div>
|
| 74 |
-
</div>
|
| 75 |
-
);
|
| 76 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
|
| 3 |
-
interface VNCStreamProps {
|
| 4 |
-
vncUrl: string;
|
| 5 |
-
}
|
| 6 |
-
|
| 7 |
-
export const VNCStream: React.FC<VNCStreamProps> = ({ vncUrl }) => {
|
| 8 |
-
return (
|
| 9 |
-
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', backgroundColor: 'white', borderRadius: '10px', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)', border: '1px solid #e5e7eb', overflow: 'hidden', padding: '20px' }}>
|
| 10 |
-
<h3 className="text-lg font-semibold text-gray-800 mb-4">VNC Stream</h3>
|
| 11 |
-
<div style={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
| 12 |
-
{vncUrl ? (
|
| 13 |
-
<iframe
|
| 14 |
-
src={vncUrl}
|
| 15 |
-
style={{ width: '100%', height: '100%', border: 'none' }}
|
| 16 |
-
title="VNC Stream"
|
| 17 |
-
/>
|
| 18 |
-
) : (
|
| 19 |
-
<div className="text-gray-400 text-center p-8">
|
| 20 |
-
<svg className="w-16 h-16 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 21 |
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
| 22 |
-
</svg>
|
| 23 |
-
<p className="font-medium">No VNC stream available</p>
|
| 24 |
-
<p className="text-sm mt-1 text-gray-500">Stream will appear when agent starts</p>
|
| 25 |
-
</div>
|
| 26 |
-
)}
|
| 27 |
-
</div>
|
| 28 |
-
</div>
|
| 29 |
-
);
|
| 30 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
export { ConnectionStatus } from './ConnectionStatus';
|
| 2 |
-
export { ProcessingIndicator } from './ProcessingIndicator';
|
| 3 |
-
export { TaskButton } from './TaskButton';
|
| 4 |
-
export { Header } from './Header';
|
| 5 |
-
export { VNCStream } from './VNCStream';
|
| 6 |
-
export { Metadata } from './Metadata';
|
| 7 |
-
export { StepCard } from './StepCard';
|
| 8 |
-
export { StackSteps } from './StackSteps';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { useNavigate } from 'react-router-dom';
|
| 3 |
+
import { Box, Typography, CircularProgress, Button, keyframes } from '@mui/material';
|
| 4 |
+
import MonitorIcon from '@mui/icons-material/Monitor';
|
| 5 |
+
import ImageIcon from '@mui/icons-material/Image';
|
| 6 |
+
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
| 7 |
+
import { AgentTraceMetadata, AgentStep } from '@/types/agent';
|
| 8 |
+
import { useAgentStore, selectError, selectFinalStep, selectSteps, selectTrace } from '@/stores/agentStore';
|
| 9 |
+
import { CompletionView } from './CompletionView';
|
| 10 |
+
import { useGifGenerator } from '@/hooks/useGifGenerator';
|
| 11 |
+
import { useJsonExporter } from '@/hooks/useJsonExporter';
|
| 12 |
+
|
| 13 |
+
// Animation for live indicator
|
| 14 |
+
const livePulse = keyframes`
|
| 15 |
+
0%, 100% {
|
| 16 |
+
opacity: 1;
|
| 17 |
+
transform: scale(1);
|
| 18 |
+
}
|
| 19 |
+
50% {
|
| 20 |
+
opacity: 0.7;
|
| 21 |
+
transform: scale(1.2);
|
| 22 |
+
}
|
| 23 |
+
`;
|
| 24 |
+
|
| 25 |
+
interface SandboxViewerProps {
|
| 26 |
+
vncUrl: string;
|
| 27 |
+
isAgentProcessing?: boolean;
|
| 28 |
+
metadata?: AgentTraceMetadata;
|
| 29 |
+
traceStartTime?: Date;
|
| 30 |
+
selectedStep?: AgentStep | null; // The step to display in time-travel mode
|
| 31 |
+
isRunning?: boolean; // Is the agent currently running
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export const SandboxViewer: React.FC<SandboxViewerProps> = ({
|
| 35 |
+
vncUrl,
|
| 36 |
+
isAgentProcessing = false,
|
| 37 |
+
metadata,
|
| 38 |
+
traceStartTime,
|
| 39 |
+
selectedStep,
|
| 40 |
+
isRunning = false
|
| 41 |
+
}) => {
|
| 42 |
+
const navigate = useNavigate();
|
| 43 |
+
const error = useAgentStore(selectError);
|
| 44 |
+
const finalStep = useAgentStore(selectFinalStep);
|
| 45 |
+
const steps = useAgentStore(selectSteps);
|
| 46 |
+
const trace = useAgentStore(selectTrace);
|
| 47 |
+
const resetAgent = useAgentStore((state) => state.resetAgent);
|
| 48 |
+
const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex);
|
| 49 |
+
|
| 50 |
+
// Hook to generate GIF
|
| 51 |
+
const { isGenerating, error: gifError, generateAndDownloadGif } = useGifGenerator({
|
| 52 |
+
steps: steps || [],
|
| 53 |
+
traceId: finalStep?.metadata.traceId || '',
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
// Hook to export JSON
|
| 57 |
+
const { downloadTraceAsJson } = useJsonExporter({
|
| 58 |
+
trace,
|
| 59 |
+
steps: steps || [],
|
| 60 |
+
metadata: finalStep?.metadata || metadata,
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
// Extract final_answer from the last step, or fallback to last thought
|
| 64 |
+
const getFinalAnswer = (): string | null => {
|
| 65 |
+
console.log('🔍 getFinalAnswer - steps:', steps);
|
| 66 |
+
if (!steps || steps.length === 0) {
|
| 67 |
+
console.log('❌ No steps available');
|
| 68 |
+
return null;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// Try to find final_answer in any step (iterate backwards)
|
| 72 |
+
for (let i = steps.length - 1; i >= 0; i--) {
|
| 73 |
+
const step = steps[i];
|
| 74 |
+
|
| 75 |
+
if (step.actions && Array.isArray(step.actions)) {
|
| 76 |
+
const finalAnswerAction = step.actions.find(
|
| 77 |
+
(action) => action.function_name === 'final_answer'
|
| 78 |
+
);
|
| 79 |
+
|
| 80 |
+
if (finalAnswerAction) {
|
| 81 |
+
// Handle both named parameter and positional argument
|
| 82 |
+
const result = finalAnswerAction?.parameters?.answer || finalAnswerAction?.parameters?.arg_0 || null;
|
| 83 |
+
console.log('✅ Final answer found in step', i + 1, ':', result);
|
| 84 |
+
return result;
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
console.log('🔍 No final_answer found, looking for last thought...');
|
| 90 |
+
|
| 91 |
+
// Fallback: find the last step with a thought (iterate backwards)
|
| 92 |
+
for (let i = steps.length - 1; i >= 0; i--) {
|
| 93 |
+
const step = steps[i];
|
| 94 |
+
if (step.thought) {
|
| 95 |
+
console.log('📝 Using thought from step', i + 1, 'as fallback:', step.thought);
|
| 96 |
+
return step.thought;
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
console.log('❌ No final answer or thought found in any step');
|
| 101 |
+
return null;
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
const finalAnswer = getFinalAnswer();
|
| 105 |
+
console.log('🎯 Final answer to display:', finalAnswer);
|
| 106 |
+
|
| 107 |
+
// Determine if we should show success/fail status
|
| 108 |
+
const showStatus = !isRunning && !selectedStep && finalStep;
|
| 109 |
+
|
| 110 |
+
// Handler to go back to home
|
| 111 |
+
const handleBackToHome = () => {
|
| 112 |
+
resetAgent();
|
| 113 |
+
navigate('/');
|
| 114 |
+
};
|
| 115 |
+
|
| 116 |
+
// Handler to go back to live mode
|
| 117 |
+
const handleGoLive = () => {
|
| 118 |
+
setSelectedStepIndex(null);
|
| 119 |
+
};
|
| 120 |
+
|
| 121 |
+
return (
|
| 122 |
+
<Box
|
| 123 |
+
sx={{
|
| 124 |
+
flex: '1 1 auto',
|
| 125 |
+
display: 'flex',
|
| 126 |
+
flexDirection: 'column',
|
| 127 |
+
position: 'relative',
|
| 128 |
+
border: '1px solid',
|
| 129 |
+
borderColor: showStatus
|
| 130 |
+
? (finalStep?.type === 'failure' ? 'error.main' : 'success.main')
|
| 131 |
+
: ((vncUrl || isAgentProcessing) && !selectedStep && !showStatus ? 'primary.main' : 'divider'),
|
| 132 |
+
borderRadius: '12px',
|
| 133 |
+
backgroundColor: 'background.paper',
|
| 134 |
+
transition: 'border 0.3s ease',
|
| 135 |
+
overflow: 'hidden',
|
| 136 |
+
}}
|
| 137 |
+
>
|
| 138 |
+
{/* Live Badge or Go Live Button */}
|
| 139 |
+
{vncUrl && !showStatus && (
|
| 140 |
+
<>
|
| 141 |
+
{!selectedStep ? (
|
| 142 |
+
// Live Badge when in live mode
|
| 143 |
+
<Box
|
| 144 |
+
sx={{
|
| 145 |
+
position: 'absolute',
|
| 146 |
+
top: 12,
|
| 147 |
+
right: 12,
|
| 148 |
+
zIndex: 10,
|
| 149 |
+
display: 'flex',
|
| 150 |
+
alignItems: 'center',
|
| 151 |
+
gap: 1,
|
| 152 |
+
px: 2,
|
| 153 |
+
py: 1,
|
| 154 |
+
backgroundColor: (theme) =>
|
| 155 |
+
theme.palette.mode === 'dark'
|
| 156 |
+
? 'rgba(0, 0, 0, 0.7)'
|
| 157 |
+
: 'rgba(255, 255, 255, 0.9)',
|
| 158 |
+
backdropFilter: 'blur(8px)',
|
| 159 |
+
borderRadius: 0.75,
|
| 160 |
+
border: '1px solid',
|
| 161 |
+
borderColor: 'primary.main',
|
| 162 |
+
boxShadow: (theme) =>
|
| 163 |
+
theme.palette.mode === 'dark'
|
| 164 |
+
? '0 2px 8px rgba(0, 0, 0, 0.4)'
|
| 165 |
+
: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
| 166 |
+
}}
|
| 167 |
+
>
|
| 168 |
+
<Box
|
| 169 |
+
sx={{
|
| 170 |
+
width: 10,
|
| 171 |
+
height: 10,
|
| 172 |
+
borderRadius: '50%',
|
| 173 |
+
backgroundColor: 'error.main',
|
| 174 |
+
animation: `${livePulse} 2s ease-in-out infinite`,
|
| 175 |
+
}}
|
| 176 |
+
/>
|
| 177 |
+
<Typography
|
| 178 |
+
variant="caption"
|
| 179 |
+
sx={{
|
| 180 |
+
fontSize: '0.8rem',
|
| 181 |
+
fontWeight: 700,
|
| 182 |
+
color: 'text.primary',
|
| 183 |
+
textTransform: 'uppercase',
|
| 184 |
+
letterSpacing: '0.5px',
|
| 185 |
+
}}
|
| 186 |
+
>
|
| 187 |
+
Live
|
| 188 |
+
</Typography>
|
| 189 |
+
</Box>
|
| 190 |
+
) : (
|
| 191 |
+
// Go Live Button when viewing a specific step
|
| 192 |
+
<Button
|
| 193 |
+
onClick={handleGoLive}
|
| 194 |
+
startIcon={<PlayArrowIcon sx={{ fontSize: 20 }} />}
|
| 195 |
+
sx={{
|
| 196 |
+
position: 'absolute',
|
| 197 |
+
top: 12,
|
| 198 |
+
right: 12,
|
| 199 |
+
zIndex: 10,
|
| 200 |
+
px: 2,
|
| 201 |
+
py: 1,
|
| 202 |
+
backgroundColor: (theme) =>
|
| 203 |
+
theme.palette.mode === 'dark'
|
| 204 |
+
? 'rgba(0, 0, 0, 0.7)'
|
| 205 |
+
: 'rgba(255, 255, 255, 0.9)',
|
| 206 |
+
backdropFilter: 'blur(8px)',
|
| 207 |
+
borderRadius: 0.75,
|
| 208 |
+
border: '1px solid',
|
| 209 |
+
borderColor: 'primary.main',
|
| 210 |
+
boxShadow: (theme) =>
|
| 211 |
+
theme.palette.mode === 'dark'
|
| 212 |
+
? '0 2px 8px rgba(0, 0, 0, 0.4)'
|
| 213 |
+
: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
| 214 |
+
fontSize: '0.8rem',
|
| 215 |
+
fontWeight: 700,
|
| 216 |
+
textTransform: 'uppercase',
|
| 217 |
+
letterSpacing: '0.5px',
|
| 218 |
+
color: 'primary.main',
|
| 219 |
+
'&:hover': {
|
| 220 |
+
backgroundColor: (theme) =>
|
| 221 |
+
theme.palette.mode === 'dark'
|
| 222 |
+
? 'rgba(0, 0, 0, 0.85)'
|
| 223 |
+
: 'rgba(255, 255, 255, 1)',
|
| 224 |
+
borderColor: 'primary.dark',
|
| 225 |
+
},
|
| 226 |
+
}}
|
| 227 |
+
>
|
| 228 |
+
Go Live
|
| 229 |
+
</Button>
|
| 230 |
+
)}
|
| 231 |
+
</>
|
| 232 |
+
)}
|
| 233 |
+
|
| 234 |
+
<Box
|
| 235 |
+
sx={{
|
| 236 |
+
flex: 1,
|
| 237 |
+
minHeight: 0,
|
| 238 |
+
display: 'flex',
|
| 239 |
+
alignItems: 'center',
|
| 240 |
+
justifyContent: 'center',
|
| 241 |
+
}}
|
| 242 |
+
>
|
| 243 |
+
{showStatus && finalStep ? (
|
| 244 |
+
// Show success/fail status when agent has completed
|
| 245 |
+
<CompletionView
|
| 246 |
+
finalStep={finalStep}
|
| 247 |
+
trace={trace}
|
| 248 |
+
steps={steps}
|
| 249 |
+
finalAnswer={finalAnswer}
|
| 250 |
+
isGenerating={isGenerating}
|
| 251 |
+
gifError={gifError}
|
| 252 |
+
onGenerateGif={generateAndDownloadGif}
|
| 253 |
+
onDownloadJson={downloadTraceAsJson}
|
| 254 |
+
onBackToHome={handleBackToHome}
|
| 255 |
+
/>
|
| 256 |
+
) : selectedStep ? (
|
| 257 |
+
// Time-travel mode: Show screenshot of selected step
|
| 258 |
+
<Box
|
| 259 |
+
sx={{
|
| 260 |
+
width: '100%',
|
| 261 |
+
height: '100%',
|
| 262 |
+
display: 'flex',
|
| 263 |
+
alignItems: 'center',
|
| 264 |
+
justifyContent: 'center',
|
| 265 |
+
overflow: 'auto',
|
| 266 |
+
backgroundColor: 'black',
|
| 267 |
+
position: 'relative',
|
| 268 |
+
}}
|
| 269 |
+
>
|
| 270 |
+
{selectedStep.image ? (
|
| 271 |
+
<img
|
| 272 |
+
src={selectedStep.image}
|
| 273 |
+
alt="Step screenshot"
|
| 274 |
+
style={{
|
| 275 |
+
maxWidth: '100%',
|
| 276 |
+
maxHeight: '100%',
|
| 277 |
+
objectFit: 'contain',
|
| 278 |
+
}}
|
| 279 |
+
/>
|
| 280 |
+
) : (
|
| 281 |
+
<Box
|
| 282 |
+
sx={{
|
| 283 |
+
textAlign: 'center',
|
| 284 |
+
p: 4,
|
| 285 |
+
color: 'text.secondary',
|
| 286 |
+
width: '100%',
|
| 287 |
+
height: '100%',
|
| 288 |
+
display: 'flex',
|
| 289 |
+
flexDirection: 'column',
|
| 290 |
+
alignItems: 'center',
|
| 291 |
+
justifyContent: 'center',
|
| 292 |
+
}}
|
| 293 |
+
>
|
| 294 |
+
<ImageIcon sx={{ fontSize: 48, mb: 2, opacity: 0.5 }} />
|
| 295 |
+
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5, fontSize: '0.875rem', color: 'text.primary' }}>
|
| 296 |
+
No screenshot available
|
| 297 |
+
</Typography>
|
| 298 |
+
<Typography variant="caption" sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>
|
| 299 |
+
This step doesn't have a screenshot
|
| 300 |
+
</Typography>
|
| 301 |
+
</Box>
|
| 302 |
+
)}
|
| 303 |
+
</Box>
|
| 304 |
+
) : vncUrl ? (
|
| 305 |
+
// Live mode: Show VNC stream
|
| 306 |
+
<iframe
|
| 307 |
+
src={vncUrl}
|
| 308 |
+
style={{ width: '100%', height: '100%', border: 'none' }}
|
| 309 |
+
title="OS Stream"
|
| 310 |
+
/>
|
| 311 |
+
) : isAgentProcessing ? (
|
| 312 |
+
// Loading state
|
| 313 |
+
<Box
|
| 314 |
+
sx={{
|
| 315 |
+
textAlign: 'center',
|
| 316 |
+
p: 4,
|
| 317 |
+
color: 'text.secondary',
|
| 318 |
+
width: '100%',
|
| 319 |
+
height: '100%',
|
| 320 |
+
display: 'flex',
|
| 321 |
+
flexDirection: 'column',
|
| 322 |
+
alignItems: 'center',
|
| 323 |
+
justifyContent: 'center',
|
| 324 |
+
}}
|
| 325 |
+
>
|
| 326 |
+
<CircularProgress
|
| 327 |
+
size={48}
|
| 328 |
+
sx={{
|
| 329 |
+
mb: 2,
|
| 330 |
+
color: 'primary.main'
|
| 331 |
+
}}
|
| 332 |
+
/>
|
| 333 |
+
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5, fontSize: '0.875rem', color: 'text.primary' }}>
|
| 334 |
+
Connecting to E2B...
|
| 335 |
+
</Typography>
|
| 336 |
+
<Typography variant="caption" sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>
|
| 337 |
+
Setting up sandbox environment
|
| 338 |
+
</Typography>
|
| 339 |
+
</Box>
|
| 340 |
+
) : (
|
| 341 |
+
// No stream available
|
| 342 |
+
<Box
|
| 343 |
+
sx={{
|
| 344 |
+
textAlign: 'center',
|
| 345 |
+
p: 4,
|
| 346 |
+
color: 'text.secondary',
|
| 347 |
+
width: '100%',
|
| 348 |
+
height: '100%',
|
| 349 |
+
display: 'flex',
|
| 350 |
+
flexDirection: 'column',
|
| 351 |
+
alignItems: 'center',
|
| 352 |
+
justifyContent: 'center',
|
| 353 |
+
}}
|
| 354 |
+
>
|
| 355 |
+
<MonitorIcon sx={{ fontSize: 48, mb: 2, opacity: 0.5 }} />
|
| 356 |
+
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5, fontSize: '0.875rem' }}>
|
| 357 |
+
No stream available
|
| 358 |
+
</Typography>
|
| 359 |
+
<Typography variant="caption" sx={{ fontSize: '0.75rem', color: 'text.secondary' }}>
|
| 360 |
+
Stream will appear when agent starts
|
| 361 |
+
</Typography>
|
| 362 |
+
</Box>
|
| 363 |
+
)}
|
| 364 |
+
</Box>
|
| 365 |
+
</Box>
|
| 366 |
+
);
|
| 367 |
+
};
|
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Box, Typography, Button, Divider, Alert, Paper } from '@mui/material';
|
| 3 |
+
import CheckIcon from '@mui/icons-material/Check';
|
| 4 |
+
import CloseIcon from '@mui/icons-material/Close';
|
| 5 |
+
import AddIcon from '@mui/icons-material/Add';
|
| 6 |
+
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
| 7 |
+
import AssignmentIcon from '@mui/icons-material/Assignment';
|
| 8 |
+
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
|
| 9 |
+
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
| 10 |
+
import InputIcon from '@mui/icons-material/Input';
|
| 11 |
+
import OutputIcon from '@mui/icons-material/Output';
|
| 12 |
+
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
| 13 |
+
import { FinalStep, AgentTrace, AgentStep } from '@/types/agent';
|
| 14 |
+
import { DownloadGifButton } from './DownloadGifButton';
|
| 15 |
+
import { DownloadJsonButton } from './DownloadJsonButton';
|
| 16 |
+
|
| 17 |
+
interface CompletionViewProps {
|
| 18 |
+
finalStep: FinalStep;
|
| 19 |
+
trace?: AgentTrace;
|
| 20 |
+
steps?: AgentStep[];
|
| 21 |
+
finalAnswer?: string | null;
|
| 22 |
+
isGenerating: boolean;
|
| 23 |
+
gifError: string | null;
|
| 24 |
+
onGenerateGif: () => void;
|
| 25 |
+
onDownloadJson: () => void;
|
| 26 |
+
onBackToHome: () => void;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* Component displaying the completion status (success or failure) of a task
|
| 31 |
+
*/
|
| 32 |
+
export const CompletionView: React.FC<CompletionViewProps> = ({
|
| 33 |
+
finalStep,
|
| 34 |
+
trace,
|
| 35 |
+
steps,
|
| 36 |
+
finalAnswer,
|
| 37 |
+
isGenerating,
|
| 38 |
+
gifError,
|
| 39 |
+
onGenerateGif,
|
| 40 |
+
onDownloadJson,
|
| 41 |
+
onBackToHome,
|
| 42 |
+
}) => {
|
| 43 |
+
const isSuccess = finalStep.type === 'success';
|
| 44 |
+
const statusColor = isSuccess ? 'success.main' : 'error.main';
|
| 45 |
+
|
| 46 |
+
// Format model name for display
|
| 47 |
+
const formatModelName = (modelId: string) => {
|
| 48 |
+
const parts = modelId.split('/');
|
| 49 |
+
return parts.length > 1 ? parts[1] : modelId;
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
return (
|
| 53 |
+
<Box
|
| 54 |
+
sx={{
|
| 55 |
+
width: '100%',
|
| 56 |
+
maxWidth: 600,
|
| 57 |
+
mx: 'auto',
|
| 58 |
+
p: 2,
|
| 59 |
+
display: 'flex',
|
| 60 |
+
flexDirection: 'column',
|
| 61 |
+
gap: 1.5,
|
| 62 |
+
}}
|
| 63 |
+
>
|
| 64 |
+
{/* Status Header - Compact */}
|
| 65 |
+
<Box sx={{ textAlign: 'center', mb: 0.5 }}>
|
| 66 |
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1.5, mb: 0.75 }}>
|
| 67 |
+
<Box
|
| 68 |
+
sx={{
|
| 69 |
+
width: 40,
|
| 70 |
+
height: 40,
|
| 71 |
+
borderRadius: '50%',
|
| 72 |
+
backgroundColor: statusColor,
|
| 73 |
+
display: 'flex',
|
| 74 |
+
alignItems: 'center',
|
| 75 |
+
justifyContent: 'center',
|
| 76 |
+
boxShadow: (theme) =>
|
| 77 |
+
isSuccess
|
| 78 |
+
? `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.3)' : 'rgba(102, 187, 106, 0.2)'}`
|
| 79 |
+
: `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.3)' : 'rgba(244, 67, 54, 0.2)'}`,
|
| 80 |
+
}}
|
| 81 |
+
>
|
| 82 |
+
{isSuccess ? (
|
| 83 |
+
<CheckIcon sx={{ fontSize: 24, color: 'white' }} />
|
| 84 |
+
) : (
|
| 85 |
+
<CloseIcon sx={{ fontSize: 24, color: 'white' }} />
|
| 86 |
+
)}
|
| 87 |
+
</Box>
|
| 88 |
+
<Typography
|
| 89 |
+
variant="h6"
|
| 90 |
+
sx={{
|
| 91 |
+
fontWeight: 700,
|
| 92 |
+
color: statusColor,
|
| 93 |
+
fontSize: '1.1rem',
|
| 94 |
+
letterSpacing: '-0.5px',
|
| 95 |
+
}}
|
| 96 |
+
>
|
| 97 |
+
{isSuccess ? 'Task Completed' : 'Task Failed'}
|
| 98 |
+
</Typography>
|
| 99 |
+
</Box>
|
| 100 |
+
</Box>
|
| 101 |
+
|
| 102 |
+
{/* Single Report Box - Task + Agent + Response + Metrics */}
|
| 103 |
+
<Paper
|
| 104 |
+
elevation={0}
|
| 105 |
+
sx={{
|
| 106 |
+
p: 2.5,
|
| 107 |
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.03)',
|
| 108 |
+
borderRadius: 1.5,
|
| 109 |
+
border: '1px solid',
|
| 110 |
+
borderColor: 'divider',
|
| 111 |
+
}}
|
| 112 |
+
>
|
| 113 |
+
{/* Task */}
|
| 114 |
+
{trace?.instruction && (
|
| 115 |
+
<Box sx={{ mb: 2 }}>
|
| 116 |
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
|
| 117 |
+
<AssignmentIcon sx={{ fontSize: 18, color: 'text.secondary', mt: 0.25, flexShrink: 0 }} />
|
| 118 |
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
| 119 |
+
<Typography
|
| 120 |
+
variant="caption"
|
| 121 |
+
sx={{
|
| 122 |
+
fontWeight: 700,
|
| 123 |
+
color: 'text.secondary',
|
| 124 |
+
fontSize: '0.7rem',
|
| 125 |
+
textTransform: 'uppercase',
|
| 126 |
+
letterSpacing: '0.5px',
|
| 127 |
+
display: 'block',
|
| 128 |
+
mb: 0.5,
|
| 129 |
+
}}
|
| 130 |
+
>
|
| 131 |
+
Task
|
| 132 |
+
</Typography>
|
| 133 |
+
<Typography
|
| 134 |
+
variant="body2"
|
| 135 |
+
sx={{
|
| 136 |
+
color: 'text.primary',
|
| 137 |
+
fontWeight: 700,
|
| 138 |
+
lineHeight: 1.5,
|
| 139 |
+
fontSize: '0.85rem',
|
| 140 |
+
}}
|
| 141 |
+
>
|
| 142 |
+
{trace.instruction}
|
| 143 |
+
</Typography>
|
| 144 |
+
</Box>
|
| 145 |
+
</Box>
|
| 146 |
+
</Box>
|
| 147 |
+
)}
|
| 148 |
+
|
| 149 |
+
{/* Agent Response */}
|
| 150 |
+
{finalAnswer && (
|
| 151 |
+
<Box sx={{ mb: 2 }}>
|
| 152 |
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
|
| 153 |
+
<ChatBubbleOutlineIcon
|
| 154 |
+
sx={{
|
| 155 |
+
fontSize: 18,
|
| 156 |
+
color: 'text.secondary',
|
| 157 |
+
mt: 0.25,
|
| 158 |
+
flexShrink: 0
|
| 159 |
+
}}
|
| 160 |
+
/>
|
| 161 |
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
| 162 |
+
<Typography
|
| 163 |
+
variant="caption"
|
| 164 |
+
sx={{
|
| 165 |
+
fontWeight: 700,
|
| 166 |
+
color: 'text.secondary',
|
| 167 |
+
fontSize: '0.7rem',
|
| 168 |
+
textTransform: 'uppercase',
|
| 169 |
+
letterSpacing: '0.5px',
|
| 170 |
+
display: 'block',
|
| 171 |
+
mb: 0.75,
|
| 172 |
+
}}
|
| 173 |
+
>
|
| 174 |
+
Agent Response
|
| 175 |
+
</Typography>
|
| 176 |
+
<Typography
|
| 177 |
+
variant="body2"
|
| 178 |
+
sx={{
|
| 179 |
+
color: 'text.primary',
|
| 180 |
+
lineHeight: 1.5,
|
| 181 |
+
fontSize: '0.85rem',
|
| 182 |
+
whiteSpace: 'pre-wrap',
|
| 183 |
+
wordBreak: 'break-word',
|
| 184 |
+
}}
|
| 185 |
+
>
|
| 186 |
+
{finalAnswer}
|
| 187 |
+
</Typography>
|
| 188 |
+
</Box>
|
| 189 |
+
</Box>
|
| 190 |
+
</Box>
|
| 191 |
+
)}
|
| 192 |
+
|
| 193 |
+
{/* Divider before metrics */}
|
| 194 |
+
<Divider sx={{ my: 2 }} />
|
| 195 |
+
|
| 196 |
+
{/* Metrics */}
|
| 197 |
+
<Box
|
| 198 |
+
sx={{
|
| 199 |
+
display: 'flex',
|
| 200 |
+
alignItems: 'center',
|
| 201 |
+
gap: 1.5,
|
| 202 |
+
flexWrap: 'wrap',
|
| 203 |
+
justifyContent: 'center',
|
| 204 |
+
}}
|
| 205 |
+
>
|
| 206 |
+
{/* Agent */}
|
| 207 |
+
{trace?.modelId && (
|
| 208 |
+
<>
|
| 209 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 210 |
+
<SmartToyIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 211 |
+
<Typography
|
| 212 |
+
variant="caption"
|
| 213 |
+
sx={{
|
| 214 |
+
color: 'text.primary',
|
| 215 |
+
fontFamily: 'monospace',
|
| 216 |
+
fontSize: '0.75rem',
|
| 217 |
+
fontWeight: 700,
|
| 218 |
+
}}
|
| 219 |
+
>
|
| 220 |
+
{formatModelName(trace.modelId)}
|
| 221 |
+
</Typography>
|
| 222 |
+
</Box>
|
| 223 |
+
|
| 224 |
+
{/* Divider */}
|
| 225 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 226 |
+
</>
|
| 227 |
+
)}
|
| 228 |
+
|
| 229 |
+
{/* Steps Count */}
|
| 230 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 231 |
+
<FormatListNumberedIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 232 |
+
<Typography
|
| 233 |
+
variant="caption"
|
| 234 |
+
sx={{
|
| 235 |
+
fontSize: '0.75rem',
|
| 236 |
+
fontWeight: 700,
|
| 237 |
+
color: 'text.primary',
|
| 238 |
+
mr: 0.5,
|
| 239 |
+
}}
|
| 240 |
+
>
|
| 241 |
+
{finalStep.metadata.numberOfSteps}
|
| 242 |
+
</Typography>
|
| 243 |
+
<Typography
|
| 244 |
+
variant="caption"
|
| 245 |
+
sx={{
|
| 246 |
+
fontSize: '0.7rem',
|
| 247 |
+
fontWeight: 400,
|
| 248 |
+
color: 'text.secondary',
|
| 249 |
+
}}
|
| 250 |
+
>
|
| 251 |
+
{finalStep.metadata.numberOfSteps === 1 ? 'Step' : 'Steps'}
|
| 252 |
+
</Typography>
|
| 253 |
+
</Box>
|
| 254 |
+
|
| 255 |
+
{/* Divider */}
|
| 256 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 257 |
+
|
| 258 |
+
{/* Duration */}
|
| 259 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 260 |
+
<AccessTimeIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 261 |
+
<Typography
|
| 262 |
+
variant="caption"
|
| 263 |
+
sx={{
|
| 264 |
+
fontSize: '0.75rem',
|
| 265 |
+
fontWeight: 700,
|
| 266 |
+
color: 'text.primary',
|
| 267 |
+
}}
|
| 268 |
+
>
|
| 269 |
+
{finalStep.metadata.duration.toFixed(1)}s
|
| 270 |
+
</Typography>
|
| 271 |
+
</Box>
|
| 272 |
+
|
| 273 |
+
{/* Divider */}
|
| 274 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 275 |
+
|
| 276 |
+
{/* Input Tokens */}
|
| 277 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 278 |
+
<InputIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 279 |
+
<Typography
|
| 280 |
+
variant="caption"
|
| 281 |
+
sx={{
|
| 282 |
+
fontSize: '0.75rem',
|
| 283 |
+
fontWeight: 700,
|
| 284 |
+
color: 'text.primary',
|
| 285 |
+
}}
|
| 286 |
+
>
|
| 287 |
+
{finalStep.metadata.inputTokensUsed.toLocaleString()}
|
| 288 |
+
</Typography>
|
| 289 |
+
</Box>
|
| 290 |
+
|
| 291 |
+
{/* Divider */}
|
| 292 |
+
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
|
| 293 |
+
|
| 294 |
+
{/* Output Tokens */}
|
| 295 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 296 |
+
<OutputIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
|
| 297 |
+
<Typography
|
| 298 |
+
variant="caption"
|
| 299 |
+
sx={{
|
| 300 |
+
fontSize: '0.75rem',
|
| 301 |
+
fontWeight: 700,
|
| 302 |
+
color: 'text.primary',
|
| 303 |
+
}}
|
| 304 |
+
>
|
| 305 |
+
{finalStep.metadata.outputTokensUsed.toLocaleString()}
|
| 306 |
+
</Typography>
|
| 307 |
+
</Box>
|
| 308 |
+
</Box>
|
| 309 |
+
</Paper>
|
| 310 |
+
|
| 311 |
+
{/* GIF Error Alert */}
|
| 312 |
+
{gifError && (
|
| 313 |
+
<Alert severity="error" sx={{ fontSize: '0.72rem', py: 0.5 }}>
|
| 314 |
+
{gifError}
|
| 315 |
+
</Alert>
|
| 316 |
+
)}
|
| 317 |
+
|
| 318 |
+
{/* Action Buttons */}
|
| 319 |
+
<Box
|
| 320 |
+
sx={{
|
| 321 |
+
display: 'flex',
|
| 322 |
+
flexDirection: 'column',
|
| 323 |
+
gap: 1.5,
|
| 324 |
+
alignItems: 'center',
|
| 325 |
+
}}
|
| 326 |
+
>
|
| 327 |
+
{/* Download buttons */}
|
| 328 |
+
<Box
|
| 329 |
+
sx={{
|
| 330 |
+
display: 'flex',
|
| 331 |
+
gap: 1,
|
| 332 |
+
justifyContent: 'center',
|
| 333 |
+
flexWrap: 'wrap',
|
| 334 |
+
}}
|
| 335 |
+
>
|
| 336 |
+
<DownloadGifButton
|
| 337 |
+
isGenerating={isGenerating}
|
| 338 |
+
onClick={onGenerateGif}
|
| 339 |
+
disabled={!steps || steps.length === 0}
|
| 340 |
+
/>
|
| 341 |
+
<DownloadJsonButton onClick={onDownloadJson} disabled={!trace} />
|
| 342 |
+
</Box>
|
| 343 |
+
|
| 344 |
+
{/* New Task button - larger and below */}
|
| 345 |
+
<Button
|
| 346 |
+
variant="contained"
|
| 347 |
+
startIcon={<AddIcon sx={{ fontSize: 20 }} />}
|
| 348 |
+
onClick={onBackToHome}
|
| 349 |
+
color="primary"
|
| 350 |
+
sx={{
|
| 351 |
+
textTransform: 'none',
|
| 352 |
+
fontWeight: 700,
|
| 353 |
+
fontSize: '0.9rem',
|
| 354 |
+
px: 3,
|
| 355 |
+
py: 1,
|
| 356 |
+
boxShadow: 2,
|
| 357 |
+
minWidth: 200,
|
| 358 |
+
'&:hover': {
|
| 359 |
+
boxShadow: 4,
|
| 360 |
+
},
|
| 361 |
+
}}
|
| 362 |
+
>
|
| 363 |
+
New Task
|
| 364 |
+
</Button>
|
| 365 |
+
</Box>
|
| 366 |
+
</Box>
|
| 367 |
+
);
|
| 368 |
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Button, CircularProgress, Tooltip } from '@mui/material';
|
| 3 |
+
import GifIcon from '@mui/icons-material/Gif';
|
| 4 |
+
|
| 5 |
+
interface DownloadGifButtonProps {
|
| 6 |
+
isGenerating: boolean;
|
| 7 |
+
onClick: () => void;
|
| 8 |
+
disabled?: boolean;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* Button to download a GIF replay of the trace
|
| 13 |
+
*/
|
| 14 |
+
export const DownloadGifButton: React.FC<DownloadGifButtonProps> = ({
|
| 15 |
+
isGenerating,
|
| 16 |
+
onClick,
|
| 17 |
+
disabled = false,
|
| 18 |
+
}) => {
|
| 19 |
+
return (
|
| 20 |
+
<Tooltip
|
| 21 |
+
title={
|
| 22 |
+
disabled
|
| 23 |
+
? "No steps available"
|
| 24 |
+
: "Download GIF replay"
|
| 25 |
+
}
|
| 26 |
+
>
|
| 27 |
+
<span>
|
| 28 |
+
<Button
|
| 29 |
+
variant="outlined"
|
| 30 |
+
size="small"
|
| 31 |
+
onClick={onClick}
|
| 32 |
+
disabled={disabled || isGenerating}
|
| 33 |
+
startIcon={
|
| 34 |
+
isGenerating ? (
|
| 35 |
+
<CircularProgress size={16} />
|
| 36 |
+
) : (
|
| 37 |
+
<GifIcon sx={{ fontSize: '1.2rem' }} />
|
| 38 |
+
)
|
| 39 |
+
}
|
| 40 |
+
sx={{
|
| 41 |
+
textTransform: 'none',
|
| 42 |
+
fontSize: '0.75rem',
|
| 43 |
+
fontWeight: 600,
|
| 44 |
+
borderRadius: 1,
|
| 45 |
+
px: 1.5,
|
| 46 |
+
py: 0.5,
|
| 47 |
+
borderColor: 'divider',
|
| 48 |
+
color: 'text.primary',
|
| 49 |
+
'&:hover': {
|
| 50 |
+
borderColor: 'primary.main',
|
| 51 |
+
backgroundColor: 'action.hover',
|
| 52 |
+
},
|
| 53 |
+
'&.Mui-disabled': {
|
| 54 |
+
borderColor: 'divider',
|
| 55 |
+
color: 'text.disabled',
|
| 56 |
+
},
|
| 57 |
+
}}
|
| 58 |
+
>
|
| 59 |
+
{isGenerating ? 'Generating...' : 'Download GIF'}
|
| 60 |
+
</Button>
|
| 61 |
+
</span>
|
| 62 |
+
</Tooltip>
|
| 63 |
+
);
|
| 64 |
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Button, Tooltip } from '@mui/material';
|
| 3 |
+
import DownloadIcon from '@mui/icons-material/Download';
|
| 4 |
+
|
| 5 |
+
interface DownloadJsonButtonProps {
|
| 6 |
+
onClick: () => void;
|
| 7 |
+
disabled?: boolean;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/**
|
| 11 |
+
* Button to download trace as JSON
|
| 12 |
+
*/
|
| 13 |
+
export const DownloadJsonButton: React.FC<DownloadJsonButtonProps> = ({
|
| 14 |
+
onClick,
|
| 15 |
+
disabled = false,
|
| 16 |
+
}) => {
|
| 17 |
+
return (
|
| 18 |
+
<Tooltip
|
| 19 |
+
title={
|
| 20 |
+
disabled
|
| 21 |
+
? "No trace available"
|
| 22 |
+
: "Download trace as JSON"
|
| 23 |
+
}
|
| 24 |
+
>
|
| 25 |
+
<span>
|
| 26 |
+
<Button
|
| 27 |
+
variant="outlined"
|
| 28 |
+
size="small"
|
| 29 |
+
onClick={onClick}
|
| 30 |
+
disabled={disabled}
|
| 31 |
+
startIcon={<DownloadIcon sx={{ fontSize: '1.2rem' }} />}
|
| 32 |
+
sx={{
|
| 33 |
+
textTransform: 'none',
|
| 34 |
+
fontSize: '0.75rem',
|
| 35 |
+
fontWeight: 600,
|
| 36 |
+
borderRadius: 1,
|
| 37 |
+
px: 1.5,
|
| 38 |
+
py: 0.5,
|
| 39 |
+
borderColor: 'divider',
|
| 40 |
+
color: 'text.primary',
|
| 41 |
+
'&:hover': {
|
| 42 |
+
borderColor: 'primary.main',
|
| 43 |
+
backgroundColor: 'action.hover',
|
| 44 |
+
},
|
| 45 |
+
'&.Mui-disabled': {
|
| 46 |
+
borderColor: 'divider',
|
| 47 |
+
color: 'text.disabled',
|
| 48 |
+
},
|
| 49 |
+
}}
|
| 50 |
+
>
|
| 51 |
+
Download JSON Trace
|
| 52 |
+
</Button>
|
| 53 |
+
</span>
|
| 54 |
+
</Tooltip>
|
| 55 |
+
);
|
| 56 |
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { CompletionView } from './CompletionView';
|
| 2 |
+
export { DownloadGifButton } from './DownloadGifButton';
|
| 3 |
+
export { DownloadJsonButton } from './DownloadJsonButton';
|
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { SandboxViewer } from './SandboxViewer';
|
| 2 |
+
export { CompletionView, DownloadGifButton, DownloadJsonButton } from './completionview';
|
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Card, CardContent, Box, Typography, CircularProgress } from '@mui/material';
|
| 3 |
+
import CableIcon from '@mui/icons-material/Cable';
|
| 4 |
+
import { keyframes } from '@mui/system';
|
| 5 |
+
|
| 6 |
+
// Animation de pulsation pour le border
|
| 7 |
+
const borderPulse = keyframes`
|
| 8 |
+
0%, 100% {
|
| 9 |
+
border-color: rgba(79, 134, 198, 0.4);
|
| 10 |
+
box-shadow: 0 2px 8px rgba(79, 134, 198, 0.15);
|
| 11 |
+
}
|
| 12 |
+
50% {
|
| 13 |
+
border-color: rgba(79, 134, 198, 0.8);
|
| 14 |
+
box-shadow: 0 2px 12px rgba(79, 134, 198, 0.3);
|
| 15 |
+
}
|
| 16 |
+
`;
|
| 17 |
+
|
| 18 |
+
// Animation de pulsation pour le fond
|
| 19 |
+
const backgroundPulse = keyframes`
|
| 20 |
+
0%, 100% {
|
| 21 |
+
background-color: rgba(79, 134, 198, 0.03);
|
| 22 |
+
}
|
| 23 |
+
50% {
|
| 24 |
+
background-color: rgba(79, 134, 198, 0.08);
|
| 25 |
+
}
|
| 26 |
+
`;
|
| 27 |
+
|
| 28 |
+
interface ConnectionStepCardProps {
|
| 29 |
+
isConnecting: boolean;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export const ConnectionStepCard: React.FC<ConnectionStepCardProps> = ({ isConnecting }) => {
|
| 33 |
+
return (
|
| 34 |
+
<Card
|
| 35 |
+
elevation={0}
|
| 36 |
+
sx={{
|
| 37 |
+
backgroundColor: 'background.paper',
|
| 38 |
+
border: '2px solid',
|
| 39 |
+
borderColor: isConnecting ? 'primary.main' : 'success.main',
|
| 40 |
+
borderRadius: 1.5,
|
| 41 |
+
animation: isConnecting ? `${borderPulse} 2s ease-in-out infinite` : 'none',
|
| 42 |
+
position: 'relative',
|
| 43 |
+
overflow: 'hidden',
|
| 44 |
+
'&::before': isConnecting ? {
|
| 45 |
+
content: '""',
|
| 46 |
+
position: 'absolute',
|
| 47 |
+
top: 0,
|
| 48 |
+
left: 0,
|
| 49 |
+
right: 0,
|
| 50 |
+
bottom: 0,
|
| 51 |
+
animation: `${backgroundPulse} 2s ease-in-out infinite`,
|
| 52 |
+
zIndex: 0,
|
| 53 |
+
} : {},
|
| 54 |
+
}}
|
| 55 |
+
>
|
| 56 |
+
<CardContent sx={{ p: 1.5, '&:last-child': { pb: 1.5 }, position: 'relative', zIndex: 1 }}>
|
| 57 |
+
{/* Header avec spinner ou check */}
|
| 58 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
| 59 |
+
<Box
|
| 60 |
+
sx={{
|
| 61 |
+
display: 'flex',
|
| 62 |
+
alignItems: 'center',
|
| 63 |
+
justifyContent: 'center',
|
| 64 |
+
position: 'relative',
|
| 65 |
+
}}
|
| 66 |
+
>
|
| 67 |
+
{isConnecting ? (
|
| 68 |
+
<CircularProgress
|
| 69 |
+
size={32}
|
| 70 |
+
thickness={2.5}
|
| 71 |
+
sx={{
|
| 72 |
+
color: 'primary.main',
|
| 73 |
+
}}
|
| 74 |
+
/>
|
| 75 |
+
) : (
|
| 76 |
+
<CableIcon
|
| 77 |
+
sx={{
|
| 78 |
+
fontSize: 28,
|
| 79 |
+
color: 'success.main',
|
| 80 |
+
}}
|
| 81 |
+
/>
|
| 82 |
+
)}
|
| 83 |
+
</Box>
|
| 84 |
+
|
| 85 |
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
| 86 |
+
<Typography
|
| 87 |
+
sx={{
|
| 88 |
+
fontSize: '0.85rem',
|
| 89 |
+
fontWeight: 700,
|
| 90 |
+
color: isConnecting ? 'primary.main' : 'success.main',
|
| 91 |
+
lineHeight: 1.3,
|
| 92 |
+
}}
|
| 93 |
+
>
|
| 94 |
+
{isConnecting ? 'Connecting to E2B...' : 'Connected to E2B'}
|
| 95 |
+
</Typography>
|
| 96 |
+
<Typography
|
| 97 |
+
sx={{
|
| 98 |
+
fontSize: '0.7rem',
|
| 99 |
+
color: 'text.secondary',
|
| 100 |
+
lineHeight: 1.2,
|
| 101 |
+
}}
|
| 102 |
+
>
|
| 103 |
+
{isConnecting ? 'Setting up sandbox environment' : 'Sandbox ready'}
|
| 104 |
+
</Typography>
|
| 105 |
+
</Box>
|
| 106 |
+
</Box>
|
| 107 |
+
</CardContent>
|
| 108 |
+
</Card>
|
| 109 |
+
);
|
| 110 |
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { FinalStep } from '@/types/agent';
|
| 2 |
+
import React from 'react';
|
| 3 |
+
import { Card, CardContent, Box, Typography } from '@mui/material';
|
| 4 |
+
import CheckIcon from '@mui/icons-material/Check';
|
| 5 |
+
import CloseIcon from '@mui/icons-material/Close';
|
| 6 |
+
import { useAgentStore } from '@/stores/agentStore';
|
| 7 |
+
|
| 8 |
+
interface FinalStepCardProps {
|
| 9 |
+
finalStep: FinalStep;
|
| 10 |
+
isActive?: boolean;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export const FinalStepCard: React.FC<FinalStepCardProps> = ({ finalStep, isActive = false }) => {
|
| 14 |
+
const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex);
|
| 15 |
+
|
| 16 |
+
const isSuccess = finalStep.type === 'success';
|
| 17 |
+
|
| 18 |
+
const handleClick = () => {
|
| 19 |
+
// Clicking on final step goes to live mode (null)
|
| 20 |
+
setSelectedStepIndex(null);
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
return (
|
| 24 |
+
<Card
|
| 25 |
+
elevation={0}
|
| 26 |
+
onClick={handleClick}
|
| 27 |
+
sx={{
|
| 28 |
+
backgroundColor: 'background.paper',
|
| 29 |
+
border: '1px solid',
|
| 30 |
+
borderColor: (theme) => `${isActive
|
| 31 |
+
? isSuccess ? theme.palette.success.main : theme.palette.error.main
|
| 32 |
+
: theme.palette.divider} !important`,
|
| 33 |
+
borderRadius: 1.5,
|
| 34 |
+
transition: 'all 0.2s ease',
|
| 35 |
+
cursor: 'pointer',
|
| 36 |
+
boxShadow: isActive
|
| 37 |
+
? (theme) => isSuccess
|
| 38 |
+
? `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.3)' : 'rgba(102, 187, 106, 0.2)'}`
|
| 39 |
+
: `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.3)' : 'rgba(244, 67, 54, 0.2)'}`
|
| 40 |
+
: 'none',
|
| 41 |
+
'&:hover': {
|
| 42 |
+
borderColor: (theme) => `${isSuccess ? theme.palette.success.main : theme.palette.error.main} !important`,
|
| 43 |
+
boxShadow: (theme) => isSuccess
|
| 44 |
+
? `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.2)' : 'rgba(102, 187, 106, 0.1)'}`
|
| 45 |
+
: `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.2)' : 'rgba(244, 67, 54, 0.1)'}`,
|
| 46 |
+
},
|
| 47 |
+
}}
|
| 48 |
+
>
|
| 49 |
+
<CardContent sx={{ p: 1.5, '&:last-child': { pb: 1.5 } }}>
|
| 50 |
+
{/* Header with icon */}
|
| 51 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
| 52 |
+
{isSuccess ? (
|
| 53 |
+
<CheckIcon sx={{ fontSize: 20, color: 'success.main' }} />
|
| 54 |
+
) : (
|
| 55 |
+
<CloseIcon sx={{ fontSize: 20, color: 'error.main' }} />
|
| 56 |
+
)}
|
| 57 |
+
<Typography
|
| 58 |
+
sx={{
|
| 59 |
+
fontSize: '0.85rem',
|
| 60 |
+
fontWeight: 700,
|
| 61 |
+
color: isSuccess ? 'success.main' : 'error.main',
|
| 62 |
+
}}
|
| 63 |
+
>
|
| 64 |
+
{isSuccess ? 'Task completed' : 'Task failed'}
|
| 65 |
+
</Typography>
|
| 66 |
+
</Box>
|
| 67 |
+
</CardContent>
|
| 68 |
+
</Card>
|
| 69 |
+
);
|
| 70 |
+
};
|
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentStep } from '@/types/agent';
|
| 2 |
+
import React, { useState } from 'react';
|
| 3 |
+
import { Card, CardContent, Box, Typography, Divider, Chip, Paper, Accordion, AccordionSummary, AccordionDetails, IconButton, Tooltip } from '@mui/material';
|
| 4 |
+
import ThoughtBubbleIcon from '@mui/icons-material/Psychology';
|
| 5 |
+
import BoltIcon from '@mui/icons-material/Bolt';
|
| 6 |
+
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
| 7 |
+
import InputIcon from '@mui/icons-material/Input';
|
| 8 |
+
import OutputIcon from '@mui/icons-material/Output';
|
| 9 |
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
| 10 |
+
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
|
| 11 |
+
import ThumbDownIcon from '@mui/icons-material/ThumbDown';
|
| 12 |
+
import { useAgentStore } from '@/stores/agentStore';
|
| 13 |
+
import { updateStepEvaluation } from '@/services/api';
|
| 14 |
+
|
| 15 |
+
interface StepCardProps {
|
| 16 |
+
step: AgentStep;
|
| 17 |
+
index: number;
|
| 18 |
+
isLatest?: boolean;
|
| 19 |
+
isActive?: boolean;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export const StepCard: React.FC<StepCardProps> = ({ step, index, isLatest = false, isActive = false }) => {
|
| 23 |
+
const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex);
|
| 24 |
+
const [thoughtExpanded, setThoughtExpanded] = useState(false);
|
| 25 |
+
const [evaluation, setEvaluation] = useState<'like' | 'dislike' | 'neutral'>(step.step_evaluation || 'neutral');
|
| 26 |
+
const [isVoting, setIsVoting] = useState(false);
|
| 27 |
+
|
| 28 |
+
const handleClick = () => {
|
| 29 |
+
setSelectedStepIndex(index);
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const handleAccordionClick = (event: React.MouseEvent) => {
|
| 33 |
+
event.stopPropagation(); // Empêcher la propagation pour ne pas sélectionner la step
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
const handleVote = async (event: React.MouseEvent, vote: 'like' | 'dislike') => {
|
| 37 |
+
event.stopPropagation(); // Empêcher la propagation pour ne pas sélectionner la step
|
| 38 |
+
|
| 39 |
+
if (isVoting) return;
|
| 40 |
+
|
| 41 |
+
const newEvaluation = evaluation === vote ? 'neutral' : vote;
|
| 42 |
+
setIsVoting(true);
|
| 43 |
+
|
| 44 |
+
try {
|
| 45 |
+
await updateStepEvaluation(step.traceId, step.stepId, newEvaluation);
|
| 46 |
+
setEvaluation(newEvaluation);
|
| 47 |
+
} catch (error) {
|
| 48 |
+
console.error('Failed to update step evaluation:', error);
|
| 49 |
+
} finally {
|
| 50 |
+
setIsVoting(false);
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
return (
|
| 55 |
+
<Card
|
| 56 |
+
elevation={0}
|
| 57 |
+
onClick={handleClick}
|
| 58 |
+
sx={{
|
| 59 |
+
backgroundColor: 'background.paper',
|
| 60 |
+
border: '1px solid',
|
| 61 |
+
borderColor: (theme) => `${isActive ? theme.palette.primary.main : theme.palette.divider} !important`,
|
| 62 |
+
borderRadius: 1.5,
|
| 63 |
+
transition: 'all 0.2s ease',
|
| 64 |
+
cursor: 'pointer',
|
| 65 |
+
boxShadow: isActive ? (theme) => `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(79, 134, 198, 0.3)' : 'rgba(79, 134, 198, 0.2)'}` : 'none',
|
| 66 |
+
'&:hover': {
|
| 67 |
+
borderColor: (theme) => `${theme.palette.primary.main} !important`,
|
| 68 |
+
boxShadow: (theme) => `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(79, 134, 198, 0.2)' : 'rgba(79, 134, 198, 0.1)'}`,
|
| 69 |
+
},
|
| 70 |
+
}}
|
| 71 |
+
>
|
| 72 |
+
<CardContent sx={{ p: 1.5, '&:last-child': { pb: 1.5 } }}>
|
| 73 |
+
{/* Step header */}
|
| 74 |
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
|
| 75 |
+
<Typography
|
| 76 |
+
sx={{
|
| 77 |
+
fontSize: '1.5rem',
|
| 78 |
+
fontWeight: 800,
|
| 79 |
+
color: isActive ? 'primary.main' : 'text.primary',
|
| 80 |
+
lineHeight: 1,
|
| 81 |
+
}}
|
| 82 |
+
>
|
| 83 |
+
{index + 1}
|
| 84 |
+
</Typography>
|
| 85 |
+
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
|
| 86 |
+
<Chip
|
| 87 |
+
icon={<AccessTimeIcon sx={{ fontSize: '0.7rem !important' }} />}
|
| 88 |
+
label={`${step.duration.toFixed(1)}s`}
|
| 89 |
+
size="small"
|
| 90 |
+
sx={{
|
| 91 |
+
height: 'auto',
|
| 92 |
+
py: 0.25,
|
| 93 |
+
fontSize: '0.65rem',
|
| 94 |
+
fontWeight: 600,
|
| 95 |
+
backgroundColor: 'action.hover',
|
| 96 |
+
color: 'text.primary',
|
| 97 |
+
'& .MuiChip-icon': { marginLeft: 0.5, color: 'text.secondary' },
|
| 98 |
+
}}
|
| 99 |
+
/>
|
| 100 |
+
<Chip
|
| 101 |
+
icon={<InputIcon sx={{ fontSize: '0.7rem !important' }} />}
|
| 102 |
+
label={step.inputTokensUsed.toLocaleString()}
|
| 103 |
+
size="small"
|
| 104 |
+
sx={{
|
| 105 |
+
height: 'auto',
|
| 106 |
+
py: 0.25,
|
| 107 |
+
fontSize: '0.65rem',
|
| 108 |
+
fontWeight: 600,
|
| 109 |
+
backgroundColor: 'action.hover',
|
| 110 |
+
color: 'text.primary',
|
| 111 |
+
'& .MuiChip-icon': { marginLeft: 0.5, color: 'text.secondary' },
|
| 112 |
+
}}
|
| 113 |
+
/>
|
| 114 |
+
<Chip
|
| 115 |
+
icon={<OutputIcon sx={{ fontSize: '0.7rem !important' }} />}
|
| 116 |
+
label={step.outputTokensUsed.toLocaleString()}
|
| 117 |
+
size="small"
|
| 118 |
+
sx={{
|
| 119 |
+
height: 'auto',
|
| 120 |
+
py: 0.25,
|
| 121 |
+
fontSize: '0.65rem',
|
| 122 |
+
fontWeight: 600,
|
| 123 |
+
backgroundColor: 'action.hover',
|
| 124 |
+
color: 'text.primary',
|
| 125 |
+
'& .MuiChip-icon': { marginLeft: 0.5, color: 'text.secondary' },
|
| 126 |
+
}}
|
| 127 |
+
/>
|
| 128 |
+
</Box>
|
| 129 |
+
</Box>
|
| 130 |
+
|
| 131 |
+
{/* Step image */}
|
| 132 |
+
{step.image && (
|
| 133 |
+
<Box
|
| 134 |
+
sx={{
|
| 135 |
+
mb: 1.5,
|
| 136 |
+
borderRadius: 1,
|
| 137 |
+
overflow: 'hidden',
|
| 138 |
+
border: '1px solid',
|
| 139 |
+
borderColor: (theme) => isActive ? theme.palette.primary.main : theme.palette.divider,
|
| 140 |
+
backgroundColor: 'action.hover',
|
| 141 |
+
transition: 'border-color 0.2s ease',
|
| 142 |
+
}}
|
| 143 |
+
>
|
| 144 |
+
<img
|
| 145 |
+
src={step.image}
|
| 146 |
+
alt={`Step ${index + 1}`}
|
| 147 |
+
style={{ width: '100%', height: 'auto', display: 'block' }}
|
| 148 |
+
/>
|
| 149 |
+
</Box>
|
| 150 |
+
)}
|
| 151 |
+
|
| 152 |
+
{/* Action */}
|
| 153 |
+
{step.actions && step.actions.length > 0 && (
|
| 154 |
+
<Box sx={{ mb: 1.5 }}>
|
| 155 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.75, justifyContent: 'space-between' }}>
|
| 156 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 157 |
+
<Typography
|
| 158 |
+
variant="caption"
|
| 159 |
+
sx={{
|
| 160 |
+
fontWeight: 700,
|
| 161 |
+
color: 'text.secondary',
|
| 162 |
+
fontSize: '0.65rem',
|
| 163 |
+
textTransform: 'uppercase',
|
| 164 |
+
letterSpacing: '0.5px',
|
| 165 |
+
}}
|
| 166 |
+
>
|
| 167 |
+
Action
|
| 168 |
+
</Typography>
|
| 169 |
+
</Box>
|
| 170 |
+
|
| 171 |
+
{/* Vote buttons */}
|
| 172 |
+
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
| 173 |
+
<Tooltip title={evaluation === 'like' ? 'Remove like' : 'Like this step'}>
|
| 174 |
+
<IconButton
|
| 175 |
+
size="small"
|
| 176 |
+
onClick={(e) => handleVote(e, 'like')}
|
| 177 |
+
disabled={isVoting}
|
| 178 |
+
sx={{
|
| 179 |
+
padding: '2px',
|
| 180 |
+
color: evaluation === 'like' ? 'success.main' : 'action.disabled',
|
| 181 |
+
'&:hover': {
|
| 182 |
+
color: 'success.main',
|
| 183 |
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.1)' : 'rgba(102, 187, 106, 0.08)',
|
| 184 |
+
},
|
| 185 |
+
}}
|
| 186 |
+
>
|
| 187 |
+
<ThumbUpIcon sx={{ fontSize: 14 }} />
|
| 188 |
+
</IconButton>
|
| 189 |
+
</Tooltip>
|
| 190 |
+
<Tooltip title={evaluation === 'dislike' ? 'Remove dislike' : 'Dislike this step'}>
|
| 191 |
+
<IconButton
|
| 192 |
+
size="small"
|
| 193 |
+
onClick={(e) => handleVote(e, 'dislike')}
|
| 194 |
+
disabled={isVoting}
|
| 195 |
+
sx={{
|
| 196 |
+
padding: '2px',
|
| 197 |
+
color: evaluation === 'dislike' ? 'error.main' : 'action.disabled',
|
| 198 |
+
'&:hover': {
|
| 199 |
+
color: 'error.main',
|
| 200 |
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.1)' : 'rgba(244, 67, 54, 0.08)',
|
| 201 |
+
},
|
| 202 |
+
}}
|
| 203 |
+
>
|
| 204 |
+
<ThumbDownIcon sx={{ fontSize: 14 }} />
|
| 205 |
+
</IconButton>
|
| 206 |
+
</Tooltip>
|
| 207 |
+
</Box>
|
| 208 |
+
</Box>
|
| 209 |
+
<Box component="ul" sx={{ listStyle: 'none', p: 0, m: 0}}>
|
| 210 |
+
{step.actions.map((action, actionIndex) => (
|
| 211 |
+
<Box
|
| 212 |
+
key={actionIndex}
|
| 213 |
+
component="li"
|
| 214 |
+
sx={{
|
| 215 |
+
display: 'flex',
|
| 216 |
+
alignItems: 'flex-start',
|
| 217 |
+
fontSize: '0.75rem',
|
| 218 |
+
color: 'text.primary',
|
| 219 |
+
lineHeight: 1.4,
|
| 220 |
+
mb: 0.5,
|
| 221 |
+
'&:last-child': { mb: 0 },
|
| 222 |
+
}}
|
| 223 |
+
>
|
| 224 |
+
{/* <Typography
|
| 225 |
+
component="span"
|
| 226 |
+
sx={{
|
| 227 |
+
mr: 0.5,
|
| 228 |
+
color: 'text.secondary',
|
| 229 |
+
fontWeight: 700,
|
| 230 |
+
flexShrink: 0,
|
| 231 |
+
fontSize: '0.75rem',
|
| 232 |
+
}}
|
| 233 |
+
>
|
| 234 |
+
→
|
| 235 |
+
</Typography> */}
|
| 236 |
+
<Typography
|
| 237 |
+
component="span"
|
| 238 |
+
sx={{
|
| 239 |
+
fontSize: '0.75rem',
|
| 240 |
+
fontWeight: 900,
|
| 241 |
+
wordBreak: 'break-word',
|
| 242 |
+
}}
|
| 243 |
+
>
|
| 244 |
+
{action.description}
|
| 245 |
+
</Typography>
|
| 246 |
+
</Box>
|
| 247 |
+
))}
|
| 248 |
+
</Box>
|
| 249 |
+
</Box>
|
| 250 |
+
)}
|
| 251 |
+
|
| 252 |
+
{/* Thought - Accordion */}
|
| 253 |
+
{step.thought && (
|
| 254 |
+
<Accordion
|
| 255 |
+
expanded={thoughtExpanded}
|
| 256 |
+
onChange={(e, expanded) => setThoughtExpanded(expanded)}
|
| 257 |
+
onClick={handleAccordionClick}
|
| 258 |
+
elevation={0}
|
| 259 |
+
disableGutters
|
| 260 |
+
sx={{
|
| 261 |
+
mb: 0.5,
|
| 262 |
+
backgroundColor: 'transparent',
|
| 263 |
+
border: 'none',
|
| 264 |
+
boxShadow: 'none',
|
| 265 |
+
'&:before': { display: 'none' },
|
| 266 |
+
'&.MuiAccordion-root': {
|
| 267 |
+
backgroundColor: 'transparent',
|
| 268 |
+
boxShadow: 'none',
|
| 269 |
+
'&:before': {
|
| 270 |
+
display: 'none',
|
| 271 |
+
},
|
| 272 |
+
},
|
| 273 |
+
'& .MuiAccordionSummary-root': {
|
| 274 |
+
minHeight: 'auto',
|
| 275 |
+
p: 0,
|
| 276 |
+
backgroundColor: 'transparent',
|
| 277 |
+
'&:hover': {
|
| 278 |
+
backgroundColor: 'transparent',
|
| 279 |
+
},
|
| 280 |
+
'&.Mui-expanded': {
|
| 281 |
+
minHeight: 'auto',
|
| 282 |
+
},
|
| 283 |
+
},
|
| 284 |
+
'& .MuiAccordionSummary-content': {
|
| 285 |
+
margin: '0 !important',
|
| 286 |
+
},
|
| 287 |
+
'& .MuiAccordionDetails-root': {
|
| 288 |
+
p: 0,
|
| 289 |
+
pt: 0.5,
|
| 290 |
+
pb: 0,
|
| 291 |
+
backgroundColor: 'transparent',
|
| 292 |
+
},
|
| 293 |
+
}}
|
| 294 |
+
>
|
| 295 |
+
<AccordionSummary
|
| 296 |
+
expandIcon={<ExpandMoreIcon sx={{ fontSize: 16, color: 'text.secondary' }} />}
|
| 297 |
+
sx={{
|
| 298 |
+
flexDirection: 'row',
|
| 299 |
+
border: 'none',
|
| 300 |
+
'& .MuiAccordionSummary-expandIconWrapper': {
|
| 301 |
+
transform: 'rotate(-90deg)',
|
| 302 |
+
transition: 'transform 0.2s',
|
| 303 |
+
'&.Mui-expanded': {
|
| 304 |
+
transform: 'rotate(0deg)',
|
| 305 |
+
},
|
| 306 |
+
},
|
| 307 |
+
}}
|
| 308 |
+
>
|
| 309 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
| 310 |
+
<Typography
|
| 311 |
+
variant="caption"
|
| 312 |
+
sx={{
|
| 313 |
+
fontWeight: 700,
|
| 314 |
+
color: 'text.secondary',
|
| 315 |
+
fontSize: '0.65rem',
|
| 316 |
+
textTransform: 'uppercase',
|
| 317 |
+
letterSpacing: '0.5px',
|
| 318 |
+
}}
|
| 319 |
+
>
|
| 320 |
+
Thought
|
| 321 |
+
</Typography>
|
| 322 |
+
</Box>
|
| 323 |
+
</AccordionSummary>
|
| 324 |
+
<AccordionDetails>
|
| 325 |
+
<Typography
|
| 326 |
+
variant="body2"
|
| 327 |
+
sx={{
|
| 328 |
+
fontSize: '0.75rem',
|
| 329 |
+
color: 'text.primary',
|
| 330 |
+
lineHeight: 1.4,
|
| 331 |
+
pl: 2.5,
|
| 332 |
+
}}
|
| 333 |
+
>
|
| 334 |
+
{step.thought}
|
| 335 |
+
</Typography>
|
| 336 |
+
</AccordionDetails>
|
| 337 |
+
</Accordion>
|
| 338 |
+
)}
|
| 339 |
+
|
| 340 |
+
{/* Error */}
|
| 341 |
+
{step.error && (
|
| 342 |
+
<Box sx={{ mt: 1.5, p: 1, borderRadius: 1, backgroundColor: 'error.main', opacity: 0.1 }}>
|
| 343 |
+
<Typography
|
| 344 |
+
variant="caption"
|
| 345 |
+
sx={{
|
| 346 |
+
fontSize: '0.7rem',
|
| 347 |
+
color: 'error.main',
|
| 348 |
+
fontWeight: 600,
|
| 349 |
+
}}
|
| 350 |
+
>
|
| 351 |
+
Error: {step.error}
|
| 352 |
+
</Typography>
|
| 353 |
+
</Box>
|
| 354 |
+
)}
|
| 355 |
+
</CardContent>
|
| 356 |
+
</Card>
|
| 357 |
+
);
|
| 358 |
+
};
|
|
@@ -0,0 +1,388 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useRef, useEffect } from 'react';
|
| 2 |
+
import { AgentTrace } from '@/types/agent';
|
| 3 |
+
import { Box, Typography, Stack, Paper } from '@mui/material';
|
| 4 |
+
import { StepCard } from './StepCard';
|
| 5 |
+
import { FinalStepCard } from './FinalStepCard';
|
| 6 |
+
import { ThinkingStepCard } from './ThinkingStepCard';
|
| 7 |
+
import { ConnectionStepCard } from './ConnectionStepCard';
|
| 8 |
+
import ListAltIcon from '@mui/icons-material/ListAlt';
|
| 9 |
+
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
| 10 |
+
import { useAgentStore, selectSelectedStepIndex, selectFinalStep, selectIsConnectingToE2B, selectIsAgentProcessing } from '@/stores/agentStore';
|
| 11 |
+
|
| 12 |
+
interface StepsListProps {
|
| 13 |
+
trace?: AgentTrace;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export const StepsList: React.FC<StepsListProps> = ({ trace }) => {
|
| 17 |
+
const containerRef = useRef<HTMLDivElement>(null);
|
| 18 |
+
const selectedStepIndex = useAgentStore(selectSelectedStepIndex);
|
| 19 |
+
const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex);
|
| 20 |
+
const finalStep = useAgentStore(selectFinalStep);
|
| 21 |
+
const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B);
|
| 22 |
+
const isAgentProcessing = useAgentStore(selectIsAgentProcessing);
|
| 23 |
+
const isScrollingProgrammatically = useRef(false);
|
| 24 |
+
const [showThinkingCard, setShowThinkingCard] = React.useState(false);
|
| 25 |
+
const thinkingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
| 26 |
+
const streamStartTimeRef = useRef<number | null>(null);
|
| 27 |
+
const [showConnectionCard, setShowConnectionCard] = React.useState(false);
|
| 28 |
+
const hasConnectedRef = useRef(false);
|
| 29 |
+
|
| 30 |
+
// Check if final step is active (when selectedStepIndex is null and finalStep exists and trace is not running)
|
| 31 |
+
const isFinalStepActive = selectedStepIndex === null && finalStep && !trace?.isRunning;
|
| 32 |
+
|
| 33 |
+
// Determine the active step index
|
| 34 |
+
// If a specific step is selected, use that
|
| 35 |
+
// If the final step is active, no normal step should be active
|
| 36 |
+
// Otherwise, show the last step as active
|
| 37 |
+
const activeStepIndex = selectedStepIndex !== null
|
| 38 |
+
? selectedStepIndex
|
| 39 |
+
: isFinalStepActive
|
| 40 |
+
? null // When final step is active, no normal step is active
|
| 41 |
+
: (trace?.steps && trace.steps.length > 0 && trace?.isRunning)
|
| 42 |
+
? trace.steps.length - 1
|
| 43 |
+
: (trace?.steps && trace.steps.length > 0)
|
| 44 |
+
? trace.steps.length - 1
|
| 45 |
+
: null;
|
| 46 |
+
|
| 47 |
+
// Manage ConnectionStepCard display:
|
| 48 |
+
// - Shows when isConnectingToE2B = true OR when we had a connection
|
| 49 |
+
// - Remains visible even when task is finished (if we have steps or finalStep)
|
| 50 |
+
useEffect(() => {
|
| 51 |
+
if (isConnectingToE2B || isAgentProcessing || (trace?.steps && trace.steps.length > 0) || finalStep) {
|
| 52 |
+
setShowConnectionCard(true);
|
| 53 |
+
hasConnectedRef.current = true;
|
| 54 |
+
}
|
| 55 |
+
}, [isConnectingToE2B, isAgentProcessing, trace?.steps, finalStep]);
|
| 56 |
+
|
| 57 |
+
// Manage ThinkingCard display:
|
| 58 |
+
// - Appears 5 seconds AFTER stream starts (isAgentProcessing = true, NOT during isConnectingToE2B)
|
| 59 |
+
// - Remains visible during the entire agent processing
|
| 60 |
+
// - Hides only when agent stops OR a finalStep exists
|
| 61 |
+
useEffect(() => {
|
| 62 |
+
// Si le stream démarre vraiment (isAgentProcessing = true et PAS en train de se connecter)
|
| 63 |
+
// Et pas encore de startTime enregistré
|
| 64 |
+
if (isAgentProcessing && !isConnectingToE2B && !streamStartTimeRef.current) {
|
| 65 |
+
streamStartTimeRef.current = Date.now();
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// Si l'agent s'arrête OU qu'on a un finalStep, reset et cacher
|
| 69 |
+
if (!isAgentProcessing || finalStep) {
|
| 70 |
+
streamStartTimeRef.current = null;
|
| 71 |
+
setShowThinkingCard(false);
|
| 72 |
+
if (thinkingTimeoutRef.current) {
|
| 73 |
+
clearTimeout(thinkingTimeoutRef.current);
|
| 74 |
+
thinkingTimeoutRef.current = null;
|
| 75 |
+
}
|
| 76 |
+
return;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// If agent is running, not connecting, no finalStep: start 5 second timer
|
| 80 |
+
if (isAgentProcessing && !isConnectingToE2B && !finalStep && streamStartTimeRef.current) {
|
| 81 |
+
// Clean up any existing timeout
|
| 82 |
+
if (thinkingTimeoutRef.current) {
|
| 83 |
+
clearTimeout(thinkingTimeoutRef.current);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// Calculer le temps écoulé depuis le début du stream
|
| 87 |
+
const elapsedTime = Date.now() - streamStartTimeRef.current;
|
| 88 |
+
const remainingTime = Math.max(0, 5000 - elapsedTime);
|
| 89 |
+
|
| 90 |
+
thinkingTimeoutRef.current = setTimeout(() => {
|
| 91 |
+
setShowThinkingCard(true);
|
| 92 |
+
}, remainingTime);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
// Cleanup on unmount or when dependencies change
|
| 96 |
+
return () => {
|
| 97 |
+
if (thinkingTimeoutRef.current) {
|
| 98 |
+
clearTimeout(thinkingTimeoutRef.current);
|
| 99 |
+
thinkingTimeoutRef.current = null;
|
| 100 |
+
}
|
| 101 |
+
};
|
| 102 |
+
}, [isAgentProcessing, isConnectingToE2B, finalStep]);
|
| 103 |
+
|
| 104 |
+
// Auto-scroll to active step when it changes (timeline → steps)
|
| 105 |
+
useEffect(() => {
|
| 106 |
+
if (containerRef.current) {
|
| 107 |
+
isScrollingProgrammatically.current = true;
|
| 108 |
+
// Use setTimeout to ensure DOM has updated
|
| 109 |
+
setTimeout(() => {
|
| 110 |
+
if (containerRef.current) {
|
| 111 |
+
// Scroll to final step if it's active
|
| 112 |
+
if (isFinalStepActive) {
|
| 113 |
+
const finalStepElement = containerRef.current.querySelector(`[data-step-index="final"]`);
|
| 114 |
+
if (finalStepElement) {
|
| 115 |
+
finalStepElement.scrollIntoView({
|
| 116 |
+
behavior: 'smooth',
|
| 117 |
+
block: 'center'
|
| 118 |
+
});
|
| 119 |
+
setTimeout(() => {
|
| 120 |
+
isScrollingProgrammatically.current = false;
|
| 121 |
+
}, 500);
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
// Otherwise scroll to active step
|
| 125 |
+
else if (activeStepIndex !== null && trace?.steps) {
|
| 126 |
+
const activeStepElement = containerRef.current.querySelector(`[data-step-index="${activeStepIndex}"]`);
|
| 127 |
+
if (activeStepElement) {
|
| 128 |
+
activeStepElement.scrollIntoView({
|
| 129 |
+
behavior: 'smooth',
|
| 130 |
+
block: 'center'
|
| 131 |
+
});
|
| 132 |
+
// Reset flag after scroll animation
|
| 133 |
+
setTimeout(() => {
|
| 134 |
+
isScrollingProgrammatically.current = false;
|
| 135 |
+
}, 500);
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
}, 100);
|
| 140 |
+
}
|
| 141 |
+
}, [activeStepIndex, trace?.steps?.length, isFinalStepActive]);
|
| 142 |
+
|
| 143 |
+
// Detect which step is visible when scrolling (steps → timeline)
|
| 144 |
+
useEffect(() => {
|
| 145 |
+
const container = containerRef.current;
|
| 146 |
+
if (!container || !trace?.steps || trace.steps.length === 0) return;
|
| 147 |
+
|
| 148 |
+
const handleScroll = () => {
|
| 149 |
+
// Don't update if we're scrolling programmatically
|
| 150 |
+
if (isScrollingProgrammatically.current) return;
|
| 151 |
+
|
| 152 |
+
const containerRect = container.getBoundingClientRect();
|
| 153 |
+
const containerTop = containerRect.top;
|
| 154 |
+
const containerBottom = containerRect.bottom;
|
| 155 |
+
const containerCenter = containerRect.top + containerRect.height / 2;
|
| 156 |
+
|
| 157 |
+
// Check scroll position
|
| 158 |
+
const isAtTop = container.scrollTop <= 5; // 5px tolerance
|
| 159 |
+
const isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 5; // 5px tolerance
|
| 160 |
+
|
| 161 |
+
let targetStepIndex: number | null = -1;
|
| 162 |
+
let targetDistance = Infinity;
|
| 163 |
+
let isFinalStepTarget = false;
|
| 164 |
+
|
| 165 |
+
if (isAtTop) {
|
| 166 |
+
// At the top: find the highest visible step
|
| 167 |
+
let highestVisibleBottom = Infinity;
|
| 168 |
+
|
| 169 |
+
trace.steps.forEach((_, index) => {
|
| 170 |
+
const stepElement = container.querySelector(`[data-step-index="${index}"]`);
|
| 171 |
+
if (stepElement) {
|
| 172 |
+
const stepRect = stepElement.getBoundingClientRect();
|
| 173 |
+
const stepTop = stepRect.top;
|
| 174 |
+
const stepBottom = stepRect.bottom;
|
| 175 |
+
const isVisible = stepTop < containerBottom && stepBottom > containerTop;
|
| 176 |
+
|
| 177 |
+
if (isVisible && stepTop < highestVisibleBottom) {
|
| 178 |
+
highestVisibleBottom = stepTop;
|
| 179 |
+
targetStepIndex = index;
|
| 180 |
+
isFinalStepTarget = false;
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
});
|
| 184 |
+
} else if (isAtBottom) {
|
| 185 |
+
// At the bottom: find the lowest visible step
|
| 186 |
+
let lowestVisibleTop = -Infinity;
|
| 187 |
+
|
| 188 |
+
trace.steps.forEach((_, index) => {
|
| 189 |
+
const stepElement = container.querySelector(`[data-step-index="${index}"]`);
|
| 190 |
+
if (stepElement) {
|
| 191 |
+
const stepRect = stepElement.getBoundingClientRect();
|
| 192 |
+
const stepTop = stepRect.top;
|
| 193 |
+
const stepBottom = stepRect.bottom;
|
| 194 |
+
const isVisible = stepTop < containerBottom && stepBottom > containerTop;
|
| 195 |
+
|
| 196 |
+
if (isVisible && stepTop > lowestVisibleTop) {
|
| 197 |
+
lowestVisibleTop = stepTop;
|
| 198 |
+
targetStepIndex = index;
|
| 199 |
+
isFinalStepTarget = false;
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
});
|
| 203 |
+
|
| 204 |
+
// Check if final step is the lowest visible
|
| 205 |
+
if (finalStep) {
|
| 206 |
+
const finalStepElement = container.querySelector(`[data-step-index="final"]`);
|
| 207 |
+
if (finalStepElement) {
|
| 208 |
+
const finalStepRect = finalStepElement.getBoundingClientRect();
|
| 209 |
+
const finalStepTop = finalStepRect.top;
|
| 210 |
+
const finalStepBottom = finalStepRect.bottom;
|
| 211 |
+
const isVisible = finalStepTop < containerBottom && finalStepBottom > containerTop;
|
| 212 |
+
|
| 213 |
+
if (isVisible && finalStepTop > lowestVisibleTop) {
|
| 214 |
+
targetStepIndex = null;
|
| 215 |
+
isFinalStepTarget = true;
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
} else {
|
| 220 |
+
// Not at bottom: find the step closest to center
|
| 221 |
+
trace.steps.forEach((_, index) => {
|
| 222 |
+
const stepElement = container.querySelector(`[data-step-index="${index}"]`);
|
| 223 |
+
if (stepElement) {
|
| 224 |
+
const stepRect = stepElement.getBoundingClientRect();
|
| 225 |
+
const stepCenter = stepRect.top + stepRect.height / 2;
|
| 226 |
+
const distance = Math.abs(containerCenter - stepCenter);
|
| 227 |
+
|
| 228 |
+
if (distance < targetDistance) {
|
| 229 |
+
targetDistance = distance;
|
| 230 |
+
targetStepIndex = index;
|
| 231 |
+
isFinalStepTarget = false;
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
});
|
| 235 |
+
|
| 236 |
+
// Check if final step is closest to center
|
| 237 |
+
if (finalStep) {
|
| 238 |
+
const finalStepElement = container.querySelector(`[data-step-index="final"]`);
|
| 239 |
+
if (finalStepElement) {
|
| 240 |
+
const finalStepRect = finalStepElement.getBoundingClientRect();
|
| 241 |
+
const finalStepCenter = finalStepRect.top + finalStepRect.height / 2;
|
| 242 |
+
const distance = Math.abs(containerCenter - finalStepCenter);
|
| 243 |
+
|
| 244 |
+
if (distance < targetDistance) {
|
| 245 |
+
targetStepIndex = null;
|
| 246 |
+
isFinalStepTarget = true;
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
// Update the selected step if changed
|
| 253 |
+
if (isFinalStepTarget && selectedStepIndex !== null) {
|
| 254 |
+
setSelectedStepIndex(null);
|
| 255 |
+
} else if (!isFinalStepTarget && targetStepIndex !== -1 && targetStepIndex !== selectedStepIndex) {
|
| 256 |
+
setSelectedStepIndex(targetStepIndex);
|
| 257 |
+
}
|
| 258 |
+
};
|
| 259 |
+
|
| 260 |
+
// Throttle scroll events
|
| 261 |
+
let scrollTimeout: NodeJS.Timeout;
|
| 262 |
+
const throttledScroll = () => {
|
| 263 |
+
clearTimeout(scrollTimeout);
|
| 264 |
+
scrollTimeout = setTimeout(handleScroll, 150);
|
| 265 |
+
};
|
| 266 |
+
|
| 267 |
+
container.addEventListener('scroll', throttledScroll);
|
| 268 |
+
return () => {
|
| 269 |
+
container.removeEventListener('scroll', throttledScroll);
|
| 270 |
+
clearTimeout(scrollTimeout);
|
| 271 |
+
};
|
| 272 |
+
}, [trace?.steps, selectedStepIndex, setSelectedStepIndex, finalStep]);
|
| 273 |
+
|
| 274 |
+
return (
|
| 275 |
+
<Paper
|
| 276 |
+
elevation={0}
|
| 277 |
+
sx={{
|
| 278 |
+
width: { xs: '100%', md: 320 },
|
| 279 |
+
flexShrink: 0,
|
| 280 |
+
display: 'flex',
|
| 281 |
+
flexDirection: 'column',
|
| 282 |
+
ml: { xs: 0, md: 1.5 },
|
| 283 |
+
mt: { xs: 3, md: 0 },
|
| 284 |
+
overflow: 'hidden',
|
| 285 |
+
}}
|
| 286 |
+
>
|
| 287 |
+
<Box sx={{ px: 2, py: 1.5, borderBottom: '1px solid', borderColor: 'divider', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
| 288 |
+
<Typography variant="h6" sx={{ fontSize: '0.9rem', fontWeight: 700, color: 'text.primary' }}>
|
| 289 |
+
Steps
|
| 290 |
+
</Typography>
|
| 291 |
+
{trace?.traceMetadata && trace.traceMetadata.numberOfSteps > 0 && (
|
| 292 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
| 293 |
+
<Typography
|
| 294 |
+
variant="caption"
|
| 295 |
+
sx={{
|
| 296 |
+
fontSize: '0.75rem',
|
| 297 |
+
fontWeight: 700,
|
| 298 |
+
color: 'text.primary',
|
| 299 |
+
}}
|
| 300 |
+
>
|
| 301 |
+
{trace.traceMetadata.numberOfSteps}
|
| 302 |
+
</Typography>
|
| 303 |
+
<Typography
|
| 304 |
+
variant="caption"
|
| 305 |
+
sx={{
|
| 306 |
+
fontSize: '0.75rem',
|
| 307 |
+
fontWeight: 700,
|
| 308 |
+
color: 'text.disabled',
|
| 309 |
+
}}
|
| 310 |
+
>
|
| 311 |
+
/{trace.traceMetadata.maxSteps}
|
| 312 |
+
</Typography>
|
| 313 |
+
</Box>
|
| 314 |
+
)}
|
| 315 |
+
</Box>
|
| 316 |
+
<Box
|
| 317 |
+
ref={containerRef}
|
| 318 |
+
sx={{
|
| 319 |
+
flex: 1,
|
| 320 |
+
overflowY: 'auto',
|
| 321 |
+
minHeight: 0,
|
| 322 |
+
p: 2,
|
| 323 |
+
}}
|
| 324 |
+
>
|
| 325 |
+
{(trace?.steps && trace.steps.length > 0) || finalStep || showThinkingCard || showConnectionCard ? (
|
| 326 |
+
<Stack spacing={2.5}>
|
| 327 |
+
{/* Show connection step card (first item) */}
|
| 328 |
+
{showConnectionCard && (
|
| 329 |
+
<Box data-step-index="connection">
|
| 330 |
+
<ConnectionStepCard isConnecting={isConnectingToE2B} />
|
| 331 |
+
</Box>
|
| 332 |
+
)}
|
| 333 |
+
|
| 334 |
+
{/* Show all steps */}
|
| 335 |
+
{trace?.steps && trace.steps.map((step, index) => (
|
| 336 |
+
<Box key={step.stepId} data-step-index={index}>
|
| 337 |
+
<StepCard
|
| 338 |
+
step={step}
|
| 339 |
+
index={index}
|
| 340 |
+
isLatest={index === trace.steps!.length - 1}
|
| 341 |
+
isActive={index === activeStepIndex}
|
| 342 |
+
/>
|
| 343 |
+
</Box>
|
| 344 |
+
))}
|
| 345 |
+
|
| 346 |
+
{/* Show thinking indicator after steps (appears 5 seconds after stream start) */}
|
| 347 |
+
{showThinkingCard && (
|
| 348 |
+
<Box data-step-index="thinking">
|
| 349 |
+
<ThinkingStepCard />
|
| 350 |
+
</Box>
|
| 351 |
+
)}
|
| 352 |
+
|
| 353 |
+
{/* Show final step card if exists */}
|
| 354 |
+
{finalStep && (
|
| 355 |
+
<Box data-step-index="final">
|
| 356 |
+
<FinalStepCard
|
| 357 |
+
finalStep={finalStep}
|
| 358 |
+
isActive={isFinalStepActive}
|
| 359 |
+
/>
|
| 360 |
+
</Box>
|
| 361 |
+
)}
|
| 362 |
+
</Stack>
|
| 363 |
+
) : (
|
| 364 |
+
<Box
|
| 365 |
+
sx={{
|
| 366 |
+
display: 'flex',
|
| 367 |
+
flexDirection: 'column',
|
| 368 |
+
alignItems: 'center',
|
| 369 |
+
justifyContent: 'center',
|
| 370 |
+
height: '100%',
|
| 371 |
+
color: 'text.secondary',
|
| 372 |
+
p: 3,
|
| 373 |
+
textAlign: 'center',
|
| 374 |
+
}}
|
| 375 |
+
>
|
| 376 |
+
<ListAltIcon sx={{ fontSize: 48, mb: 2, opacity: 0.5 }} />
|
| 377 |
+
<Typography variant="body1" sx={{ fontWeight: 600, mb: 0.5 }}>
|
| 378 |
+
No steps yet
|
| 379 |
+
</Typography>
|
| 380 |
+
<Typography variant="caption" sx={{ fontSize: '0.75rem' }}>
|
| 381 |
+
Steps will appear as the agent progresses
|
| 382 |
+
</Typography>
|
| 383 |
+
</Box>
|
| 384 |
+
)}
|
| 385 |
+
</Box>
|
| 386 |
+
</Paper>
|
| 387 |
+
);
|
| 388 |
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { Card, CardContent, Box, Typography, CircularProgress } from '@mui/material';
|
| 3 |
+
import { keyframes } from '@mui/system';
|
| 4 |
+
|
| 5 |
+
// Animation de pulsation pour le border
|
| 6 |
+
const borderPulse = keyframes`
|
| 7 |
+
0%, 100% {
|
| 8 |
+
border-color: rgba(79, 134, 198, 0.4);
|
| 9 |
+
box-shadow: 0 2px 8px rgba(79, 134, 198, 0.15);
|
| 10 |
+
}
|
| 11 |
+
50% {
|
| 12 |
+
border-color: rgba(79, 134, 198, 0.8);
|
| 13 |
+
box-shadow: 0 2px 12px rgba(79, 134, 198, 0.3);
|
| 14 |
+
}
|
| 15 |
+
`;
|
| 16 |
+
|
| 17 |
+
// Animation de pulsation pour le fond
|
| 18 |
+
const backgroundPulse = keyframes`
|
| 19 |
+
0%, 100% {
|
| 20 |
+
background-color: rgba(79, 134, 198, 0.03);
|
| 21 |
+
}
|
| 22 |
+
50% {
|
| 23 |
+
background-color: rgba(79, 134, 198, 0.08);
|
| 24 |
+
}
|
| 25 |
+
`;
|
| 26 |
+
|
| 27 |
+
export const ThinkingStepCard: React.FC = () => {
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<Card
|
| 31 |
+
elevation={0}
|
| 32 |
+
sx={{
|
| 33 |
+
backgroundColor: 'background.paper',
|
| 34 |
+
border: '2px solid',
|
| 35 |
+
borderColor: 'primary.main',
|
| 36 |
+
borderRadius: 1.5,
|
| 37 |
+
animation: `${borderPulse} 2s ease-in-out infinite`,
|
| 38 |
+
position: 'relative',
|
| 39 |
+
overflow: 'hidden',
|
| 40 |
+
'&::before': {
|
| 41 |
+
content: '""',
|
| 42 |
+
position: 'absolute',
|
| 43 |
+
top: 0,
|
| 44 |
+
left: 0,
|
| 45 |
+
right: 0,
|
| 46 |
+
bottom: 0,
|
| 47 |
+
animation: `${backgroundPulse} 2s ease-in-out infinite`,
|
| 48 |
+
zIndex: 0,
|
| 49 |
+
},
|
| 50 |
+
}}
|
| 51 |
+
>
|
| 52 |
+
<CardContent sx={{ p: 1.5, '&:last-child': { pb: 1.5 }, position: 'relative', zIndex: 1 }}>
|
| 53 |
+
{/* Header avec spinner */}
|
| 54 |
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
| 55 |
+
<Box
|
| 56 |
+
sx={{
|
| 57 |
+
display: 'flex',
|
| 58 |
+
alignItems: 'center',
|
| 59 |
+
justifyContent: 'center',
|
| 60 |
+
}}
|
| 61 |
+
>
|
| 62 |
+
{/* Spinner circulaire */}
|
| 63 |
+
<CircularProgress
|
| 64 |
+
size={32}
|
| 65 |
+
thickness={3.5}
|
| 66 |
+
sx={{
|
| 67 |
+
color: 'primary.main',
|
| 68 |
+
}}
|
| 69 |
+
/>
|
| 70 |
+
</Box>
|
| 71 |
+
|
| 72 |
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
| 73 |
+
<Typography
|
| 74 |
+
sx={{
|
| 75 |
+
fontSize: '0.85rem',
|
| 76 |
+
fontWeight: 700,
|
| 77 |
+
color: 'primary.main',
|
| 78 |
+
lineHeight: 1.3,
|
| 79 |
+
}}
|
| 80 |
+
>
|
| 81 |
+
Agent
|
| 82 |
+
</Typography>
|
| 83 |
+
<Typography
|
| 84 |
+
sx={{
|
| 85 |
+
fontSize: '0.7rem',
|
| 86 |
+
color: 'text.secondary',
|
| 87 |
+
lineHeight: 1.2,
|
| 88 |
+
fontStyle: 'italic',
|
| 89 |
+
}}
|
| 90 |
+
>
|
| 91 |
+
Thinking...
|
| 92 |
+
</Typography>
|
| 93 |
+
</Box>
|
| 94 |
+
</Box>
|
| 95 |
+
</CardContent>
|
| 96 |
+
</Card>
|
| 97 |
+
);
|
| 98 |
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { StepsList } from './StepsList';
|
| 2 |
+
export { StepCard } from './StepCard';
|
| 3 |
+
export { ThinkingStepCard } from './ThinkingStepCard';
|
| 4 |
+
export { FinalStepCard } from './FinalStepCard';
|
| 5 |
+
export { ConnectionStepCard } from './ConnectionStepCard';
|
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useRef, useEffect } from 'react';
|
| 2 |
+
import { Box, Typography, CircularProgress, Button } from '@mui/material';
|
| 3 |
+
import CheckIcon from '@mui/icons-material/Check';
|
| 4 |
+
import CloseIcon from '@mui/icons-material/Close';
|
| 5 |
+
import CableIcon from '@mui/icons-material/Cable';
|
| 6 |
+
import { AgentTraceMetadata } from '@/types/agent';
|
| 7 |
+
import { useAgentStore, selectSelectedStepIndex, selectFinalStep, selectIsConnectingToE2B, selectIsAgentProcessing } from '@/stores/agentStore';
|
| 8 |
+
|
| 9 |
+
interface TimelineProps {
|
| 10 |
+
metadata: AgentTraceMetadata;
|
| 11 |
+
isRunning: boolean;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export const Timeline: React.FC<TimelineProps> = ({ metadata, isRunning }) => {
|
| 15 |
+
const timelineRef = useRef<HTMLDivElement>(null);
|
| 16 |
+
const selectedStepIndex = useAgentStore(selectSelectedStepIndex);
|
| 17 |
+
const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex);
|
| 18 |
+
const finalStep = useAgentStore(selectFinalStep);
|
| 19 |
+
const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B);
|
| 20 |
+
const isAgentProcessing = useAgentStore(selectIsAgentProcessing);
|
| 21 |
+
|
| 22 |
+
// Show connection indicator if connecting or if we have started processing
|
| 23 |
+
const showConnectionIndicator = isConnectingToE2B || isAgentProcessing || (metadata.numberOfSteps > 0) || finalStep;
|
| 24 |
+
|
| 25 |
+
// Generate array of steps with their status
|
| 26 |
+
// Show all steps up to maxSteps (200)
|
| 27 |
+
const totalStepsToShow = metadata.maxSteps;
|
| 28 |
+
|
| 29 |
+
const steps = Array.from({ length: totalStepsToShow }, (_, index) => ({
|
| 30 |
+
stepNumber: index + 1,
|
| 31 |
+
stepIndex: index,
|
| 32 |
+
isCompleted: index < metadata.numberOfSteps,
|
| 33 |
+
// Step is current if: we're at the right index AND running AND not connecting to E2B
|
| 34 |
+
isCurrent: (index === metadata.numberOfSteps && isRunning && !isConnectingToE2B) ||
|
| 35 |
+
(index === 0 && metadata.numberOfSteps === 0 && isRunning && !isConnectingToE2B),
|
| 36 |
+
isSelected: selectedStepIndex === index,
|
| 37 |
+
}));
|
| 38 |
+
|
| 39 |
+
// Handle step click
|
| 40 |
+
const handleStepClick = (stepIndex: number, isCompleted: boolean, isCurrent: boolean) => {
|
| 41 |
+
if (isCompleted) {
|
| 42 |
+
setSelectedStepIndex(stepIndex);
|
| 43 |
+
} else if (isCurrent) {
|
| 44 |
+
// Clicking on the current step (with animation) goes back to live mode
|
| 45 |
+
setSelectedStepIndex(null);
|
| 46 |
+
}
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
// Handle final step click (goes to live mode showing the final status)
|
| 50 |
+
const handleFinalStepClick = () => {
|
| 51 |
+
setSelectedStepIndex(null);
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
// Auto-scroll to current step while running
|
| 55 |
+
useEffect(() => {
|
| 56 |
+
if (timelineRef.current && isRunning) {
|
| 57 |
+
// Only auto-scroll while running, not when finished
|
| 58 |
+
const currentStepElement = timelineRef.current.querySelector(`[data-step="${metadata.numberOfSteps}"]`);
|
| 59 |
+
if (currentStepElement) {
|
| 60 |
+
currentStepElement.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}, [metadata.numberOfSteps, isRunning]);
|
| 64 |
+
|
| 65 |
+
return (
|
| 66 |
+
<Box
|
| 67 |
+
sx={{
|
| 68 |
+
p: 2,
|
| 69 |
+
border: '1px solid',
|
| 70 |
+
borderColor: 'divider',
|
| 71 |
+
borderRadius: '12px',
|
| 72 |
+
backgroundColor: 'background.paper',
|
| 73 |
+
flexShrink: 0,
|
| 74 |
+
}}
|
| 75 |
+
>
|
| 76 |
+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
| 77 |
+
{/* Header with step count */}
|
| 78 |
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
| 79 |
+
<Typography variant="h6" sx={{ fontSize: '0.9rem', fontWeight: 700, color: 'text.primary' }}>
|
| 80 |
+
Timeline
|
| 81 |
+
{selectedStepIndex !== null && (
|
| 82 |
+
<Typography component="span" sx={{ ml: 1, color: 'text.secondary', fontWeight: 500, fontSize: '0.65rem' }}>
|
| 83 |
+
- Viewing step {selectedStepIndex + 1}
|
| 84 |
+
</Typography>
|
| 85 |
+
)}
|
| 86 |
+
</Typography>
|
| 87 |
+
{selectedStepIndex !== null && (
|
| 88 |
+
<Button
|
| 89 |
+
size="small"
|
| 90 |
+
variant="outlined"
|
| 91 |
+
onClick={handleFinalStepClick}
|
| 92 |
+
sx={{
|
| 93 |
+
textTransform: 'none',
|
| 94 |
+
fontSize: '0.7rem',
|
| 95 |
+
fontWeight: 600,
|
| 96 |
+
px: 1.5,
|
| 97 |
+
py: 0.25,
|
| 98 |
+
minWidth: 'auto',
|
| 99 |
+
color: 'text.secondary',
|
| 100 |
+
borderColor: 'divider',
|
| 101 |
+
'&:hover': {
|
| 102 |
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)',
|
| 103 |
+
borderColor: 'text.secondary',
|
| 104 |
+
},
|
| 105 |
+
}}
|
| 106 |
+
>
|
| 107 |
+
Back to latest step
|
| 108 |
+
</Button>
|
| 109 |
+
)}
|
| 110 |
+
</Box>
|
| 111 |
+
|
| 112 |
+
{/* Horizontal scrollable step indicators */}
|
| 113 |
+
<Box
|
| 114 |
+
ref={timelineRef}
|
| 115 |
+
sx={{
|
| 116 |
+
display: 'flex',
|
| 117 |
+
alignItems: 'center',
|
| 118 |
+
overflowX: 'auto',
|
| 119 |
+
overflowY: 'hidden',
|
| 120 |
+
gap: 1.5,
|
| 121 |
+
py: 1.5,
|
| 122 |
+
height: 60,
|
| 123 |
+
position: 'relative',
|
| 124 |
+
// Hide scrollbar completely
|
| 125 |
+
scrollbarWidth: 'none', // Firefox
|
| 126 |
+
'&::-webkit-scrollbar': {
|
| 127 |
+
display: 'none', // Chrome, Safari, Edge
|
| 128 |
+
},
|
| 129 |
+
// Horizontal line crossing through circles
|
| 130 |
+
'&::before': {
|
| 131 |
+
content: '""',
|
| 132 |
+
position: 'absolute',
|
| 133 |
+
left: "15px",
|
| 134 |
+
// Calculer la largeur pour couvrir tous les steps (200 steps * (40px minWidth + 12px gap))
|
| 135 |
+
width: `calc(${metadata.maxSteps} * (40px + 12px))`,
|
| 136 |
+
top: '17.5px',
|
| 137 |
+
transform: 'translateY(-50%)',
|
| 138 |
+
height: '2px',
|
| 139 |
+
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
|
| 140 |
+
zIndex: 0,
|
| 141 |
+
pointerEvents: 'none',
|
| 142 |
+
},
|
| 143 |
+
}}
|
| 144 |
+
>
|
| 145 |
+
{/* Connection indicator (step 0) */}
|
| 146 |
+
{showConnectionIndicator && (
|
| 147 |
+
<Box
|
| 148 |
+
data-step="connection"
|
| 149 |
+
sx={{
|
| 150 |
+
display: 'flex',
|
| 151 |
+
flexDirection: 'column',
|
| 152 |
+
alignItems: 'center',
|
| 153 |
+
gap: 0.75,
|
| 154 |
+
minWidth: 50,
|
| 155 |
+
flexShrink: 0,
|
| 156 |
+
position: 'relative',
|
| 157 |
+
zIndex: 1,
|
| 158 |
+
}}
|
| 159 |
+
>
|
| 160 |
+
{/* Cercle blanc en arrière-plan pour cacher la ligne */}
|
| 161 |
+
<Box
|
| 162 |
+
sx={{
|
| 163 |
+
position: 'relative',
|
| 164 |
+
display: 'flex',
|
| 165 |
+
alignItems: 'center',
|
| 166 |
+
justifyContent: 'center',
|
| 167 |
+
}}
|
| 168 |
+
>
|
| 169 |
+
{/* Fond blanc/background pour cacher la ligne */}
|
| 170 |
+
<Box
|
| 171 |
+
sx={{
|
| 172 |
+
position: 'absolute',
|
| 173 |
+
width: 32,
|
| 174 |
+
height: 32,
|
| 175 |
+
borderRadius: '50%',
|
| 176 |
+
backgroundColor: 'background.paper',
|
| 177 |
+
zIndex: 0,
|
| 178 |
+
}}
|
| 179 |
+
/>
|
| 180 |
+
|
| 181 |
+
{/* Connection icon */}
|
| 182 |
+
{isConnectingToE2B ? (
|
| 183 |
+
<CircularProgress
|
| 184 |
+
size={20}
|
| 185 |
+
thickness={5}
|
| 186 |
+
sx={{
|
| 187 |
+
color: 'primary.main',
|
| 188 |
+
position: 'relative',
|
| 189 |
+
zIndex: 1,
|
| 190 |
+
}}
|
| 191 |
+
/>
|
| 192 |
+
) : (
|
| 193 |
+
<CableIcon
|
| 194 |
+
sx={{
|
| 195 |
+
fontSize: 20,
|
| 196 |
+
color: 'success.main',
|
| 197 |
+
position: 'relative',
|
| 198 |
+
zIndex: 1,
|
| 199 |
+
}}
|
| 200 |
+
/>
|
| 201 |
+
)}
|
| 202 |
+
</Box>
|
| 203 |
+
|
| 204 |
+
{/* Connection label */}
|
| 205 |
+
<Typography
|
| 206 |
+
variant="caption"
|
| 207 |
+
sx={{
|
| 208 |
+
fontSize: '0.7rem',
|
| 209 |
+
fontWeight: 700,
|
| 210 |
+
color: isConnectingToE2B ? 'primary.main' : 'success.main',
|
| 211 |
+
whiteSpace: 'nowrap',
|
| 212 |
+
}}
|
| 213 |
+
>
|
| 214 |
+
{isConnectingToE2B ? 'Connecting' : 'Connected'}
|
| 215 |
+
</Typography>
|
| 216 |
+
</Box>
|
| 217 |
+
)}
|
| 218 |
+
|
| 219 |
+
{/* Render steps and insert final step at the right position */}
|
| 220 |
+
{steps.map((step, index) => (
|
| 221 |
+
<React.Fragment key={step.stepNumber}>
|
| 222 |
+
<Box
|
| 223 |
+
data-step={step.stepNumber}
|
| 224 |
+
onClick={() => handleStepClick(step.stepIndex, step.isCompleted, step.isCurrent)}
|
| 225 |
+
sx={{
|
| 226 |
+
display: 'flex',
|
| 227 |
+
flexDirection: 'column',
|
| 228 |
+
alignItems: 'center',
|
| 229 |
+
gap: 0.75,
|
| 230 |
+
minWidth: 40,
|
| 231 |
+
flexShrink: 0,
|
| 232 |
+
position: 'relative',
|
| 233 |
+
zIndex: 1,
|
| 234 |
+
cursor: (step.isCompleted || step.isCurrent) ? 'pointer' : 'default',
|
| 235 |
+
'&:hover': (step.isCompleted || step.isCurrent) ? {
|
| 236 |
+
'& .step-dot': {
|
| 237 |
+
transform: 'scale(1.15)',
|
| 238 |
+
},
|
| 239 |
+
} : {},
|
| 240 |
+
}}
|
| 241 |
+
>
|
| 242 |
+
{/* Cercle blanc en arrière-plan pour cacher la ligne */}
|
| 243 |
+
<Box
|
| 244 |
+
sx={{
|
| 245 |
+
position: 'relative',
|
| 246 |
+
display: 'flex',
|
| 247 |
+
alignItems: 'center',
|
| 248 |
+
justifyContent: 'center',
|
| 249 |
+
}}
|
| 250 |
+
>
|
| 251 |
+
{/* Fond blanc/background pour cacher la ligne */}
|
| 252 |
+
<Box
|
| 253 |
+
sx={{
|
| 254 |
+
position: 'absolute',
|
| 255 |
+
width: step.isCurrent || step.isSelected ? 28 : step.isCompleted ? 22 : 20,
|
| 256 |
+
height: step.isCurrent || step.isSelected ? 28 : step.isCompleted ? 22 : 20,
|
| 257 |
+
borderRadius: '50%',
|
| 258 |
+
backgroundColor: 'background.paper',
|
| 259 |
+
zIndex: 0,
|
| 260 |
+
}}
|
| 261 |
+
/>
|
| 262 |
+
|
| 263 |
+
{/* Step dot */}
|
| 264 |
+
{step.isCurrent ? (
|
| 265 |
+
<CircularProgress
|
| 266 |
+
size={20}
|
| 267 |
+
thickness={5}
|
| 268 |
+
sx={{
|
| 269 |
+
color: 'primary.main',
|
| 270 |
+
position: 'relative',
|
| 271 |
+
zIndex: 1,
|
| 272 |
+
}}
|
| 273 |
+
/>
|
| 274 |
+
) : (
|
| 275 |
+
<Box
|
| 276 |
+
className="step-dot"
|
| 277 |
+
sx={{
|
| 278 |
+
width: step.isSelected ? 20 : step.isCompleted ? 14 : 12,
|
| 279 |
+
height: step.isSelected ? 20 : step.isCompleted ? 14 : 12,
|
| 280 |
+
borderRadius: '50%',
|
| 281 |
+
// Always keep steps in primary color (blue)
|
| 282 |
+
backgroundColor: step.isCompleted
|
| 283 |
+
? 'primary.main' // Blue for completed steps
|
| 284 |
+
: (theme) => theme.palette.mode === 'dark' ? 'grey.800' : 'grey.300', // Light grey for future steps
|
| 285 |
+
display: 'flex',
|
| 286 |
+
alignItems: 'center',
|
| 287 |
+
justifyContent: 'center',
|
| 288 |
+
transition: 'all 0.2s ease',
|
| 289 |
+
boxShadow: step.isCompleted || step.isSelected
|
| 290 |
+
? step.isSelected
|
| 291 |
+
? '0 0 8px rgba(255, 167, 38, 0.5)'
|
| 292 |
+
: '0 2px 4px rgba(0,0,0,0.1)'
|
| 293 |
+
: 'none',
|
| 294 |
+
position: 'relative',
|
| 295 |
+
zIndex: 1,
|
| 296 |
+
}}
|
| 297 |
+
/>
|
| 298 |
+
)}
|
| 299 |
+
</Box>
|
| 300 |
+
|
| 301 |
+
{/* Step number - show for all steps */}
|
| 302 |
+
<Typography
|
| 303 |
+
variant="caption"
|
| 304 |
+
sx={{
|
| 305 |
+
fontSize: '0.7rem',
|
| 306 |
+
fontWeight: step.isCompleted || step.isCurrent || step.isSelected ? 700 : 400,
|
| 307 |
+
color: step.isCurrent
|
| 308 |
+
? 'primary.main'
|
| 309 |
+
: (step.isCompleted || step.isSelected
|
| 310 |
+
? 'text.primary'
|
| 311 |
+
: (theme) => theme.palette.mode === 'dark' ? 'grey.700' : 'grey.400'),
|
| 312 |
+
whiteSpace: 'nowrap',
|
| 313 |
+
}}
|
| 314 |
+
>
|
| 315 |
+
{step.stepNumber}
|
| 316 |
+
</Typography>
|
| 317 |
+
</Box>
|
| 318 |
+
|
| 319 |
+
{/* Insert final step indicator right after the last completed step */}
|
| 320 |
+
{finalStep && step.stepNumber === metadata.numberOfSteps && (
|
| 321 |
+
<Box
|
| 322 |
+
data-step="final"
|
| 323 |
+
onClick={handleFinalStepClick}
|
| 324 |
+
sx={{
|
| 325 |
+
display: 'flex',
|
| 326 |
+
flexDirection: 'column',
|
| 327 |
+
alignItems: 'center',
|
| 328 |
+
gap: 0.75,
|
| 329 |
+
minWidth: 50,
|
| 330 |
+
flexShrink: 0,
|
| 331 |
+
position: 'relative',
|
| 332 |
+
zIndex: 1,
|
| 333 |
+
cursor: 'pointer',
|
| 334 |
+
'&:hover': {
|
| 335 |
+
'& .final-step-icon': {
|
| 336 |
+
transform: 'scale(1.15)',
|
| 337 |
+
},
|
| 338 |
+
},
|
| 339 |
+
}}
|
| 340 |
+
>
|
| 341 |
+
{/* Cercle blanc en arrière-plan pour cacher la ligne */}
|
| 342 |
+
<Box
|
| 343 |
+
sx={{
|
| 344 |
+
position: 'relative',
|
| 345 |
+
display: 'flex',
|
| 346 |
+
alignItems: 'center',
|
| 347 |
+
justifyContent: 'center',
|
| 348 |
+
}}
|
| 349 |
+
>
|
| 350 |
+
{/* Fond blanc/background pour cacher la ligne */}
|
| 351 |
+
<Box
|
| 352 |
+
sx={{
|
| 353 |
+
position: 'absolute',
|
| 354 |
+
width: selectedStepIndex === null ? 32 : 28,
|
| 355 |
+
height: selectedStepIndex === null ? 32 : 28,
|
| 356 |
+
borderRadius: '50%',
|
| 357 |
+
backgroundColor: 'background.paper',
|
| 358 |
+
zIndex: 0,
|
| 359 |
+
}}
|
| 360 |
+
/>
|
| 361 |
+
|
| 362 |
+
{/* Final step icon */}
|
| 363 |
+
<Box
|
| 364 |
+
className="final-step-icon"
|
| 365 |
+
sx={{
|
| 366 |
+
width: selectedStepIndex === null ? 24 : 20,
|
| 367 |
+
height: selectedStepIndex === null ? 24 : 20,
|
| 368 |
+
borderRadius: '50%',
|
| 369 |
+
backgroundColor: finalStep.type === 'success' ? 'success.main' : 'error.main',
|
| 370 |
+
display: 'flex',
|
| 371 |
+
alignItems: 'center',
|
| 372 |
+
justifyContent: 'center',
|
| 373 |
+
transition: 'all 0.2s ease',
|
| 374 |
+
boxShadow: selectedStepIndex === null
|
| 375 |
+
? finalStep.type === 'success'
|
| 376 |
+
? '0 2px 8px rgba(102, 187, 106, 0.4)'
|
| 377 |
+
: '0 2px 8px rgba(244, 67, 54, 0.4)'
|
| 378 |
+
: '0 2px 4px rgba(0,0,0,0.1)',
|
| 379 |
+
position: 'relative',
|
| 380 |
+
zIndex: 1,
|
| 381 |
+
}}
|
| 382 |
+
>
|
| 383 |
+
{finalStep.type === 'success' ? (
|
| 384 |
+
<CheckIcon sx={{ fontSize: 14, color: 'white' }} />
|
| 385 |
+
) : (
|
| 386 |
+
<CloseIcon sx={{ fontSize: 14, color: 'white' }} />
|
| 387 |
+
)}
|
| 388 |
+
</Box>
|
| 389 |
+
</Box>
|
| 390 |
+
|
| 391 |
+
{/* Final step label */}
|
| 392 |
+
<Typography
|
| 393 |
+
variant="caption"
|
| 394 |
+
sx={{
|
| 395 |
+
fontSize: '0.7rem',
|
| 396 |
+
fontWeight: selectedStepIndex === null ? 700 : 500,
|
| 397 |
+
color: finalStep.type === 'success'
|
| 398 |
+
? (selectedStepIndex === null ? 'text.primary' : 'text.secondary')
|
| 399 |
+
: 'error.main',
|
| 400 |
+
whiteSpace: 'nowrap',
|
| 401 |
+
}}
|
| 402 |
+
>
|
| 403 |
+
{finalStep.type === 'success' ? 'End' : 'Failed'}
|
| 404 |
+
</Typography>
|
| 405 |
+
</Box>
|
| 406 |
+
)}
|
| 407 |
+
</React.Fragment>
|
| 408 |
+
))}
|
| 409 |
+
</Box>
|
| 410 |
+
</Box>
|
| 411 |
+
</Box>
|
| 412 |
+
);
|
| 413 |
+
};
|
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export { Timeline } from './Timeline';
|
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Application configuration
|
| 2 |
+
export const config = {
|
| 3 |
+
// WebSocket URL for backend connection
|
| 4 |
+
wsUrl: 'ws://localhost:8000/ws',
|
| 5 |
+
|
| 6 |
+
// API Base URL
|
| 7 |
+
apiBaseUrl: 'http://localhost:8000/api/v1',
|
| 8 |
+
|
| 9 |
+
// Default model (will be overridden by first available model from backend)
|
| 10 |
+
defaultModelId: 'Qwen/Qwen3-VL-8B-Instruct',
|
| 11 |
+
} as const;
|
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { useAgentWebSocket } from './useAgentWebSocket';
|
| 2 |
+
export { useWebSocket } from './useWebSocket';
|
| 3 |
+
export { useSendTask } from './useSendTask';
|
| 4 |
+
export { useGifGenerator } from './useGifGenerator';
|
| 5 |
+
export { useJsonExporter } from './useJsonExporter';
|
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useEffect } from 'react';
|
| 2 |
+
import { useWebSocket } from './useWebSocket';
|
| 3 |
+
import { useAgentStore } from '@/stores/agentStore';
|
| 4 |
+
import { WebSocketEvent, AgentTrace, AgentStep } from '@/types/agent';
|
| 5 |
+
import { ulid } from 'ulid';
|
| 6 |
+
|
| 7 |
+
interface UseAgentWebSocketOptions {
|
| 8 |
+
url: string;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export const useAgentWebSocket = ({ url }: UseAgentWebSocketOptions) => {
|
| 12 |
+
const {
|
| 13 |
+
setTrace,
|
| 14 |
+
updateTraceWithStep,
|
| 15 |
+
completeTrace,
|
| 16 |
+
setIsAgentProcessing,
|
| 17 |
+
setIsConnectingToE2B,
|
| 18 |
+
setVncUrl,
|
| 19 |
+
setError,
|
| 20 |
+
setIsConnected,
|
| 21 |
+
selectedModelId,
|
| 22 |
+
resetAgent,
|
| 23 |
+
} = useAgentStore();
|
| 24 |
+
|
| 25 |
+
// Handle incoming WebSocket messages
|
| 26 |
+
const handleWebSocketMessage = useCallback(
|
| 27 |
+
(event: WebSocketEvent) => {
|
| 28 |
+
console.log('WebSocket event received:', event);
|
| 29 |
+
|
| 30 |
+
switch (event.type) {
|
| 31 |
+
case 'agent_start': {
|
| 32 |
+
// Clear previous state (especially finalStep)
|
| 33 |
+
resetAgent();
|
| 34 |
+
|
| 35 |
+
setIsAgentProcessing(true);
|
| 36 |
+
setIsConnectingToE2B(true); // Start connecting to E2B
|
| 37 |
+
setError(undefined); // Clear any previous error
|
| 38 |
+
|
| 39 |
+
// Ensure trace has proper metadata with default maxSteps if not provided
|
| 40 |
+
const traceWithMetadata = {
|
| 41 |
+
...event.agentTrace,
|
| 42 |
+
traceMetadata: event.agentTrace.traceMetadata ? {
|
| 43 |
+
...event.agentTrace.traceMetadata,
|
| 44 |
+
maxSteps: event.agentTrace.traceMetadata.maxSteps > 0
|
| 45 |
+
? event.agentTrace.traceMetadata.maxSteps
|
| 46 |
+
: 200, // Default if backend sends 0
|
| 47 |
+
} : {
|
| 48 |
+
traceId: event.agentTrace.id,
|
| 49 |
+
inputTokensUsed: 0,
|
| 50 |
+
outputTokensUsed: 0,
|
| 51 |
+
duration: 0,
|
| 52 |
+
numberOfSteps: 0,
|
| 53 |
+
maxSteps: 200,
|
| 54 |
+
completed: false,
|
| 55 |
+
},
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
setTrace(traceWithMetadata);
|
| 59 |
+
console.log('Agent start received:', traceWithMetadata);
|
| 60 |
+
break;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
case 'agent_progress':
|
| 64 |
+
// Add new step from agent trace run with image, generated text, actions, tokens and timestamp
|
| 65 |
+
setIsConnectingToE2B(false); // Connected! First step received
|
| 66 |
+
updateTraceWithStep(event.agentStep, event.traceMetadata);
|
| 67 |
+
console.log('Agent progress received:', event.agentStep);
|
| 68 |
+
break;
|
| 69 |
+
|
| 70 |
+
case 'agent_complete':
|
| 71 |
+
setIsAgentProcessing(false);
|
| 72 |
+
setIsConnectingToE2B(false);
|
| 73 |
+
completeTrace(event.traceMetadata);
|
| 74 |
+
console.log('Agent complete received:', event.traceMetadata);
|
| 75 |
+
break;
|
| 76 |
+
|
| 77 |
+
case 'agent_error':
|
| 78 |
+
setIsAgentProcessing(false);
|
| 79 |
+
setIsConnectingToE2B(false);
|
| 80 |
+
setError(event.error);
|
| 81 |
+
console.error('Agent error received:', event.error);
|
| 82 |
+
break;
|
| 83 |
+
|
| 84 |
+
case 'vnc_url_set':
|
| 85 |
+
setIsConnectingToE2B(false); // Connected! VNC URL received
|
| 86 |
+
setVncUrl(event.vncUrl);
|
| 87 |
+
console.log('VNC URL set received:', event.vncUrl);
|
| 88 |
+
break;
|
| 89 |
+
|
| 90 |
+
case 'vnc_url_unset':
|
| 91 |
+
setVncUrl('');
|
| 92 |
+
console.log('VNC URL unset received');
|
| 93 |
+
break;
|
| 94 |
+
|
| 95 |
+
case 'heartbeat':
|
| 96 |
+
console.log('Heartbeat received:', event);
|
| 97 |
+
break;
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
[setTrace, updateTraceWithStep, completeTrace, setIsAgentProcessing, setIsConnectingToE2B, setVncUrl, setError, resetAgent]
|
| 101 |
+
);
|
| 102 |
+
|
| 103 |
+
// Handle WebSocket errors
|
| 104 |
+
const handleWebSocketError = useCallback(() => {
|
| 105 |
+
// WebSocket Frontend Error handling
|
| 106 |
+
console.error('WebSocket connection error');
|
| 107 |
+
}, []);
|
| 108 |
+
|
| 109 |
+
// Initialize WebSocket connection
|
| 110 |
+
const { isConnected, connectionState, sendMessage, manualReconnect } = useWebSocket({
|
| 111 |
+
url,
|
| 112 |
+
onMessage: handleWebSocketMessage,
|
| 113 |
+
onError: handleWebSocketError,
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
// Sync connection state to store
|
| 117 |
+
useEffect(() => {
|
| 118 |
+
setIsConnected(isConnected);
|
| 119 |
+
}, [isConnected, setIsConnected]);
|
| 120 |
+
|
| 121 |
+
// Create a global sendNewTask function that can be called from anywhere
|
| 122 |
+
useEffect(() => {
|
| 123 |
+
// Store sendNewTask in window for global access
|
| 124 |
+
(window as Window & { __sendNewTask?: (instruction: string, modelId: string) => void }).__sendNewTask = (instruction: string, modelId: string) => {
|
| 125 |
+
// Reset agent state before starting a new task
|
| 126 |
+
resetAgent();
|
| 127 |
+
|
| 128 |
+
const traceId = ulid();
|
| 129 |
+
const trace: AgentTrace = {
|
| 130 |
+
id: traceId,
|
| 131 |
+
instruction,
|
| 132 |
+
modelId: modelId,
|
| 133 |
+
timestamp: new Date(),
|
| 134 |
+
isRunning: true,
|
| 135 |
+
traceMetadata: {
|
| 136 |
+
traceId: traceId,
|
| 137 |
+
inputTokensUsed: 0,
|
| 138 |
+
outputTokensUsed: 0,
|
| 139 |
+
duration: 0,
|
| 140 |
+
numberOfSteps: 0,
|
| 141 |
+
maxSteps: 200, // Default max steps, will be updated by backend
|
| 142 |
+
completed: false,
|
| 143 |
+
},
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
setTrace(trace);
|
| 147 |
+
setIsAgentProcessing(true);
|
| 148 |
+
setIsConnectingToE2B(true); // Start connecting when task is sent
|
| 149 |
+
|
| 150 |
+
// Send message to Python backend via WebSocket
|
| 151 |
+
sendMessage({
|
| 152 |
+
type: 'user_task',
|
| 153 |
+
trace: trace,
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
+
console.log('Task sent:', trace);
|
| 157 |
+
};
|
| 158 |
+
}, [setTrace, setIsAgentProcessing, setIsConnectingToE2B, sendMessage, resetAgent]);
|
| 159 |
+
|
| 160 |
+
return {
|
| 161 |
+
isConnected,
|
| 162 |
+
connectionState,
|
| 163 |
+
manualReconnect,
|
| 164 |
+
};
|
| 165 |
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useCallback } from 'react';
|
| 2 |
+
import { generateGif, downloadGif, GifGenerationOptions } from '@/services/gifGenerator';
|
| 3 |
+
import { AgentStep } from '@/types/agent';
|
| 4 |
+
|
| 5 |
+
interface UseGifGeneratorOptions {
|
| 6 |
+
steps: AgentStep[];
|
| 7 |
+
traceId?: string;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
interface UseGifGeneratorReturn {
|
| 11 |
+
isGenerating: boolean;
|
| 12 |
+
error: string | null;
|
| 13 |
+
generateAndDownloadGif: () => Promise<void>;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* Custom hook to generate and download a GIF from trace steps
|
| 18 |
+
*/
|
| 19 |
+
export const useGifGenerator = ({
|
| 20 |
+
steps,
|
| 21 |
+
traceId,
|
| 22 |
+
}: UseGifGeneratorOptions): UseGifGeneratorReturn => {
|
| 23 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 24 |
+
const [error, setError] = useState<string | null>(null);
|
| 25 |
+
|
| 26 |
+
const generateAndDownloadGif = useCallback(async () => {
|
| 27 |
+
if (!steps || steps.length === 0) {
|
| 28 |
+
setError('No steps available to generate GIF');
|
| 29 |
+
return;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
setIsGenerating(true);
|
| 33 |
+
setError(null);
|
| 34 |
+
|
| 35 |
+
try {
|
| 36 |
+
// Extract images from steps
|
| 37 |
+
const images = steps
|
| 38 |
+
.map((step) => step.image)
|
| 39 |
+
.filter((image): image is string => !!image);
|
| 40 |
+
|
| 41 |
+
if (images.length === 0) {
|
| 42 |
+
setError('No images available in steps');
|
| 43 |
+
setIsGenerating(false);
|
| 44 |
+
return;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Generate GIF with maximum dimensions of 400x200
|
| 48 |
+
const options: GifGenerationOptions = {
|
| 49 |
+
images,
|
| 50 |
+
interval: 1.5, // 1.5 seconds per frame
|
| 51 |
+
gifWidth: 400,
|
| 52 |
+
gifHeight: 200,
|
| 53 |
+
quality: 10, // Medium quality for good size/quality compromise
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
const result = await generateGif(options);
|
| 57 |
+
|
| 58 |
+
if (!result.success || !result.image) {
|
| 59 |
+
setError(result.error || 'Error generating GIF');
|
| 60 |
+
setIsGenerating(false);
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Download the GIF
|
| 65 |
+
const filename = traceId
|
| 66 |
+
? `trace-${traceId}-replay.gif`
|
| 67 |
+
: `trace-replay-${Date.now()}.gif`;
|
| 68 |
+
|
| 69 |
+
downloadGif(result.image, filename);
|
| 70 |
+
setIsGenerating(false);
|
| 71 |
+
} catch (err) {
|
| 72 |
+
setError(
|
| 73 |
+
err instanceof Error
|
| 74 |
+
? err.message
|
| 75 |
+
: 'Unexpected error generating GIF'
|
| 76 |
+
);
|
| 77 |
+
setIsGenerating(false);
|
| 78 |
+
}
|
| 79 |
+
}, [steps, traceId]);
|
| 80 |
+
|
| 81 |
+
return {
|
| 82 |
+
isGenerating,
|
| 83 |
+
error,
|
| 84 |
+
generateAndDownloadGif,
|
| 85 |
+
};
|
| 86 |
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback } from 'react';
|
| 2 |
+
import { exportTraceToJson, downloadJson } from '@/services/jsonExporter';
|
| 3 |
+
import { AgentTrace, AgentStep, AgentTraceMetadata } from '@/types/agent';
|
| 4 |
+
|
| 5 |
+
interface UseJsonExporterOptions {
|
| 6 |
+
trace?: AgentTrace;
|
| 7 |
+
steps: AgentStep[];
|
| 8 |
+
metadata?: AgentTraceMetadata;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
interface UseJsonExporterReturn {
|
| 12 |
+
downloadTraceAsJson: () => void;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Hook personnalisé pour exporter et télécharger une trace en JSON
|
| 17 |
+
*/
|
| 18 |
+
export const useJsonExporter = ({
|
| 19 |
+
trace,
|
| 20 |
+
steps,
|
| 21 |
+
metadata,
|
| 22 |
+
}: UseJsonExporterOptions): UseJsonExporterReturn => {
|
| 23 |
+
const downloadTraceAsJson = useCallback(() => {
|
| 24 |
+
if (!trace) {
|
| 25 |
+
console.error('No trace available to export');
|
| 26 |
+
return;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
try {
|
| 30 |
+
const jsonString = exportTraceToJson(trace, steps, metadata);
|
| 31 |
+
const filename = `trace-${trace.id}.json`;
|
| 32 |
+
downloadJson(jsonString, filename);
|
| 33 |
+
} catch (error) {
|
| 34 |
+
console.error('Error exporting trace to JSON:', error);
|
| 35 |
+
}
|
| 36 |
+
}, [trace, steps, metadata]);
|
| 37 |
+
|
| 38 |
+
return {
|
| 39 |
+
downloadTraceAsJson,
|
| 40 |
+
};
|
| 41 |
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback } from 'react';
|
| 2 |
+
|
| 3 |
+
export const useSendTask = () => {
|
| 4 |
+
const sendTask = useCallback((instruction: string, modelId: string) => {
|
| 5 |
+
const sendNewTask = (window as Window & { __sendNewTask?: (instruction: string, modelId: string) => void }).__sendNewTask;
|
| 6 |
+
if (sendNewTask) {
|
| 7 |
+
sendNewTask(instruction, modelId);
|
| 8 |
+
} else {
|
| 9 |
+
console.error('WebSocket not initialized');
|
| 10 |
+
}
|
| 11 |
+
}, []);
|
| 12 |
+
|
| 13 |
+
return sendTask;
|
| 14 |
+
};
|
|
@@ -1,8 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
margin: 0;
|
| 3 |
-
padding: 0;
|
| 4 |
-
box-sizing: border-box;
|
| 5 |
-
}
|
| 6 |
|
| 7 |
html, body {
|
| 8 |
margin: 0;
|
|
@@ -10,11 +6,6 @@ html, body {
|
|
| 10 |
height: 100%;
|
| 11 |
width: 100%;
|
| 12 |
overflow: hidden;
|
| 13 |
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
| 14 |
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
| 15 |
-
sans-serif;
|
| 16 |
-
-webkit-font-smoothing: antialiased;
|
| 17 |
-
-moz-osx-font-smoothing: grayscale;
|
| 18 |
}
|
| 19 |
|
| 20 |
#root {
|
|
|
|
| 1 |
+
/* Global styles - Material UI handles most of the styling */
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
html, body {
|
| 4 |
margin: 0;
|
|
|
|
| 6 |
height: 100%;
|
| 7 |
width: 100%;
|
| 8 |
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
#root {
|
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect } from 'react';
|
| 2 |
+
import { useNavigate } from 'react-router-dom';
|
| 3 |
+
import { useAgentStore, selectTrace, selectIsAgentProcessing, selectVncUrl, selectMetadata, selectSelectedStep } from '@/stores/agentStore';
|
| 4 |
+
import { Header, SandboxViewer, StepsList, Timeline } from '@/components';
|
| 5 |
+
import { Box } from '@mui/material';
|
| 6 |
+
|
| 7 |
+
const Task = () => {
|
| 8 |
+
const navigate = useNavigate();
|
| 9 |
+
|
| 10 |
+
// Get state from Zustand store
|
| 11 |
+
const trace = useAgentStore(selectTrace);
|
| 12 |
+
const isAgentProcessing = useAgentStore(selectIsAgentProcessing);
|
| 13 |
+
const vncUrl = useAgentStore(selectVncUrl);
|
| 14 |
+
const metadata = useAgentStore(selectMetadata);
|
| 15 |
+
const selectedStep = useAgentStore(selectSelectedStep);
|
| 16 |
+
const error = useAgentStore((state) => state.error);
|
| 17 |
+
|
| 18 |
+
// Redirect to home if no trace is present
|
| 19 |
+
useEffect(() => {
|
| 20 |
+
if (!trace) {
|
| 21 |
+
console.log('No trace found, redirecting to home...');
|
| 22 |
+
navigate('/', { replace: true });
|
| 23 |
+
}
|
| 24 |
+
}, [trace, navigate]);
|
| 25 |
+
|
| 26 |
+
// Handler for going back to home
|
| 27 |
+
const handleBackToHome = () => {
|
| 28 |
+
useAgentStore.getState().resetAgent();
|
| 29 |
+
navigate('/');
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
// Determine if we should show success/fail status (same logic as SandboxViewer)
|
| 33 |
+
const showStatus = !trace?.isRunning && !selectedStep && metadata && metadata.numberOfSteps > 0;
|
| 34 |
+
|
| 35 |
+
// Don't render anything if no trace (will redirect)
|
| 36 |
+
if (!trace) {
|
| 37 |
+
return null;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
return (
|
| 41 |
+
<Box
|
| 42 |
+
sx={{
|
| 43 |
+
height: '100vh',
|
| 44 |
+
width: '100%',
|
| 45 |
+
display: 'flex',
|
| 46 |
+
flexDirection: 'column',
|
| 47 |
+
backgroundColor: 'background.default',
|
| 48 |
+
}}
|
| 49 |
+
>
|
| 50 |
+
{/* Header */}
|
| 51 |
+
<Header
|
| 52 |
+
isAgentProcessing={isAgentProcessing}
|
| 53 |
+
onBackToHome={handleBackToHome}
|
| 54 |
+
/>
|
| 55 |
+
|
| 56 |
+
{/* Main Content */}
|
| 57 |
+
<Box
|
| 58 |
+
sx={{
|
| 59 |
+
flex: 1,
|
| 60 |
+
display: 'flex',
|
| 61 |
+
justifyContent: 'center',
|
| 62 |
+
alignItems: 'stretch',
|
| 63 |
+
minHeight: 0,
|
| 64 |
+
p: 0,
|
| 65 |
+
overflowY: 'auto',
|
| 66 |
+
overflowX: 'hidden',
|
| 67 |
+
}}
|
| 68 |
+
>
|
| 69 |
+
<Box
|
| 70 |
+
sx={{
|
| 71 |
+
width: '100%',
|
| 72 |
+
display: 'flex',
|
| 73 |
+
flexDirection: { xs: 'column', md: 'row' },
|
| 74 |
+
p: { xs: 2, md: 4 },
|
| 75 |
+
pb: { xs: 2, md: 3 },
|
| 76 |
+
}}
|
| 77 |
+
>
|
| 78 |
+
{/* Left Side: OS Stream + Metadata */}
|
| 79 |
+
<Box
|
| 80 |
+
sx={{
|
| 81 |
+
flex: 1,
|
| 82 |
+
display: 'flex',
|
| 83 |
+
flexDirection: 'column',
|
| 84 |
+
minWidth: 0,
|
| 85 |
+
pr: { xs: 0, md: 1.5 },
|
| 86 |
+
gap: { xs: 2, md: 3 },
|
| 87 |
+
overflow: 'visible',
|
| 88 |
+
}}
|
| 89 |
+
>
|
| 90 |
+
{/* Sandbox Viewer */}
|
| 91 |
+
<SandboxViewer
|
| 92 |
+
vncUrl={vncUrl}
|
| 93 |
+
isAgentProcessing={isAgentProcessing}
|
| 94 |
+
metadata={metadata}
|
| 95 |
+
traceStartTime={trace?.timestamp}
|
| 96 |
+
selectedStep={selectedStep}
|
| 97 |
+
isRunning={trace?.isRunning || false}
|
| 98 |
+
/>
|
| 99 |
+
|
| 100 |
+
{/* Timeline - Always show, even with default values */}
|
| 101 |
+
<Timeline
|
| 102 |
+
metadata={metadata && metadata.maxSteps > 0 ? metadata : {
|
| 103 |
+
traceId: trace?.id || '',
|
| 104 |
+
inputTokensUsed: metadata?.inputTokensUsed || 0,
|
| 105 |
+
outputTokensUsed: metadata?.outputTokensUsed || 0,
|
| 106 |
+
duration: metadata?.duration || 0,
|
| 107 |
+
numberOfSteps: metadata?.numberOfSteps || 0,
|
| 108 |
+
maxSteps: 200, // Default max steps (will be updated by backend)
|
| 109 |
+
completed: metadata?.completed || false,
|
| 110 |
+
}}
|
| 111 |
+
isRunning={trace?.isRunning || false}
|
| 112 |
+
/>
|
| 113 |
+
</Box>
|
| 114 |
+
|
| 115 |
+
{/* Right Side: Steps List */}
|
| 116 |
+
<StepsList trace={trace} />
|
| 117 |
+
</Box>
|
| 118 |
+
</Box>
|
| 119 |
+
</Box>
|
| 120 |
+
);
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
export default Task;
|
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import { useNavigate } from 'react-router-dom';
|
| 3 |
+
import { useSendTask } from '@/hooks/useSendTask';
|
| 4 |
+
import { Box } from '@mui/material';
|
| 5 |
+
import { WelcomeScreen } from '@/components';
|
| 6 |
+
import { useAgentStore, selectIsConnected } from '@/stores/agentStore';
|
| 7 |
+
|
| 8 |
+
const Welcome = () => {
|
| 9 |
+
const navigate = useNavigate();
|
| 10 |
+
const isConnected = useAgentStore(selectIsConnected);
|
| 11 |
+
const sendTask = useSendTask();
|
| 12 |
+
|
| 13 |
+
const handleSendNewTask = (instruction: string, modelId: string) => {
|
| 14 |
+
sendTask(instruction, modelId);
|
| 15 |
+
// Navigate to task page after starting task
|
| 16 |
+
navigate('/task');
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
return (
|
| 20 |
+
<Box
|
| 21 |
+
sx={{
|
| 22 |
+
height: '100vh',
|
| 23 |
+
width: '100%',
|
| 24 |
+
display: 'flex',
|
| 25 |
+
flexDirection: 'column',
|
| 26 |
+
backgroundColor: 'background.default',
|
| 27 |
+
position: 'relative',
|
| 28 |
+
}}
|
| 29 |
+
>
|
| 30 |
+
<WelcomeScreen onStartTask={handleSendNewTask} isConnected={isConnected} />
|
| 31 |
+
</Box>
|
| 32 |
+
);
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
export default Welcome;
|
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { config } from '@/config';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Fetch available models from the backend
|
| 5 |
+
*/
|
| 6 |
+
export async function fetchAvailableModels(): Promise<string[]> {
|
| 7 |
+
const response = await fetch(`${config.apiBaseUrl}/models`);
|
| 8 |
+
if (!response.ok) {
|
| 9 |
+
throw new Error('Failed to fetch models');
|
| 10 |
+
}
|
| 11 |
+
const data = await response.json();
|
| 12 |
+
return data.models;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Generate a random instruction from the backend
|
| 17 |
+
*/
|
| 18 |
+
export async function generateRandomQuestion(modelId: string): Promise<string> {
|
| 19 |
+
const response = await fetch(`${config.apiBaseUrl}/generate-instruction`, {
|
| 20 |
+
method: 'POST',
|
| 21 |
+
headers: {
|
| 22 |
+
'Content-Type': 'application/json',
|
| 23 |
+
},
|
| 24 |
+
body: JSON.stringify({
|
| 25 |
+
model_id: modelId,
|
| 26 |
+
}),
|
| 27 |
+
});
|
| 28 |
+
if (!response.ok) {
|
| 29 |
+
throw new Error('Failed to generate instruction');
|
| 30 |
+
}
|
| 31 |
+
const data = await response.json();
|
| 32 |
+
return data.instruction;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* Update step evaluation (vote)
|
| 37 |
+
*/
|
| 38 |
+
export async function updateStepEvaluation(
|
| 39 |
+
traceId: string,
|
| 40 |
+
stepId: string,
|
| 41 |
+
evaluation: 'like' | 'dislike' | 'neutral'
|
| 42 |
+
): Promise<void> {
|
| 43 |
+
const response = await fetch(`${config.apiBaseUrl}/traces/${traceId}/steps/${stepId}`, {
|
| 44 |
+
method: 'PATCH',
|
| 45 |
+
headers: {
|
| 46 |
+
'Content-Type': 'application/json',
|
| 47 |
+
},
|
| 48 |
+
body: JSON.stringify({
|
| 49 |
+
step_evaluation: evaluation,
|
| 50 |
+
}),
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
if (!response.ok) {
|
| 54 |
+
throw new Error('Failed to update step evaluation');
|
| 55 |
+
}
|
| 56 |
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gifshot from 'gifshot';
|
| 2 |
+
|
| 3 |
+
export interface GifGenerationOptions {
|
| 4 |
+
images: string[];
|
| 5 |
+
interval?: number; // Durée de chaque frame en secondes
|
| 6 |
+
gifWidth?: number;
|
| 7 |
+
gifHeight?: number;
|
| 8 |
+
quality?: number;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export interface GifGenerationResult {
|
| 12 |
+
success: boolean;
|
| 13 |
+
image?: string; // Data URL du GIF
|
| 14 |
+
error?: string;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* Ajoute un compteur d'étapes sur une image
|
| 19 |
+
* @param imageSrc Source de l'image (base64 ou URL)
|
| 20 |
+
* @param stepNumber Numéro de l'étape
|
| 21 |
+
* @param totalSteps Nombre total d'étapes
|
| 22 |
+
* @param width Largeur de l'image
|
| 23 |
+
* @param height Hauteur de l'image
|
| 24 |
+
* @returns Promesse résolue avec l'image modifiée en base64
|
| 25 |
+
*/
|
| 26 |
+
const addStepCounter = async (
|
| 27 |
+
imageSrc: string,
|
| 28 |
+
stepNumber: number,
|
| 29 |
+
totalSteps: number,
|
| 30 |
+
width: number,
|
| 31 |
+
height: number
|
| 32 |
+
): Promise<string> => {
|
| 33 |
+
return new Promise((resolve, reject) => {
|
| 34 |
+
const img = new Image();
|
| 35 |
+
img.crossOrigin = 'anonymous';
|
| 36 |
+
|
| 37 |
+
img.onload = () => {
|
| 38 |
+
const canvas = document.createElement('canvas');
|
| 39 |
+
canvas.width = width;
|
| 40 |
+
canvas.height = height;
|
| 41 |
+
const ctx = canvas.getContext('2d');
|
| 42 |
+
|
| 43 |
+
if (!ctx) {
|
| 44 |
+
reject(new Error('Cannot get canvas context'));
|
| 45 |
+
return;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Dessiner l'image
|
| 49 |
+
ctx.drawImage(img, 0, 0, width, height);
|
| 50 |
+
|
| 51 |
+
// Configurer le style du compteur
|
| 52 |
+
const fontSize = Math.max(12, Math.floor(height * 0.08));
|
| 53 |
+
const padding = Math.max(6, Math.floor(height * 0.03));
|
| 54 |
+
const text = `${stepNumber}/${totalSteps}`;
|
| 55 |
+
|
| 56 |
+
ctx.font = `bold ${fontSize}px Arial, sans-serif`;
|
| 57 |
+
const textMetrics = ctx.measureText(text);
|
| 58 |
+
const textWidth = textMetrics.width;
|
| 59 |
+
const textHeight = fontSize;
|
| 60 |
+
|
| 61 |
+
// Position en bas à droite
|
| 62 |
+
const x = width - textWidth - padding * 2;
|
| 63 |
+
const y = height - padding * 2;
|
| 64 |
+
|
| 65 |
+
// Dessiner un rectangle semi-transparent pour la lisibilité
|
| 66 |
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
| 67 |
+
ctx.fillRect(
|
| 68 |
+
x - padding,
|
| 69 |
+
y - textHeight - padding,
|
| 70 |
+
textWidth + padding * 2,
|
| 71 |
+
textHeight + padding * 2
|
| 72 |
+
);
|
| 73 |
+
|
| 74 |
+
// Dessiner le texte en noir
|
| 75 |
+
ctx.fillStyle = '#000000';
|
| 76 |
+
ctx.textBaseline = 'top';
|
| 77 |
+
ctx.fillText(text, x, y - textHeight);
|
| 78 |
+
|
| 79 |
+
// Convertir le canvas en base64
|
| 80 |
+
resolve(canvas.toDataURL('image/png'));
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
img.onerror = () => {
|
| 84 |
+
reject(new Error('Failed to load image'));
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
img.src = imageSrc;
|
| 88 |
+
});
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
/**
|
| 92 |
+
* Génère un GIF à partir d'une liste d'images (base64 ou URLs)
|
| 93 |
+
* @param options Options de génération du GIF
|
| 94 |
+
* @returns Promesse résolue avec le résultat de la génération
|
| 95 |
+
*/
|
| 96 |
+
export const generateGif = async (
|
| 97 |
+
options: GifGenerationOptions
|
| 98 |
+
): Promise<GifGenerationResult> => {
|
| 99 |
+
const {
|
| 100 |
+
images,
|
| 101 |
+
interval = 1.5, // 1.5 secondes par frame par défaut
|
| 102 |
+
gifWidth = 400,
|
| 103 |
+
gifHeight = 200,
|
| 104 |
+
quality = 10,
|
| 105 |
+
} = options;
|
| 106 |
+
|
| 107 |
+
if (!images || images.length === 0) {
|
| 108 |
+
return {
|
| 109 |
+
success: false,
|
| 110 |
+
error: 'Aucune image fournie pour générer le GIF',
|
| 111 |
+
};
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
try {
|
| 115 |
+
// Ajouter le compteur sur chaque image
|
| 116 |
+
const imagesWithCounter = await Promise.all(
|
| 117 |
+
images.map((img, index) =>
|
| 118 |
+
addStepCounter(img, index + 1, images.length, gifWidth, gifHeight)
|
| 119 |
+
)
|
| 120 |
+
);
|
| 121 |
+
|
| 122 |
+
return new Promise((resolve) => {
|
| 123 |
+
gifshot.createGIF(
|
| 124 |
+
{
|
| 125 |
+
images: imagesWithCounter,
|
| 126 |
+
interval,
|
| 127 |
+
gifWidth,
|
| 128 |
+
gifHeight,
|
| 129 |
+
numFrames: imagesWithCounter.length,
|
| 130 |
+
frameDuration: interval,
|
| 131 |
+
sampleInterval: quality,
|
| 132 |
+
},
|
| 133 |
+
(obj: { error: boolean; errorMsg?: string; image?: string }) => {
|
| 134 |
+
if (obj.error) {
|
| 135 |
+
resolve({
|
| 136 |
+
success: false,
|
| 137 |
+
error: obj.errorMsg || 'Erreur lors de la génération du GIF',
|
| 138 |
+
});
|
| 139 |
+
} else {
|
| 140 |
+
resolve({
|
| 141 |
+
success: true,
|
| 142 |
+
image: obj.image,
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
);
|
| 147 |
+
});
|
| 148 |
+
} catch (error) {
|
| 149 |
+
return {
|
| 150 |
+
success: false,
|
| 151 |
+
error: error instanceof Error ? error.message : 'Erreur inconnue',
|
| 152 |
+
};
|
| 153 |
+
}
|
| 154 |
+
};
|
| 155 |
+
|
| 156 |
+
/**
|
| 157 |
+
* Télécharge un GIF (data URL) avec un nom de fichier
|
| 158 |
+
* @param dataUrl Data URL du GIF
|
| 159 |
+
* @param filename Nom du fichier à télécharger
|
| 160 |
+
*/
|
| 161 |
+
export const downloadGif = (dataUrl: string, filename: string = 'trace-replay.gif') => {
|
| 162 |
+
const link = document.createElement('a');
|
| 163 |
+
link.href = dataUrl;
|
| 164 |
+
link.download = filename;
|
| 165 |
+
document.body.appendChild(link);
|
| 166 |
+
link.click();
|
| 167 |
+
document.body.removeChild(link);
|
| 168 |
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export { api } from './api';
|
| 2 |
+
export * from './gifGenerator';
|
| 3 |
+
export * from './jsonExporter';
|
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentTrace, AgentStep, AgentTraceMetadata } from '@/types/agent';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Export the complete trace as JSON
|
| 5 |
+
* @param trace The agent trace
|
| 6 |
+
* @param steps The trace steps
|
| 7 |
+
* @param metadata The final metadata
|
| 8 |
+
* @returns A JSON object containing the entire trace
|
| 9 |
+
*/
|
| 10 |
+
export const exportTraceToJson = (
|
| 11 |
+
trace: AgentTrace,
|
| 12 |
+
steps: AgentStep[],
|
| 13 |
+
metadata?: AgentTraceMetadata
|
| 14 |
+
): string => {
|
| 15 |
+
const exportData = {
|
| 16 |
+
trace: {
|
| 17 |
+
id: trace.id,
|
| 18 |
+
timestamp: trace.timestamp,
|
| 19 |
+
instruction: trace.instruction,
|
| 20 |
+
modelId: trace.modelId,
|
| 21 |
+
isRunning: trace.isRunning,
|
| 22 |
+
},
|
| 23 |
+
metadata: metadata || trace.traceMetadata,
|
| 24 |
+
steps: steps.map((step) => ({
|
| 25 |
+
traceId: step.traceId,
|
| 26 |
+
stepId: step.stepId,
|
| 27 |
+
error: step.error,
|
| 28 |
+
thought: step.thought,
|
| 29 |
+
actions: step.actions,
|
| 30 |
+
duration: step.duration,
|
| 31 |
+
inputTokensUsed: step.inputTokensUsed,
|
| 32 |
+
outputTokensUsed: step.outputTokensUsed,
|
| 33 |
+
step_evaluation: step.step_evaluation,
|
| 34 |
+
// Ne pas inclure l'image base64 pour réduire la taille du JSON
|
| 35 |
+
hasImage: !!step.image,
|
| 36 |
+
})),
|
| 37 |
+
exportedAt: new Date().toISOString(),
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
return JSON.stringify(exportData, null, 2);
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Télécharge un JSON avec un nom de fichier
|
| 45 |
+
* @param jsonString String JSON à télécharger
|
| 46 |
+
* @param filename Nom du fichier à télécharger
|
| 47 |
+
*/
|
| 48 |
+
export const downloadJson = (jsonString: string, filename: string = 'trace.json') => {
|
| 49 |
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
| 50 |
+
const url = URL.createObjectURL(blob);
|
| 51 |
+
const link = document.createElement('a');
|
| 52 |
+
link.href = url;
|
| 53 |
+
link.download = filename;
|
| 54 |
+
document.body.appendChild(link);
|
| 55 |
+
link.click();
|
| 56 |
+
document.body.removeChild(link);
|
| 57 |
+
URL.revokeObjectURL(url);
|
| 58 |
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { create } from 'zustand';
|
| 2 |
+
import { devtools } from 'zustand/middleware';
|
| 3 |
+
import { AgentTrace, AgentStep, AgentTraceMetadata, FinalStep } from '@/types/agent';
|
| 4 |
+
|
| 5 |
+
interface AgentState {
|
| 6 |
+
// State
|
| 7 |
+
trace?: AgentTrace;
|
| 8 |
+
isAgentProcessing: boolean;
|
| 9 |
+
isConnectingToE2B: boolean; // New state for E2B connection
|
| 10 |
+
vncUrl: string;
|
| 11 |
+
selectedModelId: string;
|
| 12 |
+
availableModels: string[];
|
| 13 |
+
isLoadingModels: boolean;
|
| 14 |
+
isConnected: boolean;
|
| 15 |
+
error?: string;
|
| 16 |
+
isDarkMode: boolean;
|
| 17 |
+
selectedStepIndex: number | null; // null = live mode, number = viewing specific step or 'final'
|
| 18 |
+
finalStep?: FinalStep; // Special step for success/failure
|
| 19 |
+
|
| 20 |
+
// Actions
|
| 21 |
+
setTrace: (trace: AgentTrace | undefined) => void;
|
| 22 |
+
updateTraceWithStep: (step: AgentStep, metadata: AgentTraceMetadata) => void;
|
| 23 |
+
completeTrace: (metadata: AgentTraceMetadata) => void;
|
| 24 |
+
setIsAgentProcessing: (processing: boolean) => void;
|
| 25 |
+
setIsConnectingToE2B: (connecting: boolean) => void;
|
| 26 |
+
setVncUrl: (url: string) => void;
|
| 27 |
+
setSelectedModelId: (modelId: string) => void;
|
| 28 |
+
setAvailableModels: (models: string[]) => void;
|
| 29 |
+
setIsLoadingModels: (loading: boolean) => void;
|
| 30 |
+
setIsConnected: (connected: boolean) => void;
|
| 31 |
+
setError: (error: string | undefined) => void;
|
| 32 |
+
setSelectedStepIndex: (index: number | null) => void;
|
| 33 |
+
toggleDarkMode: () => void;
|
| 34 |
+
resetAgent: () => void;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
const initialState = {
|
| 38 |
+
trace: undefined,
|
| 39 |
+
isAgentProcessing: false,
|
| 40 |
+
isConnectingToE2B: false,
|
| 41 |
+
vncUrl: '',
|
| 42 |
+
selectedModelId: 'Qwen/Qwen3-VL-8B-Instruct',
|
| 43 |
+
availableModels: [],
|
| 44 |
+
isLoadingModels: false,
|
| 45 |
+
isConnected: false,
|
| 46 |
+
error: undefined,
|
| 47 |
+
isDarkMode: false,
|
| 48 |
+
selectedStepIndex: null, // null = live mode
|
| 49 |
+
finalStep: undefined,
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
export const useAgentStore = create<AgentState>()(
|
| 53 |
+
devtools(
|
| 54 |
+
(set) => ({
|
| 55 |
+
...initialState,
|
| 56 |
+
|
| 57 |
+
// Set the complete trace
|
| 58 |
+
setTrace: (trace) =>
|
| 59 |
+
set({ trace }, false, 'setTrace'),
|
| 60 |
+
|
| 61 |
+
// Update trace with a new step
|
| 62 |
+
updateTraceWithStep: (step, metadata) =>
|
| 63 |
+
set(
|
| 64 |
+
(state) => {
|
| 65 |
+
if (!state.trace) return state;
|
| 66 |
+
|
| 67 |
+
const existingSteps = state.trace.steps || [];
|
| 68 |
+
const stepExists = existingSteps.some((s) => s.stepId === step.stepId);
|
| 69 |
+
|
| 70 |
+
if (stepExists) return state;
|
| 71 |
+
|
| 72 |
+
// Preserve existing maxSteps if new metadata has 0
|
| 73 |
+
const updatedMetadata = {
|
| 74 |
+
...metadata,
|
| 75 |
+
maxSteps: metadata.maxSteps > 0
|
| 76 |
+
? metadata.maxSteps
|
| 77 |
+
: (state.trace.traceMetadata?.maxSteps || 200),
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
return {
|
| 81 |
+
trace: {
|
| 82 |
+
...state.trace,
|
| 83 |
+
steps: [...existingSteps, step],
|
| 84 |
+
traceMetadata: updatedMetadata,
|
| 85 |
+
isRunning: true,
|
| 86 |
+
},
|
| 87 |
+
};
|
| 88 |
+
},
|
| 89 |
+
false,
|
| 90 |
+
'updateTraceWithStep'
|
| 91 |
+
),
|
| 92 |
+
|
| 93 |
+
// Complete the trace
|
| 94 |
+
completeTrace: (metadata) =>
|
| 95 |
+
set(
|
| 96 |
+
(state) => {
|
| 97 |
+
if (!state.trace) return state;
|
| 98 |
+
|
| 99 |
+
// Preserve existing maxSteps if new metadata has 0
|
| 100 |
+
const updatedMetadata = {
|
| 101 |
+
...metadata,
|
| 102 |
+
maxSteps: metadata.maxSteps > 0
|
| 103 |
+
? metadata.maxSteps
|
| 104 |
+
: (state.trace.traceMetadata?.maxSteps || 200),
|
| 105 |
+
completed: true,
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
// Determine if the task succeeded or failed based on error state
|
| 109 |
+
const finalStep: FinalStep = {
|
| 110 |
+
type: state.error ? 'failure' : 'success',
|
| 111 |
+
message: state.error,
|
| 112 |
+
metadata: updatedMetadata,
|
| 113 |
+
};
|
| 114 |
+
|
| 115 |
+
return {
|
| 116 |
+
trace: {
|
| 117 |
+
...state.trace,
|
| 118 |
+
isRunning: false,
|
| 119 |
+
traceMetadata: updatedMetadata,
|
| 120 |
+
},
|
| 121 |
+
finalStep,
|
| 122 |
+
// Keep error in state for display
|
| 123 |
+
selectedStepIndex: null, // Reset to live mode on completion
|
| 124 |
+
};
|
| 125 |
+
},
|
| 126 |
+
false,
|
| 127 |
+
'completeTrace'
|
| 128 |
+
),
|
| 129 |
+
|
| 130 |
+
// Set processing state
|
| 131 |
+
setIsAgentProcessing: (isAgentProcessing) =>
|
| 132 |
+
set({ isAgentProcessing }, false, 'setIsAgentProcessing'),
|
| 133 |
+
|
| 134 |
+
// Set E2B connection state
|
| 135 |
+
setIsConnectingToE2B: (isConnectingToE2B) =>
|
| 136 |
+
set({ isConnectingToE2B }, false, 'setIsConnectingToE2B'),
|
| 137 |
+
|
| 138 |
+
// Set VNC URL
|
| 139 |
+
setVncUrl: (vncUrl) =>
|
| 140 |
+
set({ vncUrl }, false, 'setVncUrl'),
|
| 141 |
+
|
| 142 |
+
// Set selected model ID
|
| 143 |
+
setSelectedModelId: (selectedModelId) =>
|
| 144 |
+
set({ selectedModelId }, false, 'setSelectedModelId'),
|
| 145 |
+
|
| 146 |
+
// Set available models
|
| 147 |
+
setAvailableModels: (availableModels) =>
|
| 148 |
+
set({ availableModels }, false, 'setAvailableModels'),
|
| 149 |
+
|
| 150 |
+
// Set loading models state
|
| 151 |
+
setIsLoadingModels: (isLoadingModels) =>
|
| 152 |
+
set({ isLoadingModels }, false, 'setIsLoadingModels'),
|
| 153 |
+
|
| 154 |
+
// Set connection status
|
| 155 |
+
setIsConnected: (isConnected) =>
|
| 156 |
+
set({ isConnected }, false, 'setIsConnected'),
|
| 157 |
+
|
| 158 |
+
// Set error
|
| 159 |
+
setError: (error) =>
|
| 160 |
+
set(
|
| 161 |
+
(state) => {
|
| 162 |
+
// If there's an error and a trace, mark it as failed
|
| 163 |
+
if (error && state.trace) {
|
| 164 |
+
const metadata = state.trace.traceMetadata || {
|
| 165 |
+
traceId: state.trace.id,
|
| 166 |
+
inputTokensUsed: 0,
|
| 167 |
+
outputTokensUsed: 0,
|
| 168 |
+
duration: 0,
|
| 169 |
+
numberOfSteps: state.trace.steps?.length || 0,
|
| 170 |
+
maxSteps: 200,
|
| 171 |
+
completed: false,
|
| 172 |
+
};
|
| 173 |
+
|
| 174 |
+
// Ensure maxSteps is not 0
|
| 175 |
+
const finalMetadata = {
|
| 176 |
+
...metadata,
|
| 177 |
+
maxSteps: metadata.maxSteps > 0 ? metadata.maxSteps : 200,
|
| 178 |
+
};
|
| 179 |
+
|
| 180 |
+
const finalStep: FinalStep = {
|
| 181 |
+
type: 'failure',
|
| 182 |
+
message: error,
|
| 183 |
+
metadata: finalMetadata,
|
| 184 |
+
};
|
| 185 |
+
|
| 186 |
+
return {
|
| 187 |
+
error,
|
| 188 |
+
finalStep,
|
| 189 |
+
trace: {
|
| 190 |
+
...state.trace,
|
| 191 |
+
isRunning: false,
|
| 192 |
+
},
|
| 193 |
+
selectedStepIndex: null, // Reset to live mode on error
|
| 194 |
+
};
|
| 195 |
+
}
|
| 196 |
+
return { error };
|
| 197 |
+
},
|
| 198 |
+
false,
|
| 199 |
+
'setError'
|
| 200 |
+
),
|
| 201 |
+
|
| 202 |
+
// Set selected step index for time travel
|
| 203 |
+
setSelectedStepIndex: (selectedStepIndex) =>
|
| 204 |
+
set({ selectedStepIndex }, false, 'setSelectedStepIndex'),
|
| 205 |
+
|
| 206 |
+
// Toggle dark mode
|
| 207 |
+
toggleDarkMode: () =>
|
| 208 |
+
set((state) => ({ isDarkMode: !state.isDarkMode }), false, 'toggleDarkMode'),
|
| 209 |
+
|
| 210 |
+
// Reset agent state
|
| 211 |
+
resetAgent: () =>
|
| 212 |
+
set((state) => ({
|
| 213 |
+
...initialState,
|
| 214 |
+
isDarkMode: state.isDarkMode, // Keep dark mode preference
|
| 215 |
+
isConnected: state.isConnected, // Keep connection status
|
| 216 |
+
selectedModelId: state.selectedModelId, // Keep selected model
|
| 217 |
+
availableModels: state.availableModels, // Keep available models
|
| 218 |
+
isLoadingModels: state.isLoadingModels // Keep loading state
|
| 219 |
+
}), false, 'resetAgent'),
|
| 220 |
+
}),
|
| 221 |
+
{ name: 'AgentStore' }
|
| 222 |
+
)
|
| 223 |
+
);
|
| 224 |
+
|
| 225 |
+
// Selectors for better performance
|
| 226 |
+
export const selectTrace = (state: AgentState) => state.trace;
|
| 227 |
+
export const selectIsAgentProcessing = (state: AgentState) => state.isAgentProcessing;
|
| 228 |
+
export const selectIsConnectingToE2B = (state: AgentState) => state.isConnectingToE2B;
|
| 229 |
+
export const selectVncUrl = (state: AgentState) => state.vncUrl;
|
| 230 |
+
export const selectSelectedModelId = (state: AgentState) => state.selectedModelId;
|
| 231 |
+
export const selectAvailableModels = (state: AgentState) => state.availableModels;
|
| 232 |
+
export const selectIsLoadingModels = (state: AgentState) => state.isLoadingModels;
|
| 233 |
+
export const selectIsConnected = (state: AgentState) => state.isConnected;
|
| 234 |
+
export const selectSteps = (state: AgentState) => state.trace?.steps;
|
| 235 |
+
export const selectMetadata = (state: AgentState) => state.trace?.traceMetadata;
|
| 236 |
+
export const selectError = (state: AgentState) => state.error;
|
| 237 |
+
export const selectIsDarkMode = (state: AgentState) => state.isDarkMode;
|
| 238 |
+
export const selectSelectedStepIndex = (state: AgentState) => state.selectedStepIndex;
|
| 239 |
+
export const selectFinalStep = (state: AgentState) => state.finalStep;
|
| 240 |
+
|
| 241 |
+
// Composite selector for selected step (avoids infinite loops)
|
| 242 |
+
export const selectSelectedStep = (state: AgentState) => {
|
| 243 |
+
const steps = state.trace?.steps;
|
| 244 |
+
const selectedIndex = state.selectedStepIndex;
|
| 245 |
+
|
| 246 |
+
if (selectedIndex === null || !steps || selectedIndex >= steps.length) {
|
| 247 |
+
return null;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
return steps[selectedIndex];
|
| 251 |
+
};
|
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createTheme, alpha, Theme } from "@mui/material/styles";
|
| 2 |
+
|
| 3 |
+
const getDesignTokens = (mode: 'light' | 'dark') => ({
|
| 4 |
+
typography: {
|
| 5 |
+
fontFamily: [
|
| 6 |
+
"-apple-system",
|
| 7 |
+
"BlinkMacSystemFont",
|
| 8 |
+
'"Segoe UI"',
|
| 9 |
+
"Roboto",
|
| 10 |
+
'"Helvetica Neue"',
|
| 11 |
+
"Arial",
|
| 12 |
+
"sans-serif",
|
| 13 |
+
].join(","),
|
| 14 |
+
h1: {
|
| 15 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 16 |
+
},
|
| 17 |
+
h2: {
|
| 18 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 19 |
+
},
|
| 20 |
+
h3: {
|
| 21 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 22 |
+
},
|
| 23 |
+
h4: {
|
| 24 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 25 |
+
},
|
| 26 |
+
h5: {
|
| 27 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 28 |
+
},
|
| 29 |
+
h6: {
|
| 30 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 31 |
+
},
|
| 32 |
+
subtitle1: {
|
| 33 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 34 |
+
},
|
| 35 |
+
subtitle2: {
|
| 36 |
+
fontFamily: '"Source Sans Pro", sans-serif',
|
| 37 |
+
},
|
| 38 |
+
},
|
| 39 |
+
palette: {
|
| 40 |
+
mode,
|
| 41 |
+
primary: {
|
| 42 |
+
main: "#4F86C6",
|
| 43 |
+
light: mode === "light" ? "#7BA7D7" : "#6B97D7",
|
| 44 |
+
dark: mode === "light" ? "#2B5C94" : "#3B6CA4",
|
| 45 |
+
50: mode === "light" ? alpha("#4F86C6", 0.05) : alpha("#4F86C6", 0.15),
|
| 46 |
+
100: mode === "light" ? alpha("#4F86C6", 0.1) : alpha("#4F86C6", 0.2),
|
| 47 |
+
200: mode === "light" ? alpha("#4F86C6", 0.2) : alpha("#4F86C6", 0.3),
|
| 48 |
+
contrastText: "#fff",
|
| 49 |
+
},
|
| 50 |
+
background: {
|
| 51 |
+
default: mode === "light" ? "#f8f9fa" : "#0a0a0a",
|
| 52 |
+
paper: mode === "light" ? "#fff" : "#1a1a1a",
|
| 53 |
+
subtle: mode === "light" ? "grey.100" : "grey.900",
|
| 54 |
+
hover: mode === "light" ? "action.hover" : alpha("#fff", 0.08),
|
| 55 |
+
tooltip: mode === "light" ? alpha("#212121", 0.9) : alpha("#fff", 0.9),
|
| 56 |
+
},
|
| 57 |
+
text: {
|
| 58 |
+
primary: mode === "light" ? "rgba(0, 0, 0, 0.87)" : "#fff",
|
| 59 |
+
secondary:
|
| 60 |
+
mode === "light" ? "rgba(0, 0, 0, 0.6)" : "rgba(255, 255, 255, 0.7)",
|
| 61 |
+
disabled:
|
| 62 |
+
mode === "light" ? "rgba(0, 0, 0, 0.38)" : "rgba(255, 255, 255, 0.5)",
|
| 63 |
+
hint:
|
| 64 |
+
mode === "light" ? "rgba(0, 0, 0, 0.38)" : "rgba(255, 255, 255, 0.5)",
|
| 65 |
+
},
|
| 66 |
+
divider:
|
| 67 |
+
mode === "light" ? "rgba(0, 0, 0, 0.15)" : "rgba(255, 255, 255, 0.18)",
|
| 68 |
+
action: {
|
| 69 |
+
active:
|
| 70 |
+
mode === "light" ? "rgba(0, 0, 0, 0.54)" : "rgba(255, 255, 255, 0.7)",
|
| 71 |
+
hover:
|
| 72 |
+
mode === "light" ? "rgba(0, 0, 0, 0.04)" : "rgba(255, 255, 255, 0.08)",
|
| 73 |
+
selected:
|
| 74 |
+
mode === "light" ? "rgba(0, 0, 0, 0.08)" : "rgba(255, 255, 255, 0.16)",
|
| 75 |
+
disabled:
|
| 76 |
+
mode === "light" ? "rgba(0, 0, 0, 0.26)" : "rgba(255, 255, 255, 0.3)",
|
| 77 |
+
disabledBackground:
|
| 78 |
+
mode === "light" ? "rgba(0, 0, 0, 0.12)" : "rgba(255, 255, 255, 0.12)",
|
| 79 |
+
},
|
| 80 |
+
},
|
| 81 |
+
shape: {
|
| 82 |
+
borderRadius: 6,
|
| 83 |
+
},
|
| 84 |
+
components: {
|
| 85 |
+
MuiCssBaseline: {
|
| 86 |
+
styleOverrides: {
|
| 87 |
+
"html, body": {
|
| 88 |
+
backgroundColor: "background.default",
|
| 89 |
+
color: mode === "dark" ? "#fff" : "#000",
|
| 90 |
+
},
|
| 91 |
+
body: {
|
| 92 |
+
"& *::-webkit-scrollbar": {
|
| 93 |
+
width: 8,
|
| 94 |
+
height: 8,
|
| 95 |
+
backgroundColor: "transparent",
|
| 96 |
+
},
|
| 97 |
+
"& *::-webkit-scrollbar-thumb": {
|
| 98 |
+
borderRadius: 6,
|
| 99 |
+
backgroundColor:
|
| 100 |
+
mode === "light" ? alpha("#000", 0.2) : alpha("#fff", 0.1),
|
| 101 |
+
"&:hover": {
|
| 102 |
+
backgroundColor:
|
| 103 |
+
mode === "light" ? alpha("#000", 0.3) : alpha("#fff", 0.15),
|
| 104 |
+
},
|
| 105 |
+
},
|
| 106 |
+
},
|
| 107 |
+
},
|
| 108 |
+
},
|
| 109 |
+
MuiButton: {
|
| 110 |
+
styleOverrides: {
|
| 111 |
+
root: {
|
| 112 |
+
borderRadius: 6,
|
| 113 |
+
textTransform: 'none',
|
| 114 |
+
fontWeight: 600,
|
| 115 |
+
},
|
| 116 |
+
},
|
| 117 |
+
},
|
| 118 |
+
MuiPaper: {
|
| 119 |
+
defaultProps: {
|
| 120 |
+
elevation: 0,
|
| 121 |
+
},
|
| 122 |
+
styleOverrides: {
|
| 123 |
+
root: {
|
| 124 |
+
backgroundImage: "none",
|
| 125 |
+
boxShadow: "none",
|
| 126 |
+
border: "1px solid",
|
| 127 |
+
borderColor:
|
| 128 |
+
mode === "light"
|
| 129 |
+
? "rgba(0, 0, 0, 0.15)!important"
|
| 130 |
+
: "rgba(255, 255, 255, 0.18)!important",
|
| 131 |
+
},
|
| 132 |
+
rounded: {
|
| 133 |
+
borderRadius: 10,
|
| 134 |
+
},
|
| 135 |
+
},
|
| 136 |
+
},
|
| 137 |
+
|
| 138 |
+
MuiTableCell: {
|
| 139 |
+
styleOverrides: {
|
| 140 |
+
root: {
|
| 141 |
+
borderColor: (theme: Theme) =>
|
| 142 |
+
alpha(
|
| 143 |
+
theme.palette.divider,
|
| 144 |
+
theme.palette.mode === "dark" ? 0.1 : 0.2
|
| 145 |
+
),
|
| 146 |
+
},
|
| 147 |
+
head: {
|
| 148 |
+
backgroundColor: mode === "light" ? "grey.50" : "grey.900",
|
| 149 |
+
color: "text.primary",
|
| 150 |
+
fontWeight: 600,
|
| 151 |
+
},
|
| 152 |
+
},
|
| 153 |
+
},
|
| 154 |
+
MuiTableRow: {
|
| 155 |
+
styleOverrides: {
|
| 156 |
+
root: {
|
| 157 |
+
backgroundColor: "transparent",
|
| 158 |
+
},
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
MuiTableContainer: {
|
| 162 |
+
styleOverrides: {
|
| 163 |
+
root: {
|
| 164 |
+
backgroundColor: "background.paper",
|
| 165 |
+
borderRadius: 6,
|
| 166 |
+
border: "none",
|
| 167 |
+
boxShadow: "none",
|
| 168 |
+
},
|
| 169 |
+
},
|
| 170 |
+
},
|
| 171 |
+
MuiSlider: {
|
| 172 |
+
styleOverrides: {
|
| 173 |
+
root: {
|
| 174 |
+
"& .MuiSlider-valueLabel": {
|
| 175 |
+
backgroundColor: "background.paper",
|
| 176 |
+
color: "text.primary",
|
| 177 |
+
border: "1px solid",
|
| 178 |
+
borderColor: "divider",
|
| 179 |
+
boxShadow:
|
| 180 |
+
mode === "light"
|
| 181 |
+
? "0px 2px 4px rgba(0, 0, 0, 0.1)"
|
| 182 |
+
: "0px 2px 4px rgba(0, 0, 0, 0.3)",
|
| 183 |
+
},
|
| 184 |
+
},
|
| 185 |
+
thumb: {
|
| 186 |
+
"&:hover": {
|
| 187 |
+
boxShadow: (theme: Theme) =>
|
| 188 |
+
`0px 0px 0px 8px ${alpha(
|
| 189 |
+
theme.palette.primary.main,
|
| 190 |
+
mode === "light" ? 0.08 : 0.16
|
| 191 |
+
)}`,
|
| 192 |
+
},
|
| 193 |
+
"&.Mui-active": {
|
| 194 |
+
boxShadow: (theme: Theme) =>
|
| 195 |
+
`0px 0px 0px 12px ${alpha(
|
| 196 |
+
theme.palette.primary.main,
|
| 197 |
+
mode === "light" ? 0.08 : 0.16
|
| 198 |
+
)}`,
|
| 199 |
+
},
|
| 200 |
+
},
|
| 201 |
+
track: {
|
| 202 |
+
border: "none",
|
| 203 |
+
},
|
| 204 |
+
rail: {
|
| 205 |
+
opacity: mode === "light" ? 0.38 : 0.3,
|
| 206 |
+
},
|
| 207 |
+
mark: {
|
| 208 |
+
backgroundColor: mode === "light" ? "grey.400" : "grey.600",
|
| 209 |
+
},
|
| 210 |
+
markLabel: {
|
| 211 |
+
color: "text.secondary",
|
| 212 |
+
},
|
| 213 |
+
},
|
| 214 |
+
},
|
| 215 |
+
MuiTextField: {
|
| 216 |
+
styleOverrides: {
|
| 217 |
+
root: {
|
| 218 |
+
"& .MuiOutlinedInput-root": {
|
| 219 |
+
borderRadius: 6,
|
| 220 |
+
},
|
| 221 |
+
},
|
| 222 |
+
},
|
| 223 |
+
},
|
| 224 |
+
MuiChip: {
|
| 225 |
+
styleOverrides: {
|
| 226 |
+
root: {
|
| 227 |
+
borderRadius: 6,
|
| 228 |
+
fontWeight: 600,
|
| 229 |
+
},
|
| 230 |
+
outlinedInfo: {
|
| 231 |
+
borderWidth: 2,
|
| 232 |
+
fontWeight: 600,
|
| 233 |
+
bgcolor: "info.100",
|
| 234 |
+
borderColor: "info.400",
|
| 235 |
+
color: "info.700",
|
| 236 |
+
"& .MuiChip-label": {
|
| 237 |
+
px: 1.2,
|
| 238 |
+
},
|
| 239 |
+
"&:hover": {
|
| 240 |
+
bgcolor: "info.200",
|
| 241 |
+
},
|
| 242 |
+
},
|
| 243 |
+
outlinedWarning: {
|
| 244 |
+
borderWidth: 2,
|
| 245 |
+
fontWeight: 600,
|
| 246 |
+
bgcolor: "warning.100",
|
| 247 |
+
borderColor: "warning.400",
|
| 248 |
+
color: "warning.700",
|
| 249 |
+
"& .MuiChip-label": {
|
| 250 |
+
px: 1.2,
|
| 251 |
+
},
|
| 252 |
+
"&:hover": {
|
| 253 |
+
bgcolor: "warning.200",
|
| 254 |
+
},
|
| 255 |
+
},
|
| 256 |
+
outlinedSuccess: {
|
| 257 |
+
borderWidth: 2,
|
| 258 |
+
fontWeight: 600,
|
| 259 |
+
bgcolor: "success.100",
|
| 260 |
+
borderColor: "success.400",
|
| 261 |
+
color: "success.700",
|
| 262 |
+
"& .MuiChip-label": {
|
| 263 |
+
px: 1.2,
|
| 264 |
+
},
|
| 265 |
+
"&:hover": {
|
| 266 |
+
bgcolor: "success.200",
|
| 267 |
+
},
|
| 268 |
+
},
|
| 269 |
+
outlinedError: {
|
| 270 |
+
borderWidth: 2,
|
| 271 |
+
fontWeight: 600,
|
| 272 |
+
bgcolor: "error.100",
|
| 273 |
+
borderColor: "error.400",
|
| 274 |
+
color: "error.700",
|
| 275 |
+
"& .MuiChip-label": {
|
| 276 |
+
px: 1.2,
|
| 277 |
+
},
|
| 278 |
+
"&:hover": {
|
| 279 |
+
bgcolor: "error.200",
|
| 280 |
+
},
|
| 281 |
+
},
|
| 282 |
+
outlinedPrimary: {
|
| 283 |
+
borderWidth: 2,
|
| 284 |
+
fontWeight: 600,
|
| 285 |
+
bgcolor: "primary.100",
|
| 286 |
+
borderColor: "primary.400",
|
| 287 |
+
color: "primary.700",
|
| 288 |
+
"& .MuiChip-label": {
|
| 289 |
+
px: 1.2,
|
| 290 |
+
},
|
| 291 |
+
"&:hover": {
|
| 292 |
+
bgcolor: "primary.200",
|
| 293 |
+
},
|
| 294 |
+
},
|
| 295 |
+
outlinedSecondary: {
|
| 296 |
+
borderWidth: 2,
|
| 297 |
+
fontWeight: 600,
|
| 298 |
+
bgcolor: "secondary.100",
|
| 299 |
+
borderColor: "secondary.400",
|
| 300 |
+
color: "secondary.700",
|
| 301 |
+
"& .MuiChip-label": {
|
| 302 |
+
px: 1.2,
|
| 303 |
+
},
|
| 304 |
+
"&:hover": {
|
| 305 |
+
bgcolor: "secondary.200",
|
| 306 |
+
},
|
| 307 |
+
},
|
| 308 |
+
},
|
| 309 |
+
},
|
| 310 |
+
MuiIconButton: {
|
| 311 |
+
styleOverrides: {
|
| 312 |
+
root: {
|
| 313 |
+
borderRadius: 6,
|
| 314 |
+
padding: "8px",
|
| 315 |
+
"&.MuiIconButton-sizeSmall": {
|
| 316 |
+
padding: "4px",
|
| 317 |
+
borderRadius: 4,
|
| 318 |
+
},
|
| 319 |
+
},
|
| 320 |
+
},
|
| 321 |
+
},
|
| 322 |
+
MuiTooltip: {
|
| 323 |
+
styleOverrides: {
|
| 324 |
+
tooltip: {
|
| 325 |
+
backgroundColor:
|
| 326 |
+
mode === "light" ? alpha("#212121", 0.9) : alpha("#424242", 0.9),
|
| 327 |
+
color: "#fff",
|
| 328 |
+
fontSize: "0.875rem",
|
| 329 |
+
padding: "8px 12px",
|
| 330 |
+
maxWidth: 400,
|
| 331 |
+
borderRadius: 6,
|
| 332 |
+
lineHeight: 1.4,
|
| 333 |
+
border: "1px solid",
|
| 334 |
+
borderColor:
|
| 335 |
+
mode === "light" ? alpha("#fff", 0.1) : alpha("#fff", 0.05),
|
| 336 |
+
boxShadow:
|
| 337 |
+
mode === "light"
|
| 338 |
+
? "0 2px 8px rgba(0, 0, 0, 0.15)"
|
| 339 |
+
: "0 2px 8px rgba(0, 0, 0, 0.5)",
|
| 340 |
+
"& b": {
|
| 341 |
+
fontWeight: 600,
|
| 342 |
+
color: "inherit",
|
| 343 |
+
},
|
| 344 |
+
"& a": {
|
| 345 |
+
color: mode === "light" ? "#90caf9" : "#64b5f6",
|
| 346 |
+
textDecoration: "none",
|
| 347 |
+
"&:hover": {
|
| 348 |
+
textDecoration: "underline",
|
| 349 |
+
},
|
| 350 |
+
},
|
| 351 |
+
},
|
| 352 |
+
arrow: {
|
| 353 |
+
color:
|
| 354 |
+
mode === "light" ? alpha("#212121", 0.9) : alpha("#424242", 0.9),
|
| 355 |
+
"&:before": {
|
| 356 |
+
border: "1px solid",
|
| 357 |
+
borderColor:
|
| 358 |
+
mode === "light" ? alpha("#fff", 0.1) : alpha("#fff", 0.05),
|
| 359 |
+
},
|
| 360 |
+
},
|
| 361 |
+
},
|
| 362 |
+
defaultProps: {
|
| 363 |
+
arrow: true,
|
| 364 |
+
enterDelay: 400,
|
| 365 |
+
leaveDelay: 200,
|
| 366 |
+
},
|
| 367 |
+
},
|
| 368 |
+
MuiAppBar: {
|
| 369 |
+
styleOverrides: {
|
| 370 |
+
root: {
|
| 371 |
+
border: "none",
|
| 372 |
+
borderBottom: "none",
|
| 373 |
+
},
|
| 374 |
+
},
|
| 375 |
+
},
|
| 376 |
+
},
|
| 377 |
+
breakpoints: {
|
| 378 |
+
values: {
|
| 379 |
+
xs: 0,
|
| 380 |
+
sm: 600,
|
| 381 |
+
md: 900,
|
| 382 |
+
lg: 1240,
|
| 383 |
+
xl: 1536,
|
| 384 |
+
},
|
| 385 |
+
},
|
| 386 |
+
});
|
| 387 |
+
|
| 388 |
+
const getTheme = (mode: 'light' | 'dark') => {
|
| 389 |
+
const tokens = getDesignTokens(mode);
|
| 390 |
+
return createTheme(tokens);
|
| 391 |
+
};
|
| 392 |
+
|
| 393 |
+
// Export light theme by default
|
| 394 |
+
export const theme = getTheme('light');
|
| 395 |
+
|
| 396 |
+
// Export function to get theme with mode
|
| 397 |
+
export default getTheme;
|