mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
65 Commits
fix/git-ch
...
0.51.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a09ac73cca | ||
|
|
a32a623078 | ||
|
|
03c8312f5f | ||
|
|
b75a61bce9 | ||
|
|
2c36e2447c | ||
|
|
f87c827fe6 | ||
|
|
3f395e3cee | ||
|
|
7a45ebf0f4 | ||
|
|
5b13cfc2a0 | ||
|
|
5553584056 | ||
|
|
e951612ff4 | ||
|
|
426e16b17d | ||
|
|
d9a595c9b1 | ||
|
|
8fb3728391 | ||
|
|
d4c94dce83 | ||
|
|
74d6633e9b | ||
|
|
eecad803b1 | ||
|
|
da7a31a6fa | ||
|
|
c677f7284e | ||
|
|
60e8e55311 | ||
|
|
18557e8654 | ||
|
|
39c67e2b92 | ||
|
|
b5146e3188 | ||
|
|
a59a6f3041 | ||
|
|
056d3e4933 | ||
|
|
2b4a5a73a4 | ||
|
|
46504ab0da | ||
|
|
412f6ce58d | ||
|
|
c8f9e6b9fc | ||
|
|
588e838dc4 | ||
|
|
2550c08749 | ||
|
|
0651c51901 | ||
|
|
3ce19993bc | ||
|
|
26a9abbe82 | ||
|
|
240017add1 | ||
|
|
b5958b069e | ||
|
|
59b8009d7a | ||
|
|
b8b4f58a79 | ||
|
|
fcb190281c | ||
|
|
9fcf900a23 | ||
|
|
06ad5e30c9 | ||
|
|
739044087b | ||
|
|
fa041537c3 | ||
|
|
079f423a4b | ||
|
|
f6060f9c53 | ||
|
|
b7f234641c | ||
|
|
4ac0af699f | ||
|
|
fb9a941722 | ||
|
|
c05339cb2d | ||
|
|
2ef518f063 | ||
|
|
fbd9280239 | ||
|
|
45ac6b839c | ||
|
|
8b59143174 | ||
|
|
c7b8f5d0d1 | ||
|
|
09533d3cb9 | ||
|
|
00582a487c | ||
|
|
7a168b9b5f | ||
|
|
556ec9ab1a | ||
|
|
d567d22748 | ||
|
|
e045b757fa | ||
|
|
38ffc85470 | ||
|
|
58ea7b5248 | ||
|
|
f62ed911d2 | ||
|
|
d13e32bcec | ||
|
|
b978b71c47 |
2
.github/workflows/fe-unit-tests.yml
vendored
2
.github/workflows/fe-unit-tests.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: 22
|
||||
node-version: [22]
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
121
.github/workflows/run-eval.yml
vendored
121
.github/workflows/run-eval.yml
vendored
@@ -1,56 +1,135 @@
|
||||
# Run evaluation on a PR
|
||||
# Run evaluation on a PR, after releases, or manually
|
||||
name: Run Eval
|
||||
|
||||
# Runs when a PR is labeled with one of the "run-eval-" labels
|
||||
# Runs when a PR is labeled with one of the "run-eval-" labels, after releases, or manually triggered
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to evaluate'
|
||||
required: true
|
||||
default: 'main'
|
||||
eval_instances:
|
||||
description: 'Number of evaluation instances'
|
||||
required: true
|
||||
default: '50'
|
||||
type: choice
|
||||
options:
|
||||
- '1'
|
||||
- '2'
|
||||
- '50'
|
||||
- '100'
|
||||
reason:
|
||||
description: 'Reason for manual trigger'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
# Environment variable for the master GitHub issue number where all evaluation results will be commented
|
||||
# This should be set to the issue number where you want all evaluation results to be posted
|
||||
MASTER_EVAL_ISSUE_NUMBER: ${{ vars.MASTER_EVAL_ISSUE_NUMBER || '0' }}
|
||||
|
||||
jobs:
|
||||
trigger-job:
|
||||
name: Trigger remote eval job
|
||||
if: ${{ github.event.label.name == 'run-eval-1' || github.event.label.name == 'run-eval-2' || github.event.label.name == 'run-eval-50' || github.event.label.name == 'run-eval-100' }}
|
||||
if: ${{ (github.event_name == 'pull_request' && (github.event.label.name == 'run-eval-1' || github.event.label.name == 'run-eval-2' || github.event.label.name == 'run-eval-50' || github.event.label.name == 'run-eval-100')) || github.event_name == 'release' || github.event_name == 'workflow_dispatch' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Checkout PR branch
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
ref: ${{ github.event_name == 'pull_request' && github.head_ref || (github.event_name == 'workflow_dispatch' && github.event.inputs.branch) || github.ref }}
|
||||
|
||||
- name: Trigger remote job
|
||||
env:
|
||||
PR_BRANCH: ${{ github.head_ref }}
|
||||
- name: Set evaluation parameters
|
||||
id: eval_params
|
||||
run: |
|
||||
REPO_URL="https://github.com/${{ github.repository }}"
|
||||
echo "Repository URL: $REPO_URL"
|
||||
echo "PR Branch: $PR_BRANCH"
|
||||
|
||||
if [[ "${{ github.event.label.name }}" == "run-eval-1" ]]; then
|
||||
EVAL_INSTANCES="1"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-2" ]]; then
|
||||
EVAL_INSTANCES="2"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-50" ]]; then
|
||||
# Determine branch based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
EVAL_BRANCH="${{ github.head_ref }}"
|
||||
echo "PR Branch: $EVAL_BRANCH"
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
EVAL_BRANCH="${{ github.event.inputs.branch }}"
|
||||
echo "Manual Branch: $EVAL_BRANCH"
|
||||
else
|
||||
# For release events, use the tag name or main branch
|
||||
EVAL_BRANCH="${{ github.ref_name }}"
|
||||
echo "Release Branch/Tag: $EVAL_BRANCH"
|
||||
fi
|
||||
|
||||
# Determine evaluation instances based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
if [[ "${{ github.event.label.name }}" == "run-eval-1" ]]; then
|
||||
EVAL_INSTANCES="1"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-2" ]]; then
|
||||
EVAL_INSTANCES="2"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-50" ]]; then
|
||||
EVAL_INSTANCES="50"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-100" ]]; then
|
||||
EVAL_INSTANCES="100"
|
||||
fi
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
EVAL_INSTANCES="${{ github.event.inputs.eval_instances }}"
|
||||
else
|
||||
# For release events, default to 50 instances
|
||||
EVAL_INSTANCES="50"
|
||||
elif [[ "${{ github.event.label.name }}" == "run-eval-100" ]]; then
|
||||
EVAL_INSTANCES="100"
|
||||
fi
|
||||
|
||||
echo "Evaluation instances: $EVAL_INSTANCES"
|
||||
echo "repo_url=$REPO_URL" >> $GITHUB_OUTPUT
|
||||
echo "eval_branch=$EVAL_BRANCH" >> $GITHUB_OUTPUT
|
||||
echo "eval_instances=$EVAL_INSTANCES" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Trigger remote job
|
||||
run: |
|
||||
# Determine PR number for the remote evaluation system
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
else
|
||||
# For non-PR triggers, use the master issue number as PR number
|
||||
PR_NUMBER="${{ env.MASTER_EVAL_ISSUE_NUMBER }}"
|
||||
fi
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.PAT_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-d "{\"ref\": \"main\", \"inputs\": {\"github-repo\": \"${REPO_URL}\", \"github-branch\": \"${PR_BRANCH}\", \"pr-number\": \"${{ github.event.pull_request.number }}\", \"eval-instances\": \"${EVAL_INSTANCES}\"}}" \
|
||||
-d "{\"ref\": \"main\", \"inputs\": {\"github-repo\": \"${{ steps.eval_params.outputs.repo_url }}\", \"github-branch\": \"${{ steps.eval_params.outputs.eval_branch }}\", \"pr-number\": \"${PR_NUMBER}\", \"eval-instances\": \"${{ steps.eval_params.outputs.eval_instances }}\"}}" \
|
||||
https://api.github.com/repos/All-Hands-AI/evaluation/actions/workflows/create-branch.yml/dispatches
|
||||
|
||||
# Send Slack message
|
||||
PR_URL="https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
|
||||
slack_text="PR $PR_URL has triggered evaluation on $EVAL_INSTANCES instances..."
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
TRIGGER_URL="https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
|
||||
slack_text="PR $TRIGGER_URL has triggered evaluation on ${{ steps.eval_params.outputs.eval_instances }} instances..."
|
||||
elif [[ "${{ github.event_name }}" == "release" ]]; then
|
||||
TRIGGER_URL="https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
|
||||
slack_text="Release $TRIGGER_URL has triggered evaluation on ${{ steps.eval_params.outputs.eval_instances }} instances..."
|
||||
else
|
||||
TRIGGER_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
slack_text="Manual trigger (${{ github.event.inputs.reason || 'No reason provided' }}) has triggered evaluation on ${{ steps.eval_params.outputs.eval_instances }} instances for branch ${{ steps.eval_params.outputs.eval_branch }}..."
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data '{"text":"'"$slack_text"'"}' \
|
||||
https://hooks.slack.com/services/${{ secrets.SLACK_TOKEN }}
|
||||
|
||||
- name: Comment on PR
|
||||
- name: Comment on issue/PR
|
||||
uses: KeisukeYamashita/create-comment@v1
|
||||
with:
|
||||
# For PR triggers, comment on the PR. For other triggers, comment on the master issue
|
||||
number: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || env.MASTER_EVAL_ISSUE_NUMBER }}
|
||||
unique: false
|
||||
comment: |
|
||||
Running evaluation on the PR. Once eval is done, the results will be posted.
|
||||
**Evaluation Triggered**
|
||||
|
||||
**Trigger:** ${{ github.event_name == 'pull_request' && format('Pull Request #{0}', github.event.pull_request.number) || (github.event_name == 'release' && 'Release') || format('Manual Trigger: {0}', github.event.inputs.reason || 'No reason provided') }}
|
||||
**Branch:** ${{ steps.eval_params.outputs.eval_branch }}
|
||||
**Instances:** ${{ steps.eval_params.outputs.eval_instances }}
|
||||
**Commit:** ${{ github.sha }}
|
||||
|
||||
Running evaluation on the specified branch. Once eval is done, the results will be posted here.
|
||||
|
||||
@@ -15,8 +15,6 @@ make build && make run FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.
|
||||
|
||||
IMPORTANT: Before making any changes to the codebase, ALWAYS run `make install-pre-commit-hooks` to ensure pre-commit hooks are properly installed.
|
||||
|
||||
|
||||
|
||||
Before pushing any changes, you MUST ensure that any lint errors or simple test errors have been fixed.
|
||||
|
||||
* If you've made changes to the backend, you should run `pre-commit run --config ./dev_config/python/.pre-commit-config.yaml` (this will run on staged files).
|
||||
@@ -32,6 +30,12 @@ then re-run the command to ensure it passes. Common issues include:
|
||||
- Trailing whitespace
|
||||
- Missing newlines at end of files
|
||||
|
||||
## Git Best Practices
|
||||
|
||||
- Prefer specific `git add <filename>` instead of `git add .` to avoid accidentally staging unintended files
|
||||
- Be especially careful with `git reset --hard` after staging files, as it will remove accidentally staged files
|
||||
- When remote has new changes, use `git fetch upstream && git rebase upstream/<branch>` on the same branch
|
||||
|
||||
## Repository Structure
|
||||
Backend:
|
||||
- Located in the `openhands` directory
|
||||
|
||||
@@ -1,56 +1,158 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Running OpenHands pre-commit hook..."
|
||||
echo "This hook runs selective linting based on changed files."
|
||||
|
||||
# Store the exit code to return at the end
|
||||
# This allows us to be additive to existing pre-commit hooks
|
||||
EXIT_CODE=0
|
||||
|
||||
# Check if frontend directory has changed
|
||||
frontend_changes=$(git diff --cached --name-only | grep "^frontend/")
|
||||
if [ -n "$frontend_changes" ]; then
|
||||
echo "Frontend changes detected. Running frontend checks..."
|
||||
# Get the list of staged files
|
||||
STAGED_FILES=$(git diff --cached --name-only)
|
||||
|
||||
# Check if frontend directory exists
|
||||
if [ -d "frontend" ]; then
|
||||
# Change to frontend directory
|
||||
cd frontend || exit 1
|
||||
# Check if any files match specific patterns
|
||||
has_frontend_changes=false
|
||||
has_backend_changes=false
|
||||
has_vscode_changes=false
|
||||
|
||||
# Run lint:fix
|
||||
echo "Running npm lint:fix..."
|
||||
npm run lint:fix
|
||||
# Check each file individually to avoid issues with grep
|
||||
for file in $STAGED_FILES; do
|
||||
if [[ $file == frontend/* ]]; then
|
||||
has_frontend_changes=true
|
||||
elif [[ $file == openhands/* || $file == evaluation/* || $file == tests/* ]]; then
|
||||
has_backend_changes=true
|
||||
# Check for VSCode extension changes (subset of backend changes)
|
||||
if [[ $file == openhands/integrations/vscode/* ]]; then
|
||||
has_vscode_changes=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Analyzing changes..."
|
||||
echo "- Frontend changes: $has_frontend_changes"
|
||||
echo "- Backend changes: $has_backend_changes"
|
||||
echo "- VSCode extension changes: $has_vscode_changes"
|
||||
|
||||
# Run frontend linting if needed
|
||||
if [ "$has_frontend_changes" = true ]; then
|
||||
# Check if we're in a CI environment or if frontend dependencies are missing
|
||||
if [ -n "$CI" ] || ! command -v react-router &> /dev/null || ! command -v vitest &> /dev/null; then
|
||||
echo "Skipping frontend checks (CI environment or missing dependencies detected)."
|
||||
echo "WARNING: Frontend files have changed but frontend checks are being skipped."
|
||||
echo "Please run 'make lint-frontend' manually before submitting your PR."
|
||||
else
|
||||
echo "Running frontend linting..."
|
||||
make lint-frontend
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Frontend linting checks passed!"
|
||||
fi
|
||||
|
||||
# Run build
|
||||
echo "Running npm build..."
|
||||
npm run build
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend build failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
# Run additional frontend checks
|
||||
if [ -d "frontend" ]; then
|
||||
echo "Running additional frontend checks..."
|
||||
cd frontend || exit 1
|
||||
|
||||
# Run tests
|
||||
echo "Running npm test..."
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend tests failed. Please fix the failing tests before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
# Run build
|
||||
echo "Running npm build..."
|
||||
npm run build
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend build failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
|
||||
# Return to the original directory
|
||||
cd ..
|
||||
# Run tests
|
||||
echo "Running npm test..."
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend tests failed. Please fix the failing tests before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo "Frontend checks passed!"
|
||||
cd ..
|
||||
fi
|
||||
else
|
||||
echo "Frontend directory not found. Skipping frontend checks."
|
||||
fi
|
||||
else
|
||||
echo "No frontend changes detected. Skipping frontend checks."
|
||||
echo "Skipping frontend checks (no frontend changes detected)."
|
||||
fi
|
||||
|
||||
# Run backend linting if needed
|
||||
if [ "$has_backend_changes" = true ]; then
|
||||
echo "Running backend linting..."
|
||||
make lint-backend
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Backend linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Backend linting checks passed!"
|
||||
fi
|
||||
else
|
||||
echo "Skipping backend checks (no backend changes detected)."
|
||||
fi
|
||||
|
||||
# Run VSCode extension checks if needed
|
||||
if [ "$has_vscode_changes" = true ]; then
|
||||
# Check if we're in a CI environment
|
||||
if [ -n "$CI" ]; then
|
||||
echo "Skipping VSCode extension checks (CI environment detected)."
|
||||
echo "WARNING: VSCode extension files have changed but checks are being skipped."
|
||||
echo "Please run VSCode extension checks manually before submitting your PR."
|
||||
else
|
||||
echo "Running VSCode extension checks..."
|
||||
if [ -d "openhands/integrations/vscode" ]; then
|
||||
cd openhands/integrations/vscode || exit 1
|
||||
|
||||
echo "Running npm lint:fix..."
|
||||
npm run lint:fix
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension linting passed!"
|
||||
fi
|
||||
|
||||
echo "Running npm typecheck..."
|
||||
npm run typecheck
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension type checking failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension type checking passed!"
|
||||
fi
|
||||
|
||||
echo "Running npm compile..."
|
||||
npm run compile
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension compilation failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension compilation passed!"
|
||||
fi
|
||||
|
||||
cd ../../..
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Skipping VSCode extension checks (no VSCode extension changes detected)."
|
||||
fi
|
||||
|
||||
# If no specific code changes detected, run basic checks
|
||||
if [ "$has_frontend_changes" = false ] && [ "$has_backend_changes" = false ]; then
|
||||
echo "No specific code changes detected. Running basic checks..."
|
||||
if [ -n "$STAGED_FILES" ]; then
|
||||
# Run only basic pre-commit hooks for non-code files
|
||||
poetry run pre-commit run --files $(echo "$STAGED_FILES" | tr '\n' ' ') --hook-stage commit --config ./dev_config/python/.pre-commit-config.yaml
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Basic checks failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Basic checks passed!"
|
||||
fi
|
||||
else
|
||||
echo "No files changed. Skipping basic checks."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run any existing pre-commit hooks that might have been installed by the user
|
||||
|
||||
@@ -159,7 +159,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.49-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.51-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@@ -174,7 +174,7 @@ install-python-dependencies:
|
||||
fi
|
||||
@echo "$(GREEN)Python dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-frontend-dependencies:
|
||||
install-frontend-dependencies: check-npm check-nodejs
|
||||
@echo "$(YELLOW)Setting up frontend environment...$(RESET)"
|
||||
@echo "$(YELLOW)Detect Node.js version...$(RESET)"
|
||||
@cd frontend && node ./scripts/detect-node-version.js
|
||||
@@ -182,17 +182,17 @@ install-frontend-dependencies:
|
||||
@cd frontend && npm install
|
||||
@echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-pre-commit-hooks:
|
||||
install-pre-commit-hooks: check-python check-poetry install-python-dependencies
|
||||
@echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
|
||||
@git config --unset-all core.hooksPath || true
|
||||
@poetry run pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
@echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
|
||||
|
||||
lint-backend:
|
||||
lint-backend: install-pre-commit-hooks
|
||||
@echo "$(YELLOW)Running linters...$(RESET)"
|
||||
@poetry run pre-commit run --all-files --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
|
||||
lint-frontend:
|
||||
lint-frontend: install-frontend-dependencies
|
||||
@echo "$(YELLOW)Running linters for frontend...$(RESET)"
|
||||
@cd frontend && npm run lint
|
||||
|
||||
|
||||
@@ -62,17 +62,17 @@ system requirements and more information.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.51-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -8,6 +8,29 @@ description: This guide walks you through the process of installing OpenHands Cl
|
||||
|
||||
- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a Bitbucket account](/usage/cloud/openhands-cloud).
|
||||
|
||||
## IP Whitelisting
|
||||
|
||||
If your Bitbucket Cloud instance has IP restrictions, you'll need to whitelist the following IP addresses to allow OpenHands to access your repositories:
|
||||
|
||||
### Core App IP
|
||||
```
|
||||
34.68.58.200
|
||||
```
|
||||
|
||||
### Runtime IPs
|
||||
```
|
||||
34.10.175.217
|
||||
34.136.162.246
|
||||
34.45.0.142
|
||||
34.28.69.126
|
||||
35.224.240.213
|
||||
34.70.174.52
|
||||
34.42.4.87
|
||||
35.222.133.153
|
||||
34.29.175.97
|
||||
34.60.55.59
|
||||
```
|
||||
|
||||
## Adding Bitbucket Repository Access
|
||||
|
||||
Upon signing into OpenHands Cloud with a Bitbucket account, OpenHands will have access to your repositories.
|
||||
|
||||
@@ -24,7 +24,7 @@ description: This guide walks you through installing the OpenHands Slack app.
|
||||
**This step is for Slack admins/owners**
|
||||
|
||||
1. Make sure you have permissions to install Apps to your workspace.
|
||||
2. Click the button below to install OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
||||
2. Click the button below to install OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,channels:history,chat:write,groups:history,im:history,mpim:history,users:read&user_scope="><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
||||
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
||||
4. Review permissions and click allow.
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -112,7 +112,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
@@ -153,6 +153,7 @@ You can use the following commands whenever the prompt (`>`) is displayed:
|
||||
| `/new` | Start a new conversation |
|
||||
| `/settings` | View and modify current LLM/agent settings |
|
||||
| `/resume` | Resume the agent if paused |
|
||||
| `/mcp` | Manage MCP server configuration and view connection errors |
|
||||
|
||||
#### Settings and Configuration
|
||||
|
||||
@@ -162,7 +163,7 @@ follow the prompts:
|
||||
- **Basic settings**: Choose a model/provider and enter your API key.
|
||||
- **Advanced settings**: Set custom endpoints, enable or disable confirmation mode, and configure memory condensation.
|
||||
|
||||
Settings can also be managed via the `config.toml` file.
|
||||
Settings can also be managed via the `config.toml` file in the current directory or `~/.openhands/config.toml`.
|
||||
|
||||
#### Repository Initialization
|
||||
|
||||
@@ -174,6 +175,41 @@ project details and structure. Use this when onboarding the agent to a new codeb
|
||||
You can pause the agent while it is running by pressing `Ctrl-P`. To continue the conversation after pausing, simply
|
||||
type `/resume` at the prompt.
|
||||
|
||||
#### MCP Server Management
|
||||
|
||||
To configure Model Context Protocol (MCP) servers, you can refer to the documentation on [MCP servers](../mcp) and use the `/mcp` command in the CLI. This command provides an interactive interface for managing Model Context Protocol (MCP) servers:
|
||||
|
||||
- **List configured servers**: View all currently configured MCP servers (SSE, Stdio, and SHTTP)
|
||||
- **Add new server**: Interactively add a new MCP server with guided prompts
|
||||
- **Remove server**: Remove an existing MCP server from your configuration
|
||||
- **View errors**: Display any connection errors that occurred during MCP server startup
|
||||
|
||||
This command modifies your `~/.openhands/config.toml` file and will prompt you to restart OpenHands for changes to take effect.
|
||||
|
||||
To enable the [Tavily MCP server](https://github.com/tavily-ai/tavily-mcp) search engine, you can set the `search_api_key` under the `[core]` section in the `~/.openhands/config.toml` file.
|
||||
|
||||
##### Example of the `config.toml` file with MCP server configuration:
|
||||
|
||||
```toml
|
||||
[core]
|
||||
search_api_key = "tvly-your-api-key-here"
|
||||
|
||||
[mcp]
|
||||
stdio_servers = [
|
||||
{name="fetch", command="uvx", args=["mcp-server-fetch"]},
|
||||
]
|
||||
|
||||
sse_servers = [
|
||||
# Basic SSE server with just a URL
|
||||
"http://example.com:8080/sse",
|
||||
]
|
||||
|
||||
shttp_servers = [
|
||||
# Streamable HTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
```
|
||||
|
||||
## Tips and Troubleshooting
|
||||
|
||||
- Use `/help` at any time to see the list of available commands.
|
||||
|
||||
@@ -61,7 +61,7 @@ export GITHUB_TOKEN="your-token" # Required for repository operations
|
||||
# Run OpenHands
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -73,7 +73,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -39,6 +39,12 @@ limits and monitor usage.
|
||||
- [mistralai/devstral-small](https://www.all-hands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free)
|
||||
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
|
||||
|
||||
### Known Issues
|
||||
|
||||
<Warning>
|
||||
As of July 2025, there are known issues with Gemini 2.5 Pro conversations taking longer than normal with OpenHands. We are continuing to investigate.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
Most current local and open source models are not as powerful. When using such models, you may see long
|
||||
wait times between messages, poor responses, or errors about malformed JSON. OpenHands can only be as powerful as the
|
||||
|
||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
```
|
||||
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -67,17 +67,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
### Start the App
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
@@ -10,47 +10,83 @@ Model Context Protocol (MCP) is a mechanism that allows OpenHands to communicate
|
||||
servers can provide additional functionality to the agent, such as specialized data processing, external API access,
|
||||
or custom tools. MCP is based on the open standard defined at [modelcontextprotocol.io](https://modelcontextprotocol.io).
|
||||
|
||||
|
||||
<Note>
|
||||
MCP is currently not available on OpenHands Cloud. This feature is only available when running OpenHands locally.
|
||||
</Note>
|
||||
|
||||
### How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
|
||||
## Configuration
|
||||
|
||||
MCP configuration can be defined in:
|
||||
* The OpenHands UI through the Settings under the `MCP` tab.
|
||||
* The `config.toml` file under the `[mcp]` section if not using the UI.
|
||||
|
||||
### Configuration Example via config.toml
|
||||
### Configuration Examples
|
||||
|
||||
#### Recommended: Using Proxy Servers (SSE/HTTP)
|
||||
|
||||
For stdio-based MCP servers, we recommend using MCP proxy tools like [`supergateway`](https://github.com/supercorp-ai/supergateway) instead of direct stdio connections.
|
||||
[SuperGateway](https://github.com/supercorp-ai/supergateway) is a popular MCP proxy that converts stdio MCP servers to HTTP/SSE endpoints:
|
||||
|
||||
Start the proxy servers separately:
|
||||
```bash
|
||||
# Terminal 1: Filesystem server proxy
|
||||
supergateway --stdio "npx @modelcontextprotocol/server-filesystem /" --port 8080
|
||||
|
||||
# Terminal 2: Fetch server proxy
|
||||
supergateway --stdio "uvx mcp-server-fetch" --port 8081
|
||||
```
|
||||
|
||||
Then configure OpenHands to use the HTTP endpoint:
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# SSE Servers - External servers that communicate via Server-Sent Events
|
||||
# SSE Servers - Recommended approach using proxy tools
|
||||
sse_servers = [
|
||||
# Basic SSE server with just a URL
|
||||
"http://example.com:8080/mcp",
|
||||
|
||||
# SSE server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
# SuperGateway proxy for fetch server
|
||||
"http://localhost:8081/sse",
|
||||
|
||||
# External MCP service with authentication
|
||||
{url="https://api.example.com/mcp/sse", api_key="your-api-key"}
|
||||
]
|
||||
```
|
||||
|
||||
# SHTTP Servers - External servers that communicate via Streamable HTTP
|
||||
shttp_servers = [
|
||||
# Basic SHTTP server with just a URL
|
||||
"http://example.com:8080/mcp",
|
||||
|
||||
# SHTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
|
||||
# Stdio Servers - Local processes that communicate via standard input/output
|
||||
#### Alternative: Direct Stdio Servers (Not Recommended for Production)
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# Direct stdio servers - use only for development/testing
|
||||
stdio_servers = [
|
||||
# Basic stdio server
|
||||
{name="fetch", command="uvx", args=["mcp-server-fetch"]},
|
||||
|
||||
# Stdio server with environment variables
|
||||
{
|
||||
name="data-processor",
|
||||
command="python",
|
||||
args=["-m", "my_mcp_server"],
|
||||
name="filesystem",
|
||||
command="npx",
|
||||
args=["@modelcontextprotocol/server-filesystem", "/"],
|
||||
env={
|
||||
"DEBUG": "true",
|
||||
"PORT": "8080"
|
||||
"DEBUG": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -84,6 +120,8 @@ SHTTP (Streamable HTTP) servers are configured using either a string URL or an o
|
||||
|
||||
### Stdio Servers
|
||||
|
||||
**Note**: While stdio servers are supported, we recommend using MCP proxies (see above) for better reliability and performance.
|
||||
|
||||
Stdio servers are configured using an object with the following properties:
|
||||
|
||||
- `name` (required)
|
||||
@@ -104,20 +142,38 @@ Stdio servers are configured using an object with the following properties:
|
||||
- Default: `{}`
|
||||
- Description: Environment variables to set for the server process
|
||||
|
||||
## How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
#### When to Use Direct Stdio
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
Direct stdio connections may still be appropriate in these scenarios:
|
||||
- **Development and testing**: Quick prototyping of MCP servers
|
||||
- **Simple, single-use tools**: Tools that don't require high reliability or concurrent access
|
||||
- **Local-only environments**: When you don't want to manage additional proxy processes
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
For production use, we recommend using proxy tools like SuperGateway.
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
### Other Proxy Tools
|
||||
|
||||
Other options include:
|
||||
|
||||
- **Custom FastAPI/Express servers**: Build your own HTTP wrapper around stdio MCP servers
|
||||
- **Docker-based proxies**: Containerized solutions for better isolation
|
||||
- **Cloud-hosted MCP services**: Third-party services that provide MCP endpoints
|
||||
|
||||
### Troubleshooting MCP Connections
|
||||
|
||||
#### Common Issues with Stdio Servers
|
||||
- **Process crashes**: Stdio processes may crash without proper error handling
|
||||
- **Deadlocks**: Stdio communication can deadlock under high load
|
||||
- **Resource leaks**: Zombie processes if not properly managed
|
||||
- **Debugging difficulty**: Hard to inspect stdio communication
|
||||
|
||||
#### Benefits of Using Proxies
|
||||
- **HTTP status codes**: Clear error reporting via standard HTTP responses
|
||||
- **Request logging**: Easy to log and monitor HTTP requests
|
||||
- **Load balancing**: Can distribute requests across multiple server instances
|
||||
- **Health checks**: HTTP endpoints can provide health status
|
||||
- **CORS support**: Better integration with web-based tools
|
||||
|
||||
## Transport Protocols
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ npx lint-staged
|
||||
# Run backend pre-commit
|
||||
echo "Running backend pre-commit..."
|
||||
cd ..
|
||||
pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --show-diff-on-failure --config ./dev_config/python/.pre-commit-config.yaml
|
||||
poetry run pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --show-diff-on-failure --config ./dev_config/python/.pre-commit-config.yaml
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("HomeHeader", () => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// expect to be redirected to /conversations/:conversationId
|
||||
|
||||
@@ -209,6 +209,7 @@ describe("RepoConnector", () => {
|
||||
undefined,
|
||||
"main",
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ describe("TaskCard", () => {
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { MaintenanceBanner } from "#/components/features/maintenance/maintenance-banner";
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock("react-i18next", async () => {
|
||||
const actual = await vi.importActual<typeof import("react-i18next")>("react-i18next");
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { time?: string }) => {
|
||||
const translations: Record<string, string> = {
|
||||
"MAINTENANCE$SCHEDULED_MESSAGE": `Scheduled maintenance will begin at ${options?.time || "{{time}}"}`,
|
||||
};
|
||||
return translations[key] || key;
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("MaintenanceBanner", () => {
|
||||
it("renders maintenance banner with formatted time", () => {
|
||||
const startTime = "2024-01-15T10:00:00-05:00"; // EST timestamp
|
||||
|
||||
const { container } = render(<MaintenanceBanner startTime={startTime} />);
|
||||
|
||||
// Check if the banner is rendered
|
||||
expect(screen.getByText(/Scheduled maintenance will begin at/)).toBeInTheDocument();
|
||||
|
||||
// Check if the warning icon (SVG) is present
|
||||
const svgIcon = container.querySelector('svg');
|
||||
expect(svgIcon).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles invalid date gracefully", () => {
|
||||
const invalidTime = "invalid-date";
|
||||
|
||||
render(<MaintenanceBanner startTime={invalidTime} />);
|
||||
|
||||
// Should still render the banner with the original string
|
||||
expect(screen.getByText(/Scheduled maintenance will begin at invalid-date/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("formats ISO date string correctly", () => {
|
||||
const isoTime = "2024-01-15T15:30:00.000Z";
|
||||
|
||||
render(<MaintenanceBanner startTime={isoTime} />);
|
||||
|
||||
// Should render the banner (exact time format will depend on user's timezone)
|
||||
expect(screen.getByText(/Scheduled maintenance will begin at/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -73,4 +73,73 @@ describe("TrajectoryActions", () => {
|
||||
|
||||
expect(onExportTrajectory).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("SaaS mode", () => {
|
||||
it("should only render export button when isSaasMode is true", () => {
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
onExportTrajectory={onExportTrajectory}
|
||||
isSaasMode={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
const actions = screen.getByTestId("feedback-actions");
|
||||
|
||||
// Should not render feedback buttons in SaaS mode
|
||||
expect(within(actions).queryByTestId("positive-feedback")).toBeNull();
|
||||
expect(within(actions).queryByTestId("negative-feedback")).toBeNull();
|
||||
|
||||
// Should still render export button
|
||||
within(actions).getByTestId("export-trajectory");
|
||||
});
|
||||
|
||||
it("should render all buttons when isSaasMode is false", () => {
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
onExportTrajectory={onExportTrajectory}
|
||||
isSaasMode={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const actions = screen.getByTestId("feedback-actions");
|
||||
within(actions).getByTestId("positive-feedback");
|
||||
within(actions).getByTestId("negative-feedback");
|
||||
within(actions).getByTestId("export-trajectory");
|
||||
});
|
||||
|
||||
it("should render all buttons when isSaasMode is undefined (default behavior)", () => {
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
onExportTrajectory={onExportTrajectory}
|
||||
/>,
|
||||
);
|
||||
|
||||
const actions = screen.getByTestId("feedback-actions");
|
||||
within(actions).getByTestId("positive-feedback");
|
||||
within(actions).getByTestId("negative-feedback");
|
||||
within(actions).getByTestId("export-trajectory");
|
||||
});
|
||||
|
||||
it("should call onExportTrajectory when export button is clicked in SaaS mode", async () => {
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
onExportTrajectory={onExportTrajectory}
|
||||
isSaasMode={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
const exportButton = screen.getByTestId("export-trajectory");
|
||||
await user.click(exportButton);
|
||||
|
||||
expect(onExportTrajectory).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,10 +222,12 @@ describe("HomeScreen", () => {
|
||||
// All other buttons should be disabled when the header button is clicked
|
||||
await userEvent.click(headerLaunchButton);
|
||||
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
await waitFor(() => {
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -240,10 +242,12 @@ describe("HomeScreen", () => {
|
||||
// All other buttons should be disabled when the repo button is clicked
|
||||
await userEvent.click(repoLaunchButton);
|
||||
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
await waitFor(() => {
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -258,10 +262,12 @@ describe("HomeScreen", () => {
|
||||
// All other buttons should be disabled when the task button is clicked
|
||||
await userEvent.click(tasksLaunchButtons[0]);
|
||||
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
await waitFor(() => {
|
||||
expect(headerLaunchButton).toBeDisabled();
|
||||
expect(repoLaunchButton).toBeDisabled();
|
||||
tasksLaunchButtonsAfter.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -366,17 +366,17 @@ describe("Form submission", () => {
|
||||
|
||||
renderLlmSettingsScreen();
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
screen.getByTestId("llm-settings-form-advanced");
|
||||
await screen.findByTestId("llm-settings-form-advanced");
|
||||
|
||||
const submitButton = screen.getByTestId("submit-button");
|
||||
const submitButton = await screen.findByTestId("submit-button");
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
const model = screen.getByTestId("llm-custom-model-input");
|
||||
const baseUrl = screen.getByTestId("base-url-input");
|
||||
const apiKey = screen.getByTestId("llm-api-key-input");
|
||||
const agent = screen.getByTestId("agent-input");
|
||||
const confirmation = screen.getByTestId("enable-confirmation-mode-switch");
|
||||
const condensor = screen.getByTestId("enable-memory-condenser-switch");
|
||||
const model = await screen.findByTestId("llm-custom-model-input");
|
||||
const baseUrl = await screen.findByTestId("base-url-input");
|
||||
const apiKey = await screen.findByTestId("llm-api-key-input");
|
||||
const agent = await screen.findByTestId("agent-input");
|
||||
const confirmation = await screen.findByTestId("enable-confirmation-mode-switch");
|
||||
const condensor = await screen.findByTestId("enable-memory-condenser-switch");
|
||||
|
||||
// enter custom model
|
||||
await userEvent.type(model, "-mini");
|
||||
@@ -449,7 +449,7 @@ describe("Form submission", () => {
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// select security analyzer
|
||||
const securityAnalyzer = screen.getByTestId("security-analyzer-input");
|
||||
const securityAnalyzer = await screen.findByTestId("security-analyzer-input");
|
||||
await userEvent.click(securityAnalyzer);
|
||||
const securityAnalyzerOption = screen.getByText("mock-invariant");
|
||||
await userEvent.click(securityAnalyzerOption);
|
||||
|
||||
2542
frontend/package-lock.json
generated
2542
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,48 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.49.0",
|
||||
"version": "0.51.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.1",
|
||||
"@heroui/react": "^2.8.2",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@react-types/shared": "^3.29.1",
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"@react-types/shared": "^3.31.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@stripe/react-stripe-js": "^3.8.1",
|
||||
"@stripe/stripe-js": "^7.7.0",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.10.0",
|
||||
"axios": "^1.11.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.23.6",
|
||||
"framer-motion": "^12.23.11",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"isbot": "^5.1.28",
|
||||
"isbot": "^5.1.29",
|
||||
"jose": "^6.0.12",
|
||||
"lucide-react": "^0.525.0",
|
||||
"lucide-react": "^0.533.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.257.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"posthog-js": "^1.258.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.5.1",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-i18next": "^15.6.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.7.0",
|
||||
"react-router": "^7.7.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"remark-breaks": "^4.0.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vite": "^7.0.5",
|
||||
"vite": "^7.0.6",
|
||||
"web-vitals": "^5.0.3",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
@@ -82,19 +82,19 @@
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/traverse": "^7.28.0",
|
||||
"@babel/types": "^7.28.1",
|
||||
"@babel/types": "^7.28.2",
|
||||
"@mswjs/socket.io-binding": "^0.2.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@react-router/dev": "^7.7.0",
|
||||
"@react-router/dev": "^7.7.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.81.2",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.6.4",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^24.0.15",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/react": "^19.1.9",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/ws": "^8.18.1",
|
||||
@@ -102,7 +102,7 @@
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitest/coverage-v8": "^3.2.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "^10.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
GitChange,
|
||||
GetMicroagentsResponse,
|
||||
GetMicroagentPromptResponse,
|
||||
CreateMicroagent,
|
||||
} from "./open-hands.types";
|
||||
import { openHands } from "./open-hands-axios";
|
||||
import { ApiSettings, PostApiSettings, Provider } from "#/types/settings";
|
||||
@@ -251,6 +252,28 @@ class OpenHands {
|
||||
return data.results;
|
||||
}
|
||||
|
||||
static async searchConversations(
|
||||
selectedRepository?: string,
|
||||
conversationTrigger?: string,
|
||||
limit: number = 20,
|
||||
): Promise<Conversation[]> {
|
||||
const params = new URLSearchParams();
|
||||
params.append("limit", limit.toString());
|
||||
|
||||
if (selectedRepository) {
|
||||
params.append("selected_repository", selectedRepository);
|
||||
}
|
||||
|
||||
if (conversationTrigger) {
|
||||
params.append("conversation_trigger", conversationTrigger);
|
||||
}
|
||||
|
||||
const { data } = await openHands.get<ResultSet<Conversation>>(
|
||||
`/api/conversations?${params.toString()}`,
|
||||
);
|
||||
return data.results;
|
||||
}
|
||||
|
||||
static async deleteUserConversation(conversationId: string): Promise<void> {
|
||||
await openHands.delete(`/api/conversations/${conversationId}`);
|
||||
}
|
||||
@@ -262,6 +285,7 @@ class OpenHands {
|
||||
suggested_task?: SuggestedTask,
|
||||
selected_branch?: string,
|
||||
conversationInstructions?: string,
|
||||
createMicroagent?: CreateMicroagent,
|
||||
): Promise<Conversation> {
|
||||
const body = {
|
||||
repository: selectedRepository,
|
||||
@@ -270,6 +294,7 @@ class OpenHands {
|
||||
initial_user_msg: initialUserMsg,
|
||||
suggested_task,
|
||||
conversation_instructions: conversationInstructions,
|
||||
create_microagent: createMicroagent,
|
||||
};
|
||||
|
||||
const { data } = await openHands.post<Conversation>(
|
||||
|
||||
@@ -56,6 +56,9 @@ export interface GetConfigResponse {
|
||||
HIDE_LLM_SETTINGS: boolean;
|
||||
HIDE_MICROAGENT_MANAGEMENT?: boolean;
|
||||
};
|
||||
MAINTENANCE?: {
|
||||
startTime: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetVSCodeUrlResponse {
|
||||
@@ -79,7 +82,11 @@ export interface RepositorySelection {
|
||||
git_provider: Provider | null;
|
||||
}
|
||||
|
||||
export type ConversationTrigger = "resolver" | "gui" | "suggested_task";
|
||||
export type ConversationTrigger =
|
||||
| "resolver"
|
||||
| "gui"
|
||||
| "suggested_task"
|
||||
| "microagent_management";
|
||||
|
||||
export interface Conversation {
|
||||
conversation_id: string;
|
||||
@@ -94,6 +101,7 @@ export interface Conversation {
|
||||
trigger?: ConversationTrigger;
|
||||
url: string | null;
|
||||
session_api_key: string | null;
|
||||
pr_number?: number[] | null;
|
||||
}
|
||||
|
||||
export interface ResultSet<T> {
|
||||
@@ -133,3 +141,9 @@ export interface GetMicroagentPromptResponse {
|
||||
status: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export interface CreateMicroagent {
|
||||
repo: string;
|
||||
git_provider?: Provider;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@@ -232,17 +232,16 @@ export function ChatInterface() {
|
||||
|
||||
<div className="flex flex-col gap-[6px] px-4 pb-4">
|
||||
<div className="flex justify-between relative">
|
||||
{config?.APP_MODE !== "saas" && (
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={() =>
|
||||
onClickShareFeedbackActionButton("positive")
|
||||
}
|
||||
onNegativeFeedback={() =>
|
||||
onClickShareFeedbackActionButton("negative")
|
||||
}
|
||||
onExportTrajectory={() => onClickExportTrajectoryButton()}
|
||||
/>
|
||||
)}
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={() =>
|
||||
onClickShareFeedbackActionButton("positive")
|
||||
}
|
||||
onNegativeFeedback={() =>
|
||||
onClickShareFeedbackActionButton("negative")
|
||||
}
|
||||
onExportTrajectory={() => onClickExportTrajectoryButton()}
|
||||
isSaasMode={config?.APP_MODE === "saas"}
|
||||
/>
|
||||
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 bottom-0">
|
||||
{curAgentState === AgentState.RUNNING && <TypingIndicator />}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SettingsDropdownInput } from "../../settings/settings-dropdown-input";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
export interface BranchDropdownProps {
|
||||
items: { key: React.Key; label: string }[];
|
||||
@@ -9,6 +10,8 @@ export interface BranchDropdownProps {
|
||||
onInputChange: (value: string) => void;
|
||||
isDisabled: boolean;
|
||||
selectedKey?: string;
|
||||
wrapperClassName?: string;
|
||||
label?: ReactNode;
|
||||
}
|
||||
|
||||
export function BranchDropdown({
|
||||
@@ -17,6 +20,8 @@ export function BranchDropdown({
|
||||
onInputChange,
|
||||
isDisabled,
|
||||
selectedKey,
|
||||
wrapperClassName,
|
||||
label,
|
||||
}: BranchDropdownProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -26,11 +31,12 @@ export function BranchDropdown({
|
||||
name="branch-dropdown"
|
||||
placeholder={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
items={items}
|
||||
wrapperClassName="max-w-[500px]"
|
||||
wrapperClassName={cn("max-w-[500px]", wrapperClassName)}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
isDisabled={isDisabled}
|
||||
selectedKey={selectedKey}
|
||||
label={label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
export function BranchErrorState() {
|
||||
interface BranchErrorStateProps {
|
||||
wrapperClassName?: string;
|
||||
}
|
||||
|
||||
export function BranchErrorState({ wrapperClassName }: BranchErrorStateProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
data-testid="branch-dropdown-error"
|
||||
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm text-red-500"
|
||||
className={cn(
|
||||
"flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm text-red-500",
|
||||
wrapperClassName,
|
||||
)}
|
||||
>
|
||||
<span className="text-sm">{t("HOME$FAILED_TO_LOAD_BRANCHES")}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
export function BranchLoadingState() {
|
||||
interface BranchLoadingStateProps {
|
||||
wrapperClassName?: string;
|
||||
}
|
||||
|
||||
export function BranchLoadingState({
|
||||
wrapperClassName,
|
||||
}: BranchLoadingStateProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
data-testid="branch-dropdown-loading"
|
||||
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm"
|
||||
className={cn(
|
||||
"flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm",
|
||||
wrapperClassName,
|
||||
)}
|
||||
>
|
||||
<Spinner size="sm" />
|
||||
<span className="text-sm">{t("HOME$LOADING_BRANCHES")}</span>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaTriangleExclamation } from "react-icons/fa6";
|
||||
|
||||
interface MaintenanceBannerProps {
|
||||
startTime: string;
|
||||
}
|
||||
|
||||
export function MaintenanceBanner({ startTime }: MaintenanceBannerProps) {
|
||||
const { t } = useTranslation();
|
||||
// Convert EST timestamp to user's local timezone
|
||||
const formatMaintenanceTime = (estTimeString: string): string => {
|
||||
try {
|
||||
// Parse the EST timestamp
|
||||
// If the string doesn't include timezone info, assume it's EST
|
||||
let dateToFormat: Date;
|
||||
|
||||
if (
|
||||
estTimeString.includes("T") &&
|
||||
(estTimeString.includes("-05:00") ||
|
||||
estTimeString.includes("-04:00") ||
|
||||
estTimeString.includes("EST") ||
|
||||
estTimeString.includes("EDT"))
|
||||
) {
|
||||
// Already has timezone info
|
||||
dateToFormat = new Date(estTimeString);
|
||||
} else {
|
||||
// Assume EST and convert to UTC for proper parsing
|
||||
// EST is UTC-5, EDT is UTC-4, but we'll assume EST for simplicity
|
||||
const estDate = new Date(estTimeString);
|
||||
if (Number.isNaN(estDate.getTime())) {
|
||||
throw new Error("Invalid date");
|
||||
}
|
||||
dateToFormat = estDate;
|
||||
}
|
||||
|
||||
// Format to user's local timezone
|
||||
return dateToFormat.toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZoneName: "short",
|
||||
});
|
||||
} catch (error) {
|
||||
// Fallback to original string if parsing fails
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Failed to parse maintenance time:", error);
|
||||
return estTimeString;
|
||||
}
|
||||
};
|
||||
|
||||
const localTime = formatMaintenanceTime(startTime);
|
||||
|
||||
return (
|
||||
<div className="bg-primary text-[#0D0F11] p-4 rounded">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<FaTriangleExclamation className="text-white align-middle" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium">
|
||||
{t("MAINTENANCE$SCHEDULED_MESSAGE", { time: localTime })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GitProviderIcon } from "#/components/shared/git-provider-icon";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
|
||||
interface MicroagentManagementAccordionTitleProps {
|
||||
repository: GitRepository;
|
||||
@@ -13,14 +14,17 @@ export function MicroagentManagementAccordionTitle({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<GitProviderIcon gitProvider={repository.git_provider} />
|
||||
<div
|
||||
className="text-white text-base font-normal truncate max-w-[150px]"
|
||||
title={repository.full_name}
|
||||
<TooltipButton
|
||||
tooltip={repository.full_name}
|
||||
ariaLabel={repository.full_name}
|
||||
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[232px]"
|
||||
testId="repository-name-tooltip"
|
||||
placement="bottom"
|
||||
>
|
||||
{repository.full_name}
|
||||
</div>
|
||||
<span>{repository.full_name}</span>
|
||||
</TooltipButton>
|
||||
</div>
|
||||
<MicroagentManagementAddMicroagentButton />
|
||||
<MicroagentManagementAddMicroagentButton repository={repository} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { setAddMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
import {
|
||||
setAddMicroagentModalVisible,
|
||||
setSelectedRepository,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { RootState } from "#/store";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import PlusIcon from "#/icons/plus.svg?react";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
|
||||
export function MicroagentManagementAddMicroagentButton() {
|
||||
interface MicroagentManagementAddMicroagentButtonProps {
|
||||
repository: GitRepository;
|
||||
}
|
||||
|
||||
export function MicroagentManagementAddMicroagentButton({
|
||||
repository,
|
||||
}: MicroagentManagementAddMicroagentButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { addMicroagentModalVisible } = useSelector(
|
||||
@@ -13,18 +25,23 @@ export function MicroagentManagementAddMicroagentButton() {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setAddMicroagentModalVisible(!addMicroagentModalVisible));
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm font-normal text-[#8480FF] cursor-pointer"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
</button>
|
||||
<div onClick={handleClick}>
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
ariaLabel={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
className="p-0 min-w-0 h-6 w-6 flex items-center justify-center bg-transparent cursor-pointer"
|
||||
testId="add-microagent-button"
|
||||
placement="bottom"
|
||||
>
|
||||
<PlusIcon width={22} height={22} />
|
||||
</TooltipButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaCircleInfo } from "react-icons/fa6";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import XIcon from "#/icons/x.svg?react";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { BadgeInput } from "#/components/shared/inputs/badge-input";
|
||||
|
||||
interface MicroagentManagementAddMicroagentModalProps {
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function MicroagentManagementAddMicroagentModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: MicroagentManagementAddMicroagentModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [triggers, setTriggers] = useState<string[]>([]);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const modalTitle = selectedRepository
|
||||
? `${t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO)} ${selectedRepository}`
|
||||
: t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="items-start rounded-[12px] p-6 min-w-[611px]">
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-white text-xl font-medium">{modalTitle}</h2>
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-overview#microagents-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<button type="button" onClick={onCancel} className="cursor-pointer">
|
||||
<XIcon width={24} height={24} color="#F9FBFE" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-white text-sm font-normal">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION)}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
data-testid="add-microagent-modal"
|
||||
onSubmit={onSubmit}
|
||||
className="flex flex-col gap-6 w-full"
|
||||
>
|
||||
<label
|
||||
htmlFor="query-input"
|
||||
className="flex flex-col gap-2 w-full text-sm font-normal"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$WHAT_TO_DO)}
|
||||
<textarea
|
||||
required
|
||||
data-testid="query-input"
|
||||
name="query-input"
|
||||
placeholder={t(I18nKey.MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO)}
|
||||
rows={6}
|
||||
className={cn(
|
||||
"bg-tertiary border border-[#717888] bg-[#454545] w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt resize-none",
|
||||
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center gap-2 text-[11px] font-normal text-white leading-[16px]">
|
||||
<span className="font-semibold">
|
||||
{t(I18nKey.COMMON$FOR_EXAMPLE)}:
|
||||
</span>
|
||||
<span className="underline">
|
||||
{t(I18nKey.COMMON$TEST_DB_MIGRATION)}
|
||||
</span>
|
||||
<span className="underline">{t(I18nKey.COMMON$RUN_TEST)}</span>
|
||||
<span className="underline">{t(I18nKey.COMMON$RUN_APP)}</span>
|
||||
<span className="underline">
|
||||
{t(I18nKey.COMMON$LEARN_FILE_STRUCTURE)}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="trigger-input"
|
||||
className="flex flex-col gap-2.5 w-full text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$ADD_TRIGGERS)}
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-keyword"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<BadgeInput
|
||||
name="trigger-input"
|
||||
value={triggers}
|
||||
placeholder={t("MICROAGENT$TYPE_TRIGGER_SPACE")}
|
||||
onChange={setTriggers}
|
||||
/>
|
||||
<span className="text-xs text-[#ffffff80] font-normal">
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$HELP_TEXT_DESCRIBING_VALID_TRIGGERS,
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
<div
|
||||
className="flex items-center justify-end gap-2 w-full"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={onConfirm}
|
||||
data-testid="confirm-button"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT$LAUNCH)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { MicroagentManagementSidebar } from "./microagent-management-sidebar";
|
||||
import { MicroagentManagementMain } from "./microagent-management-main";
|
||||
import { MicroagentManagementUpsertMicroagentModal } from "./microagent-management-upsert-microagent-modal";
|
||||
import { RootState } from "#/store";
|
||||
import {
|
||||
setAddMicroagentModalVisible,
|
||||
setUpdateMicroagentModalVisible,
|
||||
setLearnThisRepoModalVisible,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { useCreateConversationAndSubscribeMultiple } from "#/hooks/use-create-conversation-and-subscribe-multiple";
|
||||
import {
|
||||
LearnThisRepoFormData,
|
||||
MicroagentFormData,
|
||||
} from "#/types/microagent-management";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { getPR, getProviderName, getPRShort } from "#/utils/utils";
|
||||
import {
|
||||
isOpenHandsEvent,
|
||||
isAgentStateChangeObservation,
|
||||
isFinishAction,
|
||||
} from "#/types/core/guards";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { queryClient } from "#/query-client-config";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { MicroagentManagementLearnThisRepoModal } from "./microagent-management-learn-this-repo-modal";
|
||||
|
||||
// Handle error events
|
||||
const isErrorEvent = (evt: unknown): evt is { error: true; message: string } =>
|
||||
typeof evt === "object" &&
|
||||
evt !== null &&
|
||||
"error" in evt &&
|
||||
evt.error === true;
|
||||
|
||||
const isAgentStatusError = (evt: unknown): boolean =>
|
||||
isOpenHandsEvent(evt) &&
|
||||
isAgentStateChangeObservation(evt) &&
|
||||
evt.extras.agent_state === AgentState.ERROR;
|
||||
|
||||
const shouldInvalidateConversationsList = (currentSocketEvent: unknown) => {
|
||||
const hasError =
|
||||
isErrorEvent(currentSocketEvent) || isAgentStatusError(currentSocketEvent);
|
||||
const hasStateChanged =
|
||||
isOpenHandsEvent(currentSocketEvent) &&
|
||||
isAgentStateChangeObservation(currentSocketEvent);
|
||||
const hasFinished =
|
||||
isOpenHandsEvent(currentSocketEvent) && isFinishAction(currentSocketEvent);
|
||||
|
||||
return hasError || hasStateChanged || hasFinished;
|
||||
};
|
||||
|
||||
const getConversationInstructions = (
|
||||
repositoryName: string,
|
||||
formData: MicroagentFormData,
|
||||
pr: string,
|
||||
prShort: string,
|
||||
gitProvider: Provider,
|
||||
) => `Create a microagent for the repository ${repositoryName} by following the steps below:
|
||||
|
||||
- Step 1: Create a markdown file inside the .openhands/microagents folder with the name of the microagent (The microagent must be created in the .openhands/microagents folder and should be able to perform the described task when triggered).
|
||||
|
||||
- This is the instructions about what the microagent should do: ${formData.query}
|
||||
|
||||
${
|
||||
formData.triggers && formData.triggers.length > 0
|
||||
? `
|
||||
- This is the triggers of the microagent: ${formData.triggers.join(", ")}
|
||||
`
|
||||
: "- Please be noted that the microagent doesn't have any triggers."
|
||||
}
|
||||
|
||||
- Step 2: Create a new branch for the repository ${repositoryName}, must avoid duplicated branches.
|
||||
|
||||
- Step 3: Please push the changes to your branch on ${getProviderName(gitProvider)} and create a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.
|
||||
`;
|
||||
|
||||
const getUpdateConversationInstructions = (
|
||||
repositoryName: string,
|
||||
formData: MicroagentFormData,
|
||||
pr: string,
|
||||
prShort: string,
|
||||
gitProvider: Provider,
|
||||
) => `Update the microagent for the repository ${repositoryName} by following the steps below:
|
||||
|
||||
|
||||
- Step 1: Update the microagent. This is the path of the microagent: ${formData.microagentPath} (The updated microagent must be in the .openhands/microagents folder and should be able to perform the described task when triggered).
|
||||
|
||||
- This is the updated instructions about what the microagent should do: ${formData.query}
|
||||
|
||||
${
|
||||
formData.triggers && formData.triggers.length > 0
|
||||
? `
|
||||
- This is the triggers of the microagent: ${formData.triggers.join(", ")}
|
||||
`
|
||||
: "- Please be noted that the microagent doesn't have any triggers."
|
||||
}
|
||||
|
||||
- Step 2: Create a new branch for the repository ${repositoryName}, must avoid duplicated branches.
|
||||
|
||||
- Step 3: Please push the changes to your branch on ${getProviderName(gitProvider)} and create a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.
|
||||
`;
|
||||
|
||||
export function MicroagentManagementContent() {
|
||||
// Responsive width state
|
||||
const [width, setWidth] = useState(window.innerWidth);
|
||||
|
||||
const {
|
||||
addMicroagentModalVisible,
|
||||
updateMicroagentModalVisible,
|
||||
selectedRepository,
|
||||
learnThisRepoModalVisible,
|
||||
} = useSelector((state: RootState) => state.microagentManagement);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { createConversationAndSubscribe, isPending } =
|
||||
useCreateConversationAndSubscribeMultiple();
|
||||
|
||||
function handleResize() {
|
||||
setWidth(window.innerWidth);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const hideUpsertMicroagentModal = (isUpdate: boolean = false) => {
|
||||
if (isUpdate) {
|
||||
dispatch(setUpdateMicroagentModalVisible(false));
|
||||
} else {
|
||||
dispatch(setAddMicroagentModalVisible(false));
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to invalidate conversations list for a repository
|
||||
const invalidateConversationsList = React.useCallback(
|
||||
(repositoryName: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
"conversations",
|
||||
"search",
|
||||
repositoryName,
|
||||
"microagent_management",
|
||||
],
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleMicroagentEvent = React.useCallback(
|
||||
(socketEvent: unknown) => {
|
||||
// Get repository name from selectedRepository for invalidation
|
||||
const repositoryName =
|
||||
selectedRepository && typeof selectedRepository === "object"
|
||||
? (selectedRepository as GitRepository).full_name
|
||||
: "";
|
||||
|
||||
if (shouldInvalidateConversationsList(socketEvent)) {
|
||||
invalidateConversationsList(repositoryName);
|
||||
}
|
||||
},
|
||||
[invalidateConversationsList, selectedRepository],
|
||||
);
|
||||
|
||||
const handleUpsertMicroagent = (
|
||||
formData: MicroagentFormData,
|
||||
isUpdate: boolean = false,
|
||||
) => {
|
||||
if (!selectedRepository || typeof selectedRepository !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the GitRepository properties
|
||||
const repository = selectedRepository as GitRepository;
|
||||
const repositoryName = repository.full_name;
|
||||
const gitProvider = repository.git_provider;
|
||||
|
||||
const isGitLab = gitProvider === "gitlab";
|
||||
|
||||
const pr = getPR(isGitLab);
|
||||
const prShort = getPRShort(isGitLab);
|
||||
|
||||
// Create conversation instructions for microagent generation or update
|
||||
const conversationInstructions = isUpdate
|
||||
? getUpdateConversationInstructions(
|
||||
repositoryName,
|
||||
formData,
|
||||
pr,
|
||||
prShort,
|
||||
gitProvider,
|
||||
)
|
||||
: getConversationInstructions(
|
||||
repositoryName,
|
||||
formData,
|
||||
pr,
|
||||
prShort,
|
||||
gitProvider,
|
||||
);
|
||||
|
||||
// Create the CreateMicroagent object
|
||||
const createMicroagent = {
|
||||
repo: repositoryName,
|
||||
git_provider: gitProvider,
|
||||
title: formData.query,
|
||||
};
|
||||
|
||||
createConversationAndSubscribe({
|
||||
query: conversationInstructions,
|
||||
conversationInstructions,
|
||||
repository: {
|
||||
name: repositoryName,
|
||||
branch: formData.selectedBranch,
|
||||
gitProvider,
|
||||
},
|
||||
createMicroagent,
|
||||
onSuccessCallback: () => {
|
||||
// Invalidate conversations list to fetch the latest conversations for this repository
|
||||
invalidateConversationsList(repositoryName);
|
||||
|
||||
// Also invalidate microagents list to fetch the latest microagents
|
||||
// Extract owner and repo from full_name (format: "owner/repo")
|
||||
const [owner, repo] = repositoryName.split("/");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["repository-microagents", owner, repo],
|
||||
});
|
||||
|
||||
hideUpsertMicroagentModal(isUpdate);
|
||||
},
|
||||
onEventCallback: (event: unknown) => {
|
||||
// Handle conversation events for real-time status updates
|
||||
handleMicroagentEvent(event);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const hideLearnThisRepoModal = () => {
|
||||
dispatch(setLearnThisRepoModalVisible(false));
|
||||
};
|
||||
|
||||
const handleLearnThisRepoConfirm = (formData: LearnThisRepoFormData) => {
|
||||
if (!selectedRepository || typeof selectedRepository !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
const repository = selectedRepository as GitRepository;
|
||||
const repositoryName = repository.full_name;
|
||||
const gitProvider = repository.git_provider;
|
||||
|
||||
// Launch a new conversation to help the user understand the repo
|
||||
createConversationAndSubscribe({
|
||||
query: formData.query,
|
||||
conversationInstructions: formData.query,
|
||||
repository: {
|
||||
name: repositoryName,
|
||||
branch: formData.selectedBranch,
|
||||
gitProvider,
|
||||
},
|
||||
onSuccessCallback: () => {
|
||||
hideLearnThisRepoModal();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderModals = () => (
|
||||
<>
|
||||
{(addMicroagentModalVisible || updateMicroagentModalVisible) && (
|
||||
<MicroagentManagementUpsertMicroagentModal
|
||||
onConfirm={(formData) =>
|
||||
handleUpsertMicroagent(formData, updateMicroagentModalVisible)
|
||||
}
|
||||
onCancel={() =>
|
||||
hideUpsertMicroagentModal(updateMicroagentModalVisible)
|
||||
}
|
||||
isLoading={isPending}
|
||||
isUpdate={updateMicroagentModalVisible}
|
||||
/>
|
||||
)}
|
||||
{learnThisRepoModalVisible && (
|
||||
<MicroagentManagementLearnThisRepoModal
|
||||
onCancel={hideLearnThisRepoModal}
|
||||
onConfirm={handleLearnThisRepoConfirm}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (width < 1024) {
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col gap-6">
|
||||
<div className="w-full rounded-lg border border-[#525252] bg-[#24272E] max-h-[494px] min-h-[494px]">
|
||||
<MicroagentManagementSidebar isSmallerScreen />
|
||||
</div>
|
||||
<div className="w-full rounded-lg border border-[#525252] bg-[#24272E] flex-1 min-h-[494px]">
|
||||
<MicroagentManagementMain />
|
||||
</div>
|
||||
{renderModals()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex rounded-lg border border-[#525252] bg-[#24272E] overflow-hidden">
|
||||
<MicroagentManagementSidebar />
|
||||
<div className="flex-1">
|
||||
<MicroagentManagementMain />
|
||||
</div>
|
||||
{renderModals()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { Loader } from "#/components/shared/loader";
|
||||
|
||||
export function MicroagentManagementConversationStopped() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { conversation } = selectedMicroagentItem ?? {};
|
||||
|
||||
const { conversation_id: conversationId } = conversation ?? {};
|
||||
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#ffffff99] text-[22px] font-bold pb-[22px] text-center max-w-[455px]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED)}
|
||||
</div>
|
||||
<Loader size="small" className="pb-[22px]" />
|
||||
<a
|
||||
href={`/conversations/${conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
testId="view-conversation-button"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT$VIEW_CONVERSATION)}
|
||||
</BrandButton>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export function MicroagentManagementDefault() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#F9FBFE] text-xl font-bold pb-4">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT)}
|
||||
</div>
|
||||
<div className="text-white text-sm font-normal text-center max-w-[455px]">
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { Loader } from "#/components/shared/loader";
|
||||
|
||||
export function MicroagentManagementError() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { conversation } = selectedMicroagentItem ?? {};
|
||||
|
||||
const { conversation_id: conversationId } = conversation ?? {};
|
||||
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#ffffff99] text-[22px] font-bold pb-[22px] text-center max-w-[455px]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$ERROR)}
|
||||
</div>
|
||||
<Loader size="small" className="pb-[22px]" />
|
||||
<a
|
||||
href={`/conversations/${conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
testId="view-conversation-button"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT$VIEW_CONVERSATION)}
|
||||
</BrandButton>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaCircleInfo } from "react-icons/fa6";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import XIcon from "#/icons/x.svg?react";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { LearnThisRepoFormData } from "#/types/microagent-management";
|
||||
import { Branch } from "#/types/git";
|
||||
import { useRepositoryBranches } from "#/hooks/query/use-repository-branches";
|
||||
import {
|
||||
BranchDropdown,
|
||||
BranchLoadingState,
|
||||
BranchErrorState,
|
||||
} from "../home/repository-selection";
|
||||
|
||||
interface MicroagentManagementLearnThisRepoModalProps {
|
||||
onConfirm: (formData: LearnThisRepoFormData) => void;
|
||||
onCancel: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementLearnThisRepoModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
}: MicroagentManagementLearnThisRepoModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
// Add a ref to track if the branch was manually cleared by the user
|
||||
const branchManuallyClearedRef = useRef<boolean>(false);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isLoadingBranches,
|
||||
isError: isBranchesError,
|
||||
} = useRepositoryBranches(selectedRepository?.full_name || null);
|
||||
|
||||
const branchesItems = branches?.map((branch) => ({
|
||||
key: branch.name,
|
||||
label: branch.name,
|
||||
}));
|
||||
|
||||
// Auto-select main or master branch if it exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
branches &&
|
||||
branches.length > 0 &&
|
||||
!selectedBranch &&
|
||||
!isLoadingBranches
|
||||
) {
|
||||
// Look for main or master branch
|
||||
const mainBranch = branches.find((branch) => branch.name === "main");
|
||||
const masterBranch = branches.find((branch) => branch.name === "master");
|
||||
|
||||
// Select main if it exists, otherwise select master if it exists
|
||||
if (mainBranch) {
|
||||
setSelectedBranch(mainBranch);
|
||||
} else if (masterBranch) {
|
||||
setSelectedBranch(masterBranch);
|
||||
}
|
||||
}
|
||||
}, [branches, isLoadingBranches, selectedBranch]);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleBranchSelection = (key: React.Key | null) => {
|
||||
const selectedBranchObj = branches?.find((branch) => branch.name === key);
|
||||
setSelectedBranch(selectedBranchObj || null);
|
||||
// Reset the manually cleared flag when a branch is explicitly selected
|
||||
branchManuallyClearedRef.current = false;
|
||||
};
|
||||
|
||||
const handleBranchInputChange = (value: string) => {
|
||||
// Clear the selected branch if the input is empty or contains only whitespace
|
||||
// This fixes the issue where users can't delete the entire default branch name
|
||||
if (value === "" || value.trim() === "") {
|
||||
setSelectedBranch(null);
|
||||
// Set the flag to indicate that the branch was manually cleared
|
||||
branchManuallyClearedRef.current = true;
|
||||
} else {
|
||||
// Reset the flag when the user starts typing again
|
||||
branchManuallyClearedRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the appropriate UI for branch selector based on the loading/error state
|
||||
const renderBranchSelector = () => {
|
||||
if (!selectedRepository) {
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={[]}
|
||||
onSelectionChange={() => {}}
|
||||
onInputChange={() => {}}
|
||||
isDisabled
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoadingBranches) {
|
||||
return <BranchLoadingState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
if (isBranchesError) {
|
||||
return <BranchErrorState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={branchesItems || []}
|
||||
onSelectionChange={handleBranchSelection}
|
||||
onInputChange={handleBranchInputChange}
|
||||
isDisabled={false}
|
||||
selectedKey={selectedBranch?.name}
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop onClose={onCancel}>
|
||||
<ModalBody
|
||||
className="items-start rounded-[12px] p-6 min-w-[611px]"
|
||||
data-testid="learn-this-repo-modal"
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2
|
||||
className="text-white text-xl font-medium"
|
||||
data-testid="modal-title"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE)}
|
||||
</h2>
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-overview#microagents-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-testid="modal-info-link"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="cursor-pointer"
|
||||
data-testid="modal-close-button"
|
||||
>
|
||||
<XIcon width={24} height={24} color="#F9FBFE" />
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
className="text-white text-sm font-normal"
|
||||
data-testid="modal-description"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION)}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
data-testid="learn-this-repo-form"
|
||||
onSubmit={onSubmit}
|
||||
className="flex flex-col gap-6 w-full"
|
||||
>
|
||||
<div data-testid="branch-selector-container">
|
||||
{renderBranchSelector()}
|
||||
</div>
|
||||
<label
|
||||
htmlFor="query-input"
|
||||
className="flex flex-col gap-2 w-full text-sm font-normal"
|
||||
>
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO,
|
||||
)}
|
||||
<textarea
|
||||
required
|
||||
data-testid="query-input"
|
||||
name="query-input"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO,
|
||||
)}
|
||||
rows={6}
|
||||
className={cn(
|
||||
"bg-tertiary border border-[#717888] bg-[#454545] w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt resize-none",
|
||||
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
</form>
|
||||
<div
|
||||
className="flex items-center justify-end gap-2 w-full"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
data-testid="modal-actions"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
testId="cancel-button"
|
||||
>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleConfirm}
|
||||
testId="confirm-button"
|
||||
isDisabled={
|
||||
!query.trim() ||
|
||||
isLoading ||
|
||||
isLoadingBranches ||
|
||||
!selectedBranch ||
|
||||
isBranchesError
|
||||
}
|
||||
>
|
||||
{isLoading || isLoadingBranches
|
||||
? t(I18nKey.HOME$LOADING)
|
||||
: t(I18nKey.MICROAGENT$LAUNCH)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +1,36 @@
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import {
|
||||
setLearnThisRepoModalVisible,
|
||||
setSelectedRepository,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { GitRepository } from "#/types/git";
|
||||
|
||||
interface MicroagentManagementLearnThisRepoProps {
|
||||
repositoryUrl: string;
|
||||
repository: GitRepository;
|
||||
}
|
||||
|
||||
export function MicroagentManagementLearnThisRepo({
|
||||
repositoryUrl,
|
||||
repository,
|
||||
}: MicroagentManagementLearnThisRepoProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = () => {
|
||||
dispatch(setLearnThisRepoModalVisible(true));
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-lg bg-[#ffffff0d] border border-dashed border-[#ffffff4d] p-4 hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300 cursor-pointer">
|
||||
<a
|
||||
className="text-[16px] font-normal text-[#8480FF]"
|
||||
href={repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center rounded-lg bg-[#ffffff0d] border border-dashed border-[#ffffff4d] p-4 hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300 cursor-pointer"
|
||||
onClick={handleClick}
|
||||
data-testid="learn-this-repo-trigger"
|
||||
>
|
||||
<span className="text-[16px] font-normal text-[#8480FF]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO)}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,52 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { MicroagentManagementDefault } from "./microagent-management-default";
|
||||
import { MicroagentManagementOpeningPr } from "./microagent-management-opening-pr";
|
||||
import { MicroagentManagementReviewPr } from "./microagent-management-review-pr";
|
||||
import { MicroagentManagementViewMicroagent } from "./microagent-management-view-microagent";
|
||||
import { MicroagentManagementError } from "./microagent-management-error";
|
||||
import { MicroagentManagementConversationStopped } from "./microagent-management-conversation-stopped";
|
||||
|
||||
export function MicroagentManagementMain() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagent } = useSelector(
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
if (!selectedMicroagent) {
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#F9FBFE] text-xl font-bold pb-4">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT)}
|
||||
</div>
|
||||
<div className="text-white text-sm font-normal text-center max-w-[455px]">
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const { microagent, conversation } = selectedMicroagentItem ?? {};
|
||||
|
||||
if (microagent) {
|
||||
return <MicroagentManagementViewMicroagent />;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (conversation) {
|
||||
if (conversation.pr_number && conversation.pr_number.length > 0) {
|
||||
return <MicroagentManagementReviewPr />;
|
||||
}
|
||||
|
||||
const isConversationStarting =
|
||||
conversation.status === "STARTING" ||
|
||||
conversation.runtime_status === "STATUS$STARTING_RUNTIME";
|
||||
const isConversationOpeningPr =
|
||||
conversation.status === "RUNNING" &&
|
||||
conversation.runtime_status === "STATUS$READY";
|
||||
|
||||
if (isConversationStarting || isConversationOpeningPr) {
|
||||
return <MicroagentManagementOpeningPr />;
|
||||
}
|
||||
|
||||
if (conversation.runtime_status === "STATUS$ERROR") {
|
||||
return <MicroagentManagementError />;
|
||||
}
|
||||
|
||||
if (
|
||||
conversation.status === "STOPPED" ||
|
||||
conversation.runtime_status === "STATUS$STOPPED"
|
||||
) {
|
||||
return <MicroagentManagementConversationStopped />;
|
||||
}
|
||||
|
||||
return <MicroagentManagementDefault />;
|
||||
}
|
||||
|
||||
return <MicroagentManagementDefault />;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,142 @@
|
||||
import { useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { formatDateMMDDYYYY } from "#/utils/format-time-delta";
|
||||
import { RepositoryMicroagent } from "#/types/microagent-management";
|
||||
import { Conversation } from "#/api/open-hands.types";
|
||||
import {
|
||||
setSelectedMicroagentItem,
|
||||
setSelectedRepository,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { RootState } from "#/store";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { GitRepository } from "#/types/git";
|
||||
|
||||
interface MicroagentManagementMicroagentCardProps {
|
||||
microagent: {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
};
|
||||
microagent?: RepositoryMicroagent;
|
||||
conversation?: Conversation;
|
||||
repository: GitRepository;
|
||||
}
|
||||
|
||||
export function MicroagentManagementMicroagentCard({
|
||||
microagent,
|
||||
conversation,
|
||||
repository,
|
||||
}: MicroagentManagementMicroagentCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
status: conversationStatus,
|
||||
runtime_status: runtimeStatus,
|
||||
pr_number: prNumber,
|
||||
} = conversation ?? {};
|
||||
|
||||
// Format the repository URL to point to the microagent file
|
||||
const microagentFilePath = `.openhands/microagents/${microagent.name}`;
|
||||
const microagentFilePath = microagent
|
||||
? `.openhands/microagents/${microagent.name}`
|
||||
: "";
|
||||
|
||||
// Format the createdAt date using MM/DD/YYYY format
|
||||
const formattedCreatedAt = formatDateMMDDYYYY(new Date(microagent.createdAt));
|
||||
const formattedCreatedAt = useMemo(() => {
|
||||
if (microagent) {
|
||||
return formatDateMMDDYYYY(new Date(microagent.created_at));
|
||||
}
|
||||
if (conversation) {
|
||||
return formatDateMMDDYYYY(new Date(conversation.created_at));
|
||||
}
|
||||
return "";
|
||||
}, [microagent, conversation]);
|
||||
|
||||
const hasPr = !!(prNumber && prNumber.length > 0);
|
||||
|
||||
// Helper function to get status text
|
||||
const statusText = useMemo(() => {
|
||||
if (hasPr) {
|
||||
return t(I18nKey.COMMON$READY_FOR_REVIEW);
|
||||
}
|
||||
if (
|
||||
conversationStatus === "STARTING" ||
|
||||
runtimeStatus === "STATUS$STARTING_RUNTIME"
|
||||
) {
|
||||
return t(I18nKey.COMMON$STARTING);
|
||||
}
|
||||
if (
|
||||
conversationStatus === "STOPPED" ||
|
||||
runtimeStatus === "STATUS$STOPPED"
|
||||
) {
|
||||
return t(I18nKey.COMMON$STOPPED);
|
||||
}
|
||||
if (runtimeStatus === "STATUS$ERROR") {
|
||||
return t(I18nKey.MICROAGENT$STATUS_ERROR);
|
||||
}
|
||||
if (conversationStatus === "RUNNING" && runtimeStatus === "STATUS$READY") {
|
||||
return t(I18nKey.MICROAGENT$STATUS_OPENING_PR);
|
||||
}
|
||||
return "";
|
||||
}, [conversationStatus, runtimeStatus, t, hasPr]);
|
||||
|
||||
const cardTitle = microagent?.name ?? conversation?.title;
|
||||
|
||||
const isCardSelected = useMemo(() => {
|
||||
if (microagent && selectedMicroagentItem?.microagent) {
|
||||
return selectedMicroagentItem.microagent.name === microagent.name;
|
||||
}
|
||||
if (conversation && selectedMicroagentItem?.conversation) {
|
||||
return (
|
||||
selectedMicroagentItem.conversation.conversation_id ===
|
||||
conversation.conversation_id
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}, [microagent, conversation, selectedMicroagentItem]);
|
||||
|
||||
const onMicroagentCardClicked = () => {
|
||||
dispatch(
|
||||
setSelectedMicroagentItem(
|
||||
microagent
|
||||
? {
|
||||
microagent,
|
||||
conversation: null,
|
||||
}
|
||||
: {
|
||||
microagent: null,
|
||||
conversation,
|
||||
},
|
||||
),
|
||||
);
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-[#ffffff0d] border border-[#ffffff33] p-4 cursor-pointer hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300">
|
||||
<div className="text-white text-[16px] font-semibold">
|
||||
{microagent.name}
|
||||
</div>
|
||||
<div className="text-white text-sm font-normal">{microagentFilePath}</div>
|
||||
<div className="text-white text-sm font-normal">
|
||||
{t(I18nKey.COMMON$CREATED_ON)} {formattedCreatedAt}
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-lg bg-[#ffffff0d] border border-[#ffffff33] p-4 cursor-pointer hover:bg-[#ffffff33] hover:border-[#C9B974] transition-all duration-300",
|
||||
isCardSelected && "bg-[#ffffff33] border-[#C9B974]",
|
||||
)}
|
||||
onClick={onMicroagentCardClicked}
|
||||
>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
{statusText && (
|
||||
<div className="px-[6px] py-[2px] text-[11px] font-medium bg-[#C9B97433] text-white rounded-2xl">
|
||||
{statusText}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-white text-[16px] font-semibold">{cardTitle}</div>
|
||||
{!!microagent && (
|
||||
<div className="text-white text-sm font-normal">
|
||||
{microagentFilePath}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-white text-sm font-normal">
|
||||
{t(I18nKey.COMMON$CREATED_ON)} {formattedCreatedAt}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { Loader } from "#/components/shared/loader";
|
||||
|
||||
export function MicroagentManagementOpeningPr() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { conversation } = selectedMicroagentItem ?? {};
|
||||
|
||||
const { conversation_id: conversationId } = conversation ?? {};
|
||||
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#ffffff99] text-[22px] font-semibold pb-2">
|
||||
{t(I18nKey.COMMON$WORKING_ON_IT)}!
|
||||
</div>
|
||||
<div className="text-[#ffffff99] text-[18px] font-normal text-center max-w-[518px] pb-[22px]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT)}
|
||||
</div>
|
||||
<Loader size="small" className="pb-[22px]" />
|
||||
<a
|
||||
href={`/conversations/${conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
testId="view-conversation-button"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT$VIEW_CONVERSATION)}
|
||||
</BrandButton>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,66 +1,121 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { MicroagentManagementMicroagentCard } from "./microagent-management-microagent-card";
|
||||
import { MicroagentManagementLearnThisRepo } from "./microagent-management-learn-this-repo";
|
||||
import { useRepositoryMicroagents } from "#/hooks/query/use-repository-microagents";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
|
||||
export interface RepoMicroagent {
|
||||
id: string;
|
||||
repositoryName: string;
|
||||
repositoryUrl: string;
|
||||
}
|
||||
import { useSearchConversations } from "#/hooks/query/use-search-conversations";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { RootState } from "#/store";
|
||||
import { setSelectedMicroagentItem } from "#/state/microagent-management-slice";
|
||||
|
||||
interface MicroagentManagementRepoMicroagentsProps {
|
||||
repoMicroagent: RepoMicroagent;
|
||||
repository: GitRepository;
|
||||
}
|
||||
|
||||
export function MicroagentManagementRepoMicroagents({
|
||||
repoMicroagent,
|
||||
repository,
|
||||
}: MicroagentManagementRepoMicroagentsProps) {
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { full_name: repositoryName } = repository;
|
||||
|
||||
// Extract owner and repo from repositoryName (format: "owner/repo")
|
||||
const [owner, repo] = repoMicroagent.repositoryName.split("/");
|
||||
const [owner, repo] = repositoryName.split("/");
|
||||
|
||||
const {
|
||||
data: microagents,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useRepositoryMicroagents(owner, repo);
|
||||
isLoading: isLoadingMicroagents,
|
||||
isError: isErrorMicroagents,
|
||||
} = useRepositoryMicroagents(owner, repo, true);
|
||||
|
||||
const {
|
||||
data: conversations,
|
||||
isLoading: isLoadingConversations,
|
||||
isError: isErrorConversations,
|
||||
} = useSearchConversations(
|
||||
repositoryName,
|
||||
"microagent_management",
|
||||
1000,
|
||||
true,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const hasConversations = conversations && conversations.length > 0;
|
||||
const selectedConversation = selectedMicroagentItem?.conversation;
|
||||
|
||||
if (hasConversations && selectedConversation) {
|
||||
// get the latest selected conversation.
|
||||
const latestSelectedConversation = conversations.find(
|
||||
(conversation) =>
|
||||
conversation.conversation_id === selectedConversation.conversation_id,
|
||||
);
|
||||
if (latestSelectedConversation) {
|
||||
dispatch(
|
||||
setSelectedMicroagentItem({
|
||||
microagent: null,
|
||||
conversation: latestSelectedConversation,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [conversations]);
|
||||
|
||||
// Show loading only when both queries are loading
|
||||
const isLoading = isLoadingMicroagents || isLoadingConversations;
|
||||
|
||||
// Show error UI.
|
||||
const isError = isErrorMicroagents || isErrorConversations;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="pb-4 flex justify-center">
|
||||
<LoadingSpinner size="small" />
|
||||
<Spinner size="sm" data-testid="loading-spinner" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If there's an error with microagents, show the learn this repo component
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="pb-4">
|
||||
<MicroagentManagementLearnThisRepo
|
||||
repositoryUrl={repoMicroagent.repositoryUrl}
|
||||
/>
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const numberOfMicroagents = microagents?.length || 0;
|
||||
const numberOfConversations = conversations?.length || 0;
|
||||
const totalItems = numberOfMicroagents + numberOfConversations;
|
||||
|
||||
return (
|
||||
<div className="pb-4">
|
||||
{numberOfMicroagents === 0 && (
|
||||
<MicroagentManagementLearnThisRepo
|
||||
repositoryUrl={repoMicroagent.repositoryUrl}
|
||||
/>
|
||||
{totalItems === 0 && (
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
)}
|
||||
|
||||
{/* Render microagents */}
|
||||
{numberOfMicroagents > 0 &&
|
||||
microagents?.map((microagent) => (
|
||||
<div key={microagent.name} className="pb-4 last:pb-0">
|
||||
<MicroagentManagementMicroagentCard
|
||||
microagent={{
|
||||
id: microagent.name,
|
||||
name: microagent.name,
|
||||
createdAt: microagent.created_at,
|
||||
}}
|
||||
microagent={microagent}
|
||||
repository={repository}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Render conversations */}
|
||||
{numberOfConversations > 0 &&
|
||||
conversations?.map((conversation) => (
|
||||
<div key={conversation.conversation_id} className="pb-4 last:pb-0">
|
||||
<MicroagentManagementMicroagentCard
|
||||
conversation={conversation}
|
||||
repository={repository}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Accordion, AccordionItem } from "@heroui/react";
|
||||
import { MicroagentManagementRepoMicroagents } from "./microagent-management-repo-microagents";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { getGitProviderBaseUrl, cn } from "#/utils/utils";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { TabType } from "#/types/microagent-management";
|
||||
import { MicroagentManagementNoRepositories } from "./microagent-management-no-repositories";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
@@ -98,7 +98,7 @@ export function MicroagentManagementRepositories({
|
||||
className="w-full px-0 gap-3"
|
||||
itemClasses={{
|
||||
base: "shadow-none bg-transparent border border-[#ffffff40] rounded-[6px] cursor-pointer",
|
||||
trigger: "cursor-pointer",
|
||||
trigger: "cursor-pointer gap-1",
|
||||
}}
|
||||
selectionMode="multiple"
|
||||
>
|
||||
@@ -110,13 +110,7 @@ export function MicroagentManagementRepositories({
|
||||
<MicroagentManagementAccordionTitle repository={repository} />
|
||||
}
|
||||
>
|
||||
<MicroagentManagementRepoMicroagents
|
||||
repoMicroagent={{
|
||||
id: repository.id,
|
||||
repositoryName: repository.full_name,
|
||||
repositoryUrl: `${getGitProviderBaseUrl(repository.git_provider)}/${repository.full_name}`,
|
||||
}}
|
||||
/>
|
||||
<MicroagentManagementRepoMicroagents repository={repository} />
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { getProviderName, constructPullRequestUrl } from "#/utils/utils";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
export function MicroagentManagementReviewPr() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { conversation } = selectedMicroagentItem ?? {};
|
||||
|
||||
const {
|
||||
conversation_id: conversationId,
|
||||
selected_repository: selectedRepository,
|
||||
git_provider: gitProvider,
|
||||
pr_number: prNumber,
|
||||
} = conversation ?? {};
|
||||
|
||||
if (!conversationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full items-center justify-center">
|
||||
<div className="text-[#ffffff99] text-[22px] font-bold pb-[22px] text-center max-w-[455px]">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY)}
|
||||
</div>
|
||||
<div className="flex gap-[22px]">
|
||||
<a
|
||||
href={`/conversations/${conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
testId="view-conversation-button"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT$VIEW_CONVERSATION)}
|
||||
</BrandButton>
|
||||
</a>
|
||||
<a
|
||||
href={
|
||||
selectedRepository && gitProvider && prNumber && prNumber.length > 0
|
||||
? constructPullRequestUrl(
|
||||
prNumber[0],
|
||||
gitProvider,
|
||||
selectedRepository,
|
||||
)
|
||||
: "/#"
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
testId="view-conversation-button"
|
||||
>
|
||||
{`${t(I18nKey.COMMON$REVIEW_PR_IN)} ${getProviderName(
|
||||
gitProvider as Provider,
|
||||
)}`}
|
||||
</BrandButton>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import QuestionCircleIcon from "#/icons/question-circle.svg?react";
|
||||
import { DOCUMENTATION_URL } from "#/utils/constants";
|
||||
|
||||
export function MicroagentManagementSidebarHeader() {
|
||||
const { t } = useTranslation();
|
||||
@@ -12,7 +13,13 @@ export function MicroagentManagementSidebarHeader() {
|
||||
</h1>
|
||||
<p className="text-white text-sm font-normal leading-[20px] pt-2">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$USE_MICROAGENTS)}
|
||||
<QuestionCircleIcon className="inline-block ml-1" />
|
||||
<a
|
||||
href={DOCUMENTATION_URL.MICROAGENTS.MICROAGENTS_OVERVIEW}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<QuestionCircleIcon className="inline-block ml-1" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import { MicroagentManagementSidebarHeader } from "./microagent-management-sidebar-header";
|
||||
import { MicroagentManagementSidebarTabs } from "./microagent-management-sidebar-tabs";
|
||||
import { useUserRepositories } from "#/hooks/query/use-user-repositories";
|
||||
@@ -10,9 +11,15 @@ import {
|
||||
setRepositories,
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
export function MicroagentManagementSidebar() {
|
||||
interface MicroagentManagementSidebarProps {
|
||||
isSmallerScreen?: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementSidebar({
|
||||
isSmallerScreen = false,
|
||||
}: MicroagentManagementSidebarProps) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { data: repositories, isLoading } = useUserRepositories();
|
||||
@@ -42,11 +49,16 @@ export function MicroagentManagementSidebar() {
|
||||
}, [repositories, dispatch]);
|
||||
|
||||
return (
|
||||
<div className="w-[418px] h-full max-h-full overflow-y-auto overflow-x-hidden border-r border-[#525252] bg-[#24272E] rounded-tl-lg rounded-bl-lg py-10 px-6 flex flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
"w-[418px] h-full max-h-full overflow-y-auto overflow-x-hidden border-r border-[#525252] bg-[#24272E] rounded-tl-lg rounded-bl-lg py-10 px-6 flex flex-col",
|
||||
isSmallerScreen && "w-full border-none",
|
||||
)}
|
||||
>
|
||||
<MicroagentManagementSidebarHeader />
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 flex-1">
|
||||
<LoadingSpinner size="small" />
|
||||
<Spinner size="sm" />
|
||||
<span className="text-sm text-white">
|
||||
{t("HOME$LOADING_REPOSITORIES")}
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
import { useEffect, useRef, useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaCircleInfo } from "react-icons/fa6";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import XIcon from "#/icons/x.svg?react";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { BadgeInput } from "#/components/shared/inputs/badge-input";
|
||||
import { MicroagentFormData } from "#/types/microagent-management";
|
||||
import { Branch, GitRepository } from "#/types/git";
|
||||
import { useRepositoryBranches } from "#/hooks/query/use-repository-branches";
|
||||
import {
|
||||
BranchDropdown,
|
||||
BranchLoadingState,
|
||||
BranchErrorState,
|
||||
} from "../home/repository-selection";
|
||||
|
||||
interface MicroagentManagementUpsertMicroagentModalProps {
|
||||
onConfirm: (formData: MicroagentFormData) => void;
|
||||
onCancel: () => void;
|
||||
isLoading: boolean;
|
||||
isUpdate?: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementUpsertMicroagentModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
isUpdate = false,
|
||||
}: MicroagentManagementUpsertMicroagentModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [triggers, setTriggers] = useState<string[]>([]);
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { microagent } = selectedMicroagentItem ?? {};
|
||||
|
||||
// Add a ref to track if the branch was manually cleared by the user
|
||||
const branchManuallyClearedRef = useRef<boolean>(false);
|
||||
|
||||
// Populate form fields with existing microagent data when updating
|
||||
useEffect(() => {
|
||||
if (isUpdate && microagent) {
|
||||
setQuery(microagent.content);
|
||||
setTriggers(microagent.triggers || []);
|
||||
}
|
||||
}, [isUpdate, microagent]);
|
||||
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isLoadingBranches,
|
||||
isError: isBranchesError,
|
||||
} = useRepositoryBranches(selectedRepository?.full_name || null);
|
||||
|
||||
const branchesItems = branches?.map((branch) => ({
|
||||
key: branch.name,
|
||||
label: branch.name,
|
||||
}));
|
||||
|
||||
// Auto-select main or master branch if it exists.
|
||||
useEffect(() => {
|
||||
if (
|
||||
branches &&
|
||||
branches.length > 0 &&
|
||||
!selectedBranch &&
|
||||
!isLoadingBranches
|
||||
) {
|
||||
// Look for main or master branch
|
||||
const mainBranch = branches.find((branch) => branch.name === "main");
|
||||
const masterBranch = branches.find((branch) => branch.name === "master");
|
||||
|
||||
// Select main if it exists, otherwise select master if it exists
|
||||
if (mainBranch) {
|
||||
setSelectedBranch(mainBranch);
|
||||
} else if (masterBranch) {
|
||||
setSelectedBranch(masterBranch);
|
||||
}
|
||||
}
|
||||
}, [branches, isLoadingBranches, selectedBranch]);
|
||||
|
||||
const modalTitle = useMemo(() => {
|
||||
if (isUpdate) {
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT);
|
||||
}
|
||||
|
||||
if (selectedRepository) {
|
||||
return `${t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO)} ${(selectedRepository as GitRepository).full_name}`;
|
||||
}
|
||||
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT);
|
||||
}, [isUpdate, selectedRepository, t]);
|
||||
|
||||
const modalDescription = useMemo(() => {
|
||||
if (isUpdate) {
|
||||
return t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION,
|
||||
);
|
||||
}
|
||||
|
||||
return t(I18nKey.MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION);
|
||||
}, [isUpdate, t]);
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
triggers,
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
microagentPath: microagent?.path || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
query: query.trim(),
|
||||
triggers,
|
||||
selectedBranch: selectedBranch?.name || "",
|
||||
microagentPath: microagent?.path || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleBranchSelection = (key: React.Key | null) => {
|
||||
const selectedBranchObj = branches?.find((branch) => branch.name === key);
|
||||
setSelectedBranch(selectedBranchObj || null);
|
||||
// Reset the manually cleared flag when a branch is explicitly selected
|
||||
branchManuallyClearedRef.current = false;
|
||||
};
|
||||
|
||||
const handleBranchInputChange = (value: string) => {
|
||||
// Clear the selected branch if the input is empty or contains only whitespace
|
||||
// This fixes the issue where users can't delete the entire default branch name
|
||||
if (value === "" || value.trim() === "") {
|
||||
setSelectedBranch(null);
|
||||
// Set the flag to indicate that the branch was manually cleared
|
||||
branchManuallyClearedRef.current = true;
|
||||
} else {
|
||||
// Reset the flag when the user starts typing again
|
||||
branchManuallyClearedRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the appropriate UI for branch selector based on the loading/error state
|
||||
const renderBranchSelector = () => {
|
||||
if (!selectedRepository) {
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={[]}
|
||||
onSelectionChange={() => {}}
|
||||
onInputChange={() => {}}
|
||||
isDisabled
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoadingBranches) {
|
||||
return <BranchLoadingState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
if (isBranchesError) {
|
||||
return <BranchErrorState wrapperClassName="max-w-full w-full" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BranchDropdown
|
||||
items={branchesItems || []}
|
||||
onSelectionChange={handleBranchSelection}
|
||||
onInputChange={handleBranchInputChange}
|
||||
isDisabled={false}
|
||||
selectedKey={selectedBranch?.name}
|
||||
wrapperClassName="max-w-full w-full"
|
||||
label={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop onClose={onCancel}>
|
||||
<ModalBody className="items-start rounded-[12px] p-6 min-w-[611px]">
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-white text-xl font-medium">{modalTitle}</h2>
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-overview#microagents-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<button type="button" onClick={onCancel} className="cursor-pointer">
|
||||
<XIcon width={24} height={24} color="#F9FBFE" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-white text-sm font-normal">
|
||||
{modalDescription}
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
data-testid="add-microagent-modal"
|
||||
onSubmit={onSubmit}
|
||||
className="flex flex-col gap-6 w-full"
|
||||
>
|
||||
{renderBranchSelector()}
|
||||
<label
|
||||
htmlFor="query-input"
|
||||
className="flex flex-col gap-2 w-full text-sm font-normal"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$WHAT_TO_DO)}
|
||||
<textarea
|
||||
required
|
||||
data-testid="query-input"
|
||||
name="query-input"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t(I18nKey.MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO)}
|
||||
rows={6}
|
||||
className={cn(
|
||||
"bg-tertiary border border-[#717888] bg-[#454545] w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt resize-none",
|
||||
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="trigger-input"
|
||||
className="flex flex-col gap-2.5 w-full text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$ADD_TRIGGERS)}
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/prompting/microagents-keyword"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaCircleInfo className="text-primary" />
|
||||
</a>
|
||||
</div>
|
||||
<BadgeInput
|
||||
name="trigger-input"
|
||||
value={triggers}
|
||||
placeholder={t("MICROAGENT$TYPE_TRIGGER_SPACE")}
|
||||
onChange={setTriggers}
|
||||
/>
|
||||
<span className="text-xs text-[#ffffff80] font-normal">
|
||||
{t(
|
||||
I18nKey.MICROAGENT_MANAGEMENT$HELP_TEXT_DESCRIBING_VALID_TRIGGERS,
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
<div
|
||||
className="flex items-center justify-end gap-2 w-full"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
testId="cancel-button"
|
||||
>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleConfirm}
|
||||
testId="confirm-button"
|
||||
isDisabled={
|
||||
!query.trim() ||
|
||||
isLoading ||
|
||||
isLoadingBranches ||
|
||||
!selectedBranch ||
|
||||
isBranchesError
|
||||
}
|
||||
>
|
||||
{isLoading || isLoadingBranches
|
||||
? t(I18nKey.HOME$LOADING)
|
||||
: t(I18nKey.MICROAGENT$LAUNCH)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { ul, ol } from "../markdown/list";
|
||||
import { paragraph } from "../markdown/paragraph";
|
||||
import { anchor } from "../markdown/anchor";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
export function MicroagentManagementViewMicroagentContent() {
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { microagent } = selectedMicroagentItem ?? {};
|
||||
|
||||
const transformMicroagentContent = (): string => {
|
||||
if (!microagent) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// If no triggers exist, return the content as-is
|
||||
if (!microagent.triggers || microagent.triggers.length === 0) {
|
||||
return microagent.content;
|
||||
}
|
||||
|
||||
// Create the triggers frontmatter
|
||||
const triggersFrontmatter = `
|
||||
---
|
||||
|
||||
triggers:
|
||||
${microagent.triggers.map((trigger) => ` - ${trigger}`).join("\n")}
|
||||
|
||||
---
|
||||
`;
|
||||
|
||||
// Prepend the frontmatter to the content
|
||||
return `
|
||||
${triggersFrontmatter}
|
||||
|
||||
${microagent.content}
|
||||
`;
|
||||
};
|
||||
|
||||
if (!microagent || !selectedRepository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Transform the content to include triggers frontmatter if applicable
|
||||
const transformedContent = transformMicroagentContent();
|
||||
|
||||
return (
|
||||
<div className="w-full h-full p-6 bg-[#ffffff1a] rounded-2xl text-white text-sm">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{transformedContent}
|
||||
</Markdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RootState } from "#/store";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { getProviderName, constructMicroagentUrl } from "#/utils/utils";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { setUpdateMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
|
||||
export function MicroagentManagementViewMicroagentHeader() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { microagent } = selectedMicroagentItem ?? {};
|
||||
|
||||
if (!microagent || !selectedRepository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Construct the microagent URL
|
||||
const microagentUrl = constructMicroagentUrl(
|
||||
selectedRepository.git_provider,
|
||||
selectedRepository.full_name,
|
||||
microagent.path,
|
||||
);
|
||||
|
||||
const handleLearnSomethingNew = () => {
|
||||
dispatch(setUpdateMicroagentModalVisible(true));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between pb-2">
|
||||
<span className="text-sm text-[#ffffff99]">
|
||||
{selectedRepository.full_name}
|
||||
</span>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<a href={microagentUrl} target="_blank" rel="noopener noreferrer">
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
testId="edit-in-git-button"
|
||||
className="py-1 px-2"
|
||||
>
|
||||
{`${t(I18nKey.COMMON$EDIT_IN)} ${getProviderName(selectedRepository.git_provider)}`}
|
||||
</BrandButton>
|
||||
</a>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleLearnSomethingNew}
|
||||
testId="learn-button"
|
||||
className="py-1 px-2"
|
||||
>
|
||||
{t(I18nKey.COMMON$LEARN_SOMETHING_NEW)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { MicroagentManagementViewMicroagentHeader } from "./microagent-management-view-microagent-header";
|
||||
import { MicroagentManagementViewMicroagentContent } from "./microagent-management-view-microagent-content";
|
||||
|
||||
export function MicroagentManagementViewMicroagent() {
|
||||
const { selectedMicroagentItem } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { selectedRepository } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const { microagent } = selectedMicroagentItem ?? {};
|
||||
|
||||
if (!microagent || !selectedRepository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full p-6 overflow-auto">
|
||||
<MicroagentManagementViewMicroagentHeader />
|
||||
<span className="text-white text-2xl font-medium pb-2">
|
||||
{microagent.name}
|
||||
</span>
|
||||
<span className="text-white text-lg font-medium pb-6">
|
||||
{microagent.path}
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<MicroagentManagementViewMicroagentContent />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export function InstallSlackAppAnchor() {
|
||||
return (
|
||||
<a
|
||||
data-testid="install-slack-app-button"
|
||||
href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"
|
||||
href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,channels:history,chat:write,groups:history,im:history,mpim:history,users:read&user_scope="
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="py-9"
|
||||
|
||||
@@ -9,29 +9,35 @@ interface TrajectoryActionsProps {
|
||||
onPositiveFeedback: () => void;
|
||||
onNegativeFeedback: () => void;
|
||||
onExportTrajectory: () => void;
|
||||
isSaasMode?: boolean;
|
||||
}
|
||||
|
||||
export function TrajectoryActions({
|
||||
onPositiveFeedback,
|
||||
onNegativeFeedback,
|
||||
onExportTrajectory,
|
||||
isSaasMode = false,
|
||||
}: TrajectoryActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div data-testid="feedback-actions" className="flex gap-1">
|
||||
<TrajectoryActionButton
|
||||
testId="positive-feedback"
|
||||
onClick={onPositiveFeedback}
|
||||
icon={<ThumbsUpIcon width={15} height={15} />}
|
||||
tooltip={t(I18nKey.BUTTON$MARK_HELPFUL)}
|
||||
/>
|
||||
<TrajectoryActionButton
|
||||
testId="negative-feedback"
|
||||
onClick={onNegativeFeedback}
|
||||
icon={<ThumbDownIcon width={15} height={15} />}
|
||||
tooltip={t(I18nKey.BUTTON$MARK_NOT_HELPFUL)}
|
||||
/>
|
||||
{!isSaasMode && (
|
||||
<>
|
||||
<TrajectoryActionButton
|
||||
testId="positive-feedback"
|
||||
onClick={onPositiveFeedback}
|
||||
icon={<ThumbsUpIcon width={15} height={15} />}
|
||||
tooltip={t(I18nKey.BUTTON$MARK_HELPFUL)}
|
||||
/>
|
||||
<TrajectoryActionButton
|
||||
testId="negative-feedback"
|
||||
onClick={onNegativeFeedback}
|
||||
icon={<ThumbDownIcon width={15} height={15} />}
|
||||
tooltip={t(I18nKey.BUTTON$MARK_NOT_HELPFUL)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<TrajectoryActionButton
|
||||
testId="export-trajectory"
|
||||
onClick={onExportTrajectory}
|
||||
|
||||
25
frontend/src/components/shared/loader.tsx
Normal file
25
frontend/src/components/shared/loader.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
interface LoaderProps {
|
||||
size?: "small" | "medium" | "large";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Loader({ size = "medium", className }: LoaderProps) {
|
||||
const sizeClasses = {
|
||||
small: "w-3 h-3",
|
||||
medium: "w-4 h-4",
|
||||
large: "w-5 h-5",
|
||||
};
|
||||
|
||||
const dotSize = sizeClasses[size];
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="loader"
|
||||
className={cn("flex items-center justify-center", className)}
|
||||
>
|
||||
<div className={cn("loader rounded-full", dotSize)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import posthog from "posthog-js";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { SuggestedTask } from "#/components/features/home/tasks/task.types";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { CreateMicroagent } from "#/api/open-hands.types";
|
||||
|
||||
interface CreateConversationVariables {
|
||||
query?: string;
|
||||
@@ -13,6 +14,7 @@ interface CreateConversationVariables {
|
||||
};
|
||||
suggestedTask?: SuggestedTask;
|
||||
conversationInstructions?: string;
|
||||
createMicroagent?: CreateMicroagent;
|
||||
}
|
||||
|
||||
export const useCreateConversation = () => {
|
||||
@@ -21,8 +23,13 @@ export const useCreateConversation = () => {
|
||||
return useMutation({
|
||||
mutationKey: ["create-conversation"],
|
||||
mutationFn: async (variables: CreateConversationVariables) => {
|
||||
const { query, repository, suggestedTask, conversationInstructions } =
|
||||
variables;
|
||||
const {
|
||||
query,
|
||||
repository,
|
||||
suggestedTask,
|
||||
conversationInstructions,
|
||||
createMicroagent,
|
||||
} = variables;
|
||||
|
||||
return OpenHands.createConversation(
|
||||
repository?.name,
|
||||
@@ -31,6 +38,7 @@ export const useCreateConversation = () => {
|
||||
suggestedTask,
|
||||
repository?.branch,
|
||||
conversationInstructions,
|
||||
createMicroagent,
|
||||
);
|
||||
},
|
||||
onSuccess: async (_, { query, repository }) => {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
export const useRepositoryMicroagents = (owner: string, repo: string) =>
|
||||
export const useRepositoryMicroagents = (
|
||||
owner: string,
|
||||
repo: string,
|
||||
cacheDisabled: boolean = false,
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: ["repository", "microagents", owner, repo],
|
||||
queryFn: () => OpenHands.getRepositoryMicroagents(owner, repo),
|
||||
enabled: !!owner && !!repo,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
staleTime: cacheDisabled ? 0 : 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: cacheDisabled ? 0 : 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
|
||||
27
frontend/src/hooks/query/use-search-conversations.ts
Normal file
27
frontend/src/hooks/query/use-search-conversations.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
export const useSearchConversations = (
|
||||
selectedRepository?: string,
|
||||
conversationTrigger?: string,
|
||||
limit: number = 20,
|
||||
cacheDisabled: boolean = false,
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: [
|
||||
"conversations",
|
||||
"search",
|
||||
selectedRepository,
|
||||
conversationTrigger,
|
||||
limit,
|
||||
],
|
||||
queryFn: () =>
|
||||
OpenHands.searchConversations(
|
||||
selectedRepository,
|
||||
conversationTrigger,
|
||||
limit,
|
||||
),
|
||||
enabled: true, // Always enabled since parameters are optional
|
||||
staleTime: cacheDisabled ? 0 : 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: cacheDisabled ? 0 : 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { useCreateConversation } from "./mutation/use-create-conversation";
|
||||
import { useUserProviders } from "./use-user-providers";
|
||||
import { useConversationSubscriptions } from "#/context/conversation-subscriptions-provider";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { CreateMicroagent } from "#/api/open-hands.types";
|
||||
|
||||
/**
|
||||
* Custom hook to create a conversation and subscribe to it, supporting multiple subscriptions.
|
||||
@@ -24,6 +25,7 @@ export const useCreateConversationAndSubscribeMultiple = () => {
|
||||
query,
|
||||
conversationInstructions,
|
||||
repository,
|
||||
createMicroagent,
|
||||
onSuccessCallback,
|
||||
onEventCallback,
|
||||
}: {
|
||||
@@ -34,6 +36,7 @@ export const useCreateConversationAndSubscribeMultiple = () => {
|
||||
branch: string;
|
||||
gitProvider: Provider;
|
||||
};
|
||||
createMicroagent?: CreateMicroagent;
|
||||
onSuccessCallback?: (conversationId: string) => void;
|
||||
onEventCallback?: (event: unknown, conversationId: string) => void;
|
||||
}) => {
|
||||
@@ -42,6 +45,7 @@ export const useCreateConversationAndSubscribeMultiple = () => {
|
||||
query,
|
||||
conversationInstructions,
|
||||
repository,
|
||||
createMicroagent,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// this file generate by script, don't modify it manually!!!
|
||||
export enum I18nKey {
|
||||
MAINTENANCE$SCHEDULED_MESSAGE = "MAINTENANCE$SCHEDULED_MESSAGE",
|
||||
MICROAGENT$NO_REPOSITORY_FOUND = "MICROAGENT$NO_REPOSITORY_FOUND",
|
||||
MICROAGENT$ADD_TO_MICROAGENT = "MICROAGENT$ADD_TO_MICROAGENT",
|
||||
MICROAGENT$WHAT_TO_ADD = "MICROAGENT$WHAT_TO_ADD",
|
||||
@@ -12,6 +13,7 @@ export enum I18nKey {
|
||||
MICROAGENT$VIEW_CONVERSATION = "MICROAGENT$VIEW_CONVERSATION",
|
||||
MICROAGENT$SUCCESS_PR_READY = "MICROAGENT$SUCCESS_PR_READY",
|
||||
MICROAGENT$STATUS_CREATING = "MICROAGENT$STATUS_CREATING",
|
||||
MICROAGENT$STATUS_OPENING_PR = "MICROAGENT$STATUS_OPENING_PR",
|
||||
MICROAGENT$STATUS_COMPLETED = "MICROAGENT$STATUS_COMPLETED",
|
||||
MICROAGENT$STATUS_ERROR = "MICROAGENT$STATUS_ERROR",
|
||||
MICROAGENT$VIEW_YOUR_PR = "MICROAGENT$VIEW_YOUR_PR",
|
||||
@@ -698,6 +700,7 @@ export enum I18nKey {
|
||||
MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES = "MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
||||
MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO = "MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO",
|
||||
MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT = "MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT",
|
||||
MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT = "MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT",
|
||||
MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION",
|
||||
MICROAGENT_MANAGEMENT$WHAT_TO_DO = "MICROAGENT_MANAGEMENT$WHAT_TO_DO",
|
||||
MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO = "MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO",
|
||||
@@ -712,4 +715,23 @@ export enum I18nKey {
|
||||
MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_MICROAGENTS = "MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_MICROAGENTS",
|
||||
MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_ORGANIZATION_LEVEL_MICROAGENTS = "MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_ORGANIZATION_LEVEL_MICROAGENTS",
|
||||
COMMON$SEARCH_REPOSITORIES = "COMMON$SEARCH_REPOSITORIES",
|
||||
COMMON$READY_FOR_REVIEW = "COMMON$READY_FOR_REVIEW",
|
||||
COMMON$COMPLETED = "COMMON$COMPLETED",
|
||||
COMMON$COMPLETED_PARTIALLY = "COMMON$COMPLETED_PARTIALLY",
|
||||
COMMON$STOPPED = "COMMON$STOPPED",
|
||||
COMMON$WORKING_ON_IT = "COMMON$WORKING_ON_IT",
|
||||
MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT = "MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT",
|
||||
MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY = "MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY",
|
||||
COMMON$REVIEW_PR_IN = "COMMON$REVIEW_PR_IN",
|
||||
COMMON$EDIT_IN = "COMMON$EDIT_IN",
|
||||
COMMON$LEARN = "COMMON$LEARN",
|
||||
COMMON$LEARN_SOMETHING_NEW = "COMMON$LEARN_SOMETHING_NEW",
|
||||
COMMON$STARTING = "COMMON$STARTING",
|
||||
MICROAGENT_MANAGEMENT$ERROR = "MICROAGENT_MANAGEMENT$ERROR",
|
||||
MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED = "MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED",
|
||||
MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE = "MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE",
|
||||
MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION",
|
||||
MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO = "MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO",
|
||||
MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO = "MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO",
|
||||
MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION = "MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
{
|
||||
"MAINTENANCE$SCHEDULED_MESSAGE": {
|
||||
"en": "Scheduled maintenance will begin at {{time}}",
|
||||
"ja": "予定されたメンテナンスは{{time}}に開始されます",
|
||||
"zh-CN": "计划维护将于{{time}}开始",
|
||||
"zh-TW": "計劃維護將於{{time}}開始",
|
||||
"ko-KR": "예정된 유지보수가 {{time}}에 시작됩니다",
|
||||
"no": "Planlagt vedlikehold starter {{time}}",
|
||||
"it": "La manutenzione programmata inizierà alle {{time}}",
|
||||
"pt": "A manutenção programada começará às {{time}}",
|
||||
"es": "El mantenimiento programado comenzará a las {{time}}",
|
||||
"ar": "ستبدأ الصيانة المجدولة في {{time}}",
|
||||
"fr": "La maintenance programmée commencera à {{time}}",
|
||||
"tr": "Planlı bakım {{time}} tarihinde başlayacak",
|
||||
"de": "Die geplante Wartung beginnt um {{time}}",
|
||||
"uk": "Планове технічне обслуговування розпочнеться о {{time}}"
|
||||
},
|
||||
"MICROAGENT$NO_REPOSITORY_FOUND": {
|
||||
"en": "No repository found to launch microagent",
|
||||
"ja": "マイクロエージェントを起動するためのリポジトリが見つかりません",
|
||||
@@ -191,6 +207,22 @@
|
||||
"de": "Microagent wird geändert...",
|
||||
"uk": "Зміна мікроагента..."
|
||||
},
|
||||
"MICROAGENT$STATUS_OPENING_PR": {
|
||||
"en": "Opening PR",
|
||||
"ja": "PRを開いています",
|
||||
"zh-CN": "正在打开PR",
|
||||
"zh-TW": "正在打開PR",
|
||||
"ko-KR": "PR 열는 중",
|
||||
"no": "Åpner PR",
|
||||
"it": "Apertura PR",
|
||||
"pt": "Abrindo PR",
|
||||
"es": "Abriendo PR",
|
||||
"ar": "فتح PR",
|
||||
"fr": "Ouverture de la PR",
|
||||
"tr": "PR açılıyor",
|
||||
"de": "PR wird geöffnet",
|
||||
"uk": "Відкриття PR"
|
||||
},
|
||||
"MICROAGENT$STATUS_COMPLETED": {
|
||||
"en": "View microagent update",
|
||||
"ja": "マイクロエージェントの更新を表示",
|
||||
@@ -11167,6 +11199,22 @@
|
||||
"de": "Microagent hinzufügen",
|
||||
"uk": "Додати мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT": {
|
||||
"en": "Update microagent",
|
||||
"ja": "マイクロエージェントを更新",
|
||||
"zh-CN": "更新微代理",
|
||||
"zh-TW": "更新微代理",
|
||||
"ko-KR": "마이크로에이전트 업데이트",
|
||||
"no": "Oppdater mikroagent",
|
||||
"it": "Aggiorna microagent",
|
||||
"pt": "Atualizar microagente",
|
||||
"es": "Actualizar microagente",
|
||||
"ar": "تحديث الوكيل الصغير",
|
||||
"fr": "Mettre à jour le microagent",
|
||||
"tr": "Mikro ajanı güncelle",
|
||||
"de": "Microagent aktualisieren",
|
||||
"uk": "Оновити мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION": {
|
||||
"en": "OpenHands will create a new microagent based on your instructions.",
|
||||
"ja": "OpenHandsはあなたの指示に基づいて新しいマイクロエージェントを作成します。",
|
||||
@@ -11390,5 +11438,309 @@
|
||||
"tr": "Depo ara",
|
||||
"de": "Repositorys durchsuchen",
|
||||
"uk": "Пошук репозиторіїв"
|
||||
},
|
||||
"COMMON$READY_FOR_REVIEW": {
|
||||
"en": "Ready for review",
|
||||
"ja": "レビューの準備ができました",
|
||||
"zh-CN": "准备好审核",
|
||||
"zh-TW": "已準備好審查",
|
||||
"ko-KR": "검토 준비 완료",
|
||||
"no": "Klar for gjennomgang",
|
||||
"it": "Pronto per la revisione",
|
||||
"pt": "Pronto para revisão",
|
||||
"es": "Listo para revisión",
|
||||
"ar": "جاهز للمراجعة",
|
||||
"fr": "Prêt pour la relecture",
|
||||
"tr": "İncelemeye hazır",
|
||||
"de": "Bereit zur Überprüfung",
|
||||
"uk": "Готово до перегляду"
|
||||
},
|
||||
"COMMON$COMPLETED": {
|
||||
"en": "Completed",
|
||||
"ja": "完了",
|
||||
"zh-CN": "已完成",
|
||||
"zh-TW": "已完成",
|
||||
"ko-KR": "완료됨",
|
||||
"no": "Fullført",
|
||||
"it": "Completato",
|
||||
"pt": "Concluído",
|
||||
"es": "Completado",
|
||||
"ar": "مكتمل",
|
||||
"fr": "Terminé",
|
||||
"tr": "Tamamlandı",
|
||||
"de": "Abgeschlossen",
|
||||
"uk": "Завершено"
|
||||
},
|
||||
"COMMON$COMPLETED_PARTIALLY": {
|
||||
"en": "Completed partially",
|
||||
"ja": "一部完了",
|
||||
"zh-CN": "部分完成",
|
||||
"zh-TW": "部分完成",
|
||||
"ko-KR": "부분적으로 완료됨",
|
||||
"no": "Delvis fullført",
|
||||
"it": "Completato parzialmente",
|
||||
"pt": "Concluído parcialmente",
|
||||
"es": "Completado parcialmente",
|
||||
"ar": "مكتمل جزئيًا",
|
||||
"fr": "Partiellement terminé",
|
||||
"tr": "Kısmen tamamlandı",
|
||||
"de": "Teilweise abgeschlossen",
|
||||
"uk": "Частково завершено"
|
||||
},
|
||||
"COMMON$STOPPED": {
|
||||
"en": "Stopped",
|
||||
"ja": "停止しました",
|
||||
"zh-CN": "已停止",
|
||||
"zh-TW": "已停止",
|
||||
"ko-KR": "중지됨",
|
||||
"no": "Stoppet",
|
||||
"it": "Interrotto",
|
||||
"pt": "Parado",
|
||||
"es": "Detenido",
|
||||
"ar": "متوقف",
|
||||
"fr": "Arrêté",
|
||||
"tr": "Durduruldu",
|
||||
"de": "Gestoppt",
|
||||
"uk": "Зупинено"
|
||||
},
|
||||
"COMMON$WORKING_ON_IT": {
|
||||
"en": "Working on it",
|
||||
"ja": "作業中",
|
||||
"zh-CN": "正在处理",
|
||||
"zh-TW": "正在處理",
|
||||
"ko-KR": "작업 중",
|
||||
"no": "Jobber med det",
|
||||
"it": "Ci sto lavorando",
|
||||
"pt": "Trabalhando nisso",
|
||||
"es": "Trabajando en ello",
|
||||
"ar": "يتم العمل عليه",
|
||||
"fr": "En cours",
|
||||
"tr": "Üzerinde çalışılıyor",
|
||||
"de": "Wird bearbeitet",
|
||||
"uk": "В процесі виконання"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT": {
|
||||
"en": "We're working on it! Once OpenHands is done investigating, you'll be able to review its pull request before merging your new microagent.",
|
||||
"ja": "作業中です!OpenHandsの調査が完了すると、新しいマイクロエージェントをマージする前にプルリクエストを確認できます。",
|
||||
"zh-CN": "我们正在处理!OpenHands 调查完成后,您将能够在合并新微代理之前审查其拉取请求。",
|
||||
"zh-TW": "我們正在處理!OpenHands 調查完成後,您將能在合併新微代理前審查其拉取請求。",
|
||||
"ko-KR": "작업 중입니다! OpenHands의 조사가 끝나면 새 마이크로에이전트를 병합하기 전에 풀 리퀘스트를 검토할 수 있습니다.",
|
||||
"no": "Vi jobber med det! Når OpenHands er ferdig med å undersøke, kan du gjennomgå pull requesten før du slår sammen din nye mikroagent.",
|
||||
"it": "Ci stiamo lavorando! Una volta che OpenHands avrà terminato l'analisi, potrai rivedere la pull request prima di unire il tuo nuovo microagent.",
|
||||
"pt": "Estamos trabalhando nisso! Assim que o OpenHands terminar a investigação, você poderá revisar o pull request antes de mesclar seu novo microagente.",
|
||||
"es": "¡Estamos trabajando en ello! Una vez que OpenHands termine de investigar, podrás revisar su pull request antes de fusionar tu nuevo microagente.",
|
||||
"ar": "نحن نعمل على ذلك! بمجرد أن ينتهي OpenHands من التحقيق، ستتمكن من مراجعة طلب السحب قبل دمج وكيلك الدقيق الجديد.",
|
||||
"fr": "Nous y travaillons ! Une fois qu'OpenHands aura terminé l'investigation, vous pourrez examiner sa pull request avant de fusionner votre nouveau microagent.",
|
||||
"tr": "Üzerinde çalışıyoruz! OpenHands incelemeyi bitirdiğinde, yeni mikro ajanınızı birleştirmeden önce pull request'i gözden geçirebileceksiniz.",
|
||||
"de": "Wir arbeiten daran! Sobald OpenHands die Untersuchung abgeschlossen hat, können Sie den Pull Request überprüfen, bevor Sie Ihren neuen Microagenten zusammenführen.",
|
||||
"uk": "Ми працюємо над цим! Після завершення розслідування OpenHands ви зможете переглянути його pull request перед об'єднанням нового мікроагента."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY": {
|
||||
"en": "Your microagent is ready! Merge the PR in GitHub to start using it.",
|
||||
"ja": "マイクロエージェントの準備ができました!GitHubでPRをマージして使い始めましょう。",
|
||||
"zh-CN": "您的微代理已准备就绪!在 GitHub 上合并 PR 即可开始使用。",
|
||||
"zh-TW": "您的微代理已準備就緒!在 GitHub 上合併 PR 即可開始使用。",
|
||||
"ko-KR": "마이크로에이전트가 준비되었습니다! GitHub에서 PR을 병합하여 사용을 시작하세요.",
|
||||
"no": "Din mikroagent er klar! Slå sammen PR-en i GitHub for å begynne å bruke den.",
|
||||
"it": "Il tuo microagente è pronto! Unisci la PR su GitHub per iniziare a usarlo.",
|
||||
"pt": "Seu microagente está pronto! Faça o merge do PR no GitHub para começar a usá-lo.",
|
||||
"es": "¡Tu microagente está listo! Haz merge del PR en GitHub para empezar a usarlo.",
|
||||
"ar": "وكيلك المصغر جاهز! ادمج طلب السحب في GitHub لبدء استخدامه.",
|
||||
"fr": "Votre micro-agent est prêt ! Fusionnez la PR sur GitHub pour commencer à l'utiliser.",
|
||||
"tr": "Mikro ajanınız hazır! Kullanmak için GitHub'da PR'ı birleştirin.",
|
||||
"de": "Ihr Microagent ist bereit! Führen Sie den PR in GitHub zusammen, um ihn zu verwenden.",
|
||||
"uk": "Ваш мікроагент готовий! Злийте PR у GitHub, щоб почати ним користуватися."
|
||||
},
|
||||
"COMMON$REVIEW_PR_IN": {
|
||||
"en": "Review PR in",
|
||||
"ja": "でPRをレビュー",
|
||||
"zh-CN": "在中审查PR",
|
||||
"zh-TW": "在中審查PR",
|
||||
"ko-KR": "에서 PR 검토",
|
||||
"no": "Se gjennom PR i",
|
||||
"it": "Revisiona la PR su",
|
||||
"pt": "Revisar PR em",
|
||||
"es": "Revisar PR en",
|
||||
"ar": "مراجعة PR في",
|
||||
"fr": "Examiner la PR sur",
|
||||
"tr": "PR'ı şurada gözden geçir:",
|
||||
"de": "PR überprüfen in",
|
||||
"uk": "Переглянути PR у"
|
||||
},
|
||||
"COMMON$EDIT_IN": {
|
||||
"en": "Edit in",
|
||||
"ja": "で編集",
|
||||
"zh-CN": "在中编辑",
|
||||
"zh-TW": "在中編輯",
|
||||
"ko-KR": "에서 편집",
|
||||
"no": "Rediger i",
|
||||
"it": "Modifica su",
|
||||
"pt": "Editar em",
|
||||
"es": "Editar en",
|
||||
"ar": "تعديل في",
|
||||
"fr": "Modifier dans",
|
||||
"tr": "Şurada düzenle:",
|
||||
"de": "Bearbeiten in",
|
||||
"uk": "Редагувати у"
|
||||
},
|
||||
"COMMON$LEARN": {
|
||||
"en": "Learn",
|
||||
"ja": "学ぶ",
|
||||
"zh-CN": "学习",
|
||||
"zh-TW": "學習",
|
||||
"ko-KR": "학습",
|
||||
"no": "Lær",
|
||||
"it": "Impara",
|
||||
"pt": "Aprender",
|
||||
"es": "Aprender",
|
||||
"ar": "تعلم",
|
||||
"fr": "Apprendre",
|
||||
"tr": "Öğren",
|
||||
"de": "Lernen",
|
||||
"uk": "Вчитися"
|
||||
},
|
||||
"COMMON$LEARN_SOMETHING_NEW": {
|
||||
"en": "Learn something new",
|
||||
"ja": "新しいことを学ぶ",
|
||||
"zh-CN": "学习新东西",
|
||||
"zh-TW": "學習新東西",
|
||||
"ko-KR": "새로운 것을 배우기",
|
||||
"no": "Lær noe nytt",
|
||||
"it": "Impara qualcosa di nuovo",
|
||||
"pt": "Aprender algo novo",
|
||||
"es": "Aprender algo nuevo",
|
||||
"ar": "تعلم شيئًا جديدًا",
|
||||
"fr": "Apprendre quelque chose de nouveau",
|
||||
"tr": "Yeni bir şey öğren",
|
||||
"de": "Etwas Neues lernen",
|
||||
"uk": "Вивчити щось нове"
|
||||
},
|
||||
"COMMON$STARTING": {
|
||||
"en": "Starting",
|
||||
"ja": "開始中",
|
||||
"zh-CN": "启动中",
|
||||
"zh-TW": "啟動中",
|
||||
"ko-KR": "시작 중",
|
||||
"no": "Starter",
|
||||
"it": "Avvio",
|
||||
"pt": "Iniciando",
|
||||
"es": "Iniciando",
|
||||
"ar": "جارٍ البدء",
|
||||
"fr": "Démarrage",
|
||||
"tr": "Başlatılıyor",
|
||||
"de": "Wird gestartet",
|
||||
"uk": "Запуск"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ERROR": {
|
||||
"en": "The system has encountered an error. Please try again later.",
|
||||
"ja": "システムでエラーが発生しました。後でもう一度お試しください。",
|
||||
"zh-CN": "系统遇到错误。请稍后再试。",
|
||||
"zh-TW": "系統發生錯誤。請稍後再試。",
|
||||
"ko-KR": "시스템에 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
|
||||
"no": "Systemet har oppdaget en feil. Prøv igjen senere.",
|
||||
"it": "Il sistema ha riscontrato un errore. Riprova più tardi.",
|
||||
"pt": "O sistema encontrou um erro. Por favor, tente novamente mais tarde.",
|
||||
"es": "El sistema ha encontrado un error. Por favor, inténtalo de nuevo más tarde.",
|
||||
"ar": "واجه النظام خطأ. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"fr": "Le système a rencontré une erreur. Veuillez réessayer plus tard.",
|
||||
"tr": "Sistem bir hata ile karşılaştı. Lütfen daha sonra tekrar deneyin.",
|
||||
"de": "Das System hat einen Fehler festgestellt. Bitte versuchen Sie es später erneut.",
|
||||
"uk": "Система зіткнулася з помилкою. Будь ласка, спробуйте пізніше."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$CONVERSATION_STOPPED": {
|
||||
"en": "The conversation has been stopped.",
|
||||
"ja": "会話が停止されました。",
|
||||
"zh-CN": "对话已被停止。",
|
||||
"zh-TW": "對話已被停止。",
|
||||
"ko-KR": "대화가 중단되었습니다.",
|
||||
"no": "Samtalen har blitt stoppet.",
|
||||
"it": "La conversazione è stata interrotta.",
|
||||
"pt": "A conversa foi interrompida.",
|
||||
"es": "La conversación ha sido detenida.",
|
||||
"ar": "تم إيقاف المحادثة.",
|
||||
"fr": "La conversation a été arrêtée.",
|
||||
"tr": "Konuşma durduruldu.",
|
||||
"de": "Das Gespräch wurde gestoppt.",
|
||||
"uk": "Розмову зупинено."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_TITLE": {
|
||||
"en": "Learn this repository?",
|
||||
"ja": "このリポジトリを学習しますか?",
|
||||
"zh-CN": "要学习此存储库吗?",
|
||||
"zh-TW": "要學習此存儲庫嗎?",
|
||||
"ko-KR": "이 저장소를 학습하시겠습니까?",
|
||||
"no": "Vil du lære dette depotet?",
|
||||
"it": "Vuoi imparare questo repository?",
|
||||
"pt": "Aprender este repositório?",
|
||||
"es": "¿Aprender este repositorio?",
|
||||
"ar": "هل تريد تعلم هذا المستودع؟",
|
||||
"fr": "Apprendre ce dépôt ?",
|
||||
"tr": "Bu depoyu öğrenmek ister misiniz?",
|
||||
"de": "Dieses Repository lernen?",
|
||||
"uk": "Вивчити цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO_MODAL_DESCRIPTION": {
|
||||
"en": "Do you want OpenHands to launch a new conversation to help you understand this repository?",
|
||||
"ja": "OpenHandsがこのリポジトリを理解するための新しい会話を開始しますか?",
|
||||
"zh-CN": "您想让OpenHands启动一个新对话来帮助您了解此存储库吗?",
|
||||
"zh-TW": "您想讓OpenHands啟動一個新對話來幫助您了解此存儲庫嗎?",
|
||||
"ko-KR": "OpenHands가 이 저장소를 이해하는 데 도움이 되는 새 대화를 시작하도록 하시겠습니까?",
|
||||
"no": "Vil du at OpenHands skal starte en ny samtale for å hjelpe deg med å forstå dette depotet?",
|
||||
"it": "Vuoi che OpenHands avvii una nuova conversazione per aiutarti a capire questo repository?",
|
||||
"pt": "Você quer que o OpenHands inicie uma nova conversa para ajudá-lo a entender este repositório?",
|
||||
"es": "¿Quieres que OpenHands inicie una nueva conversación para ayudarte a entender este repositorio?",
|
||||
"ar": "هل تريد أن يبدأ OpenHands محادثة جديدة لمساعدتك على فهم هذا المستودع؟",
|
||||
"fr": "Voulez-vous qu'OpenHands lance une nouvelle conversation pour vous aider à comprendre ce dépôt ?",
|
||||
"tr": "OpenHands'in bu depoyu anlamanıza yardımcı olacak yeni bir konuşma başlatmasını ister misiniz?",
|
||||
"de": "Möchten Sie, dass OpenHands eine neue Unterhaltung startet, um Ihnen dieses Repository zu erklären?",
|
||||
"uk": "Бажаєте, щоб OpenHands розпочав нову розмову, щоб допомогти вам зрозуміти цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$WHAT_YOU_WOULD_LIKE_TO_KNOW_ABOUT_THIS_REPO": {
|
||||
"en": "What would you like to know about this repository?",
|
||||
"ja": "このリポジトリについて何を知りたいですか?",
|
||||
"zh-CN": "您想了解此存储库的哪些内容?",
|
||||
"zh-TW": "您想了解此存儲庫的哪些內容?",
|
||||
"ko-KR": "이 저장소에 대해 무엇을 알고 싶으신가요?",
|
||||
"no": "Hva vil du vite om dette depotet?",
|
||||
"it": "Cosa vorresti sapere su questo repository?",
|
||||
"pt": "O que você gostaria de saber sobre este repositório?",
|
||||
"es": "¿Qué te gustaría saber sobre este repositorio?",
|
||||
"ar": "ماذا تريد أن تعرف عن هذا المستودع؟",
|
||||
"fr": "Que souhaitez-vous savoir sur ce dépôt ?",
|
||||
"tr": "Bu depo hakkında ne bilmek istersiniz?",
|
||||
"de": "Was möchten Sie über dieses Repository wissen?",
|
||||
"uk": "Що ви хотіли б дізнатися про цей репозиторій?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_KNOW_ABOUT_THIS_REPO": {
|
||||
"en": "Describe what you would like to know about this repository.",
|
||||
"ja": "このリポジトリについて知りたいことを説明してください。",
|
||||
"zh-CN": "请描述您想了解此存储库的内容。",
|
||||
"zh-TW": "請描述您想了解此存儲庫的內容。",
|
||||
"ko-KR": "이 저장소에 대해 알고 싶은 내용을 설명하세요.",
|
||||
"no": "Beskriv hva du vil vite om dette depotet.",
|
||||
"it": "Descrivi cosa vorresti sapere su questo repository.",
|
||||
"pt": "Descreva o que você gostaria de saber sobre este repositório.",
|
||||
"es": "Describe lo que te gustaría saber sobre este repositorio.",
|
||||
"ar": "صف ما ترغب في معرفته عن هذا المستودع.",
|
||||
"fr": "Décrivez ce que vous souhaitez savoir sur ce dépôt.",
|
||||
"tr": "Bu depo hakkında ne bilmek istediğinizi açıklayın.",
|
||||
"de": "Beschreiben Sie, was Sie über dieses Repository wissen möchten.",
|
||||
"uk": "Опишіть, що ви хотіли б дізнатися про цей репозиторій."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT_MODAL_DESCRIPTION": {
|
||||
"en": "OpenHands will update the microagent based on your instructions.",
|
||||
"ja": "OpenHandsはあなたの指示に基づいてマイクロエージェントを更新します。",
|
||||
"zh-CN": "OpenHands 将根据您的指示更新微代理。",
|
||||
"zh-TW": "OpenHands 將根據您的指示更新微代理。",
|
||||
"ko-KR": "OpenHands가 귀하의 지시에 따라 마이크로에이전트를 업데이트합니다.",
|
||||
"no": "OpenHands vil oppdatere mikroagenten basert på dine instruksjoner.",
|
||||
"it": "OpenHands aggiornerà il microagent in base alle tue istruzioni.",
|
||||
"pt": "O OpenHands atualizará o microagente com base nas suas instruções.",
|
||||
"es": "OpenHands actualizará el microagente según tus instrucciones.",
|
||||
"ar": "سيقوم OpenHands بتحديث الوكيل الصغير بناءً على تعليماتك.",
|
||||
"fr": "OpenHands mettra à jour le microagent selon vos instructions.",
|
||||
"tr": "OpenHands, talimatlarınıza göre mikro ajanı güncelleyecektir.",
|
||||
"de": "OpenHands aktualisiert den Microagenten basierend auf Ihren Anweisungen.",
|
||||
"uk": "OpenHands оновить мікроагента відповідно до ваших інструкцій."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,10 @@ export const handlers = [
|
||||
ENABLE_BILLING: false,
|
||||
HIDE_LLM_SETTINGS: mockSaas,
|
||||
},
|
||||
// Uncomment the following to test the maintenance banner
|
||||
// MAINTENANCE: {
|
||||
// startTime: "2024-01-15T10:00:00-05:00", // EST timestamp
|
||||
// },
|
||||
};
|
||||
|
||||
return HttpResponse.json(config);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { redirect } from "react-router";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { MicroagentManagementSidebar } from "#/components/features/microagent-management/microagent-management-sidebar";
|
||||
import { Route } from "./+types/settings";
|
||||
import { queryClient } from "#/query-client-config";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { MicroagentManagementMain } from "#/components/features/microagent-management/microagent-management-main";
|
||||
import { MicroagentManagementAddMicroagentModal } from "#/components/features/microagent-management/microagent-management-add-microagent-modal";
|
||||
import { RootState } from "#/store";
|
||||
import { setAddMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
import { MicroagentManagementContent } from "#/components/features/microagent-management/microagent-management-content";
|
||||
import { ConversationSubscriptionsProvider } from "#/context/conversation-subscriptions-provider";
|
||||
import { EventHandler } from "#/wrapper/event-handler";
|
||||
|
||||
export const clientLoader = async ({ request }: Route.ClientLoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
@@ -31,31 +28,12 @@ export const clientLoader = async ({ request }: Route.ClientLoaderArgs) => {
|
||||
};
|
||||
|
||||
function MicroagentManagement() {
|
||||
const { addMicroagentModalVisible } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const hideAddMicroagentModal = () => {
|
||||
dispatch(setAddMicroagentModalVisible(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex rounded-lg border border-[#525252] bg-[#24272E]">
|
||||
<MicroagentManagementSidebar />
|
||||
<MicroagentManagementMain />
|
||||
{addMicroagentModalVisible && (
|
||||
<MicroagentManagementAddMicroagentModal
|
||||
onConfirm={() => {
|
||||
hideAddMicroagentModal();
|
||||
}}
|
||||
onCancel={() => {
|
||||
hideAddMicroagentModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ConversationSubscriptionsProvider>
|
||||
<EventHandler>
|
||||
<MicroagentManagementContent />
|
||||
</EventHandler>
|
||||
</ConversationSubscriptionsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
import { EmailVerificationGuard } from "#/components/features/guards/email-verification-guard";
|
||||
import { MaintenanceBanner } from "#/components/features/maintenance/maintenance-banner";
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const error = useRouteError();
|
||||
@@ -205,6 +206,9 @@ export default function MainApp() {
|
||||
id="root-outlet"
|
||||
className="h-[calc(100%-50px)] md:h-full w-full relative overflow-auto"
|
||||
>
|
||||
{config.data?.MAINTENANCE && (
|
||||
<MaintenanceBanner startTime={config.data.MAINTENANCE.startTime} />
|
||||
)}
|
||||
<EmailVerificationGuard>
|
||||
<Outlet />
|
||||
</EmailVerificationGuard>
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import { IMicroagentItem } from "#/types/microagent-management";
|
||||
|
||||
export const microagentManagementSlice = createSlice({
|
||||
name: "microagentManagement",
|
||||
initialState: {
|
||||
selectedMicroagent: null,
|
||||
addMicroagentModalVisible: false,
|
||||
selectedRepository: null,
|
||||
updateMicroagentModalVisible: false,
|
||||
selectedRepository: null as GitRepository | null,
|
||||
personalRepositories: [] as GitRepository[],
|
||||
organizationRepositories: [] as GitRepository[],
|
||||
repositories: [] as GitRepository[],
|
||||
selectedMicroagentItem: null as IMicroagentItem | null,
|
||||
learnThisRepoModalVisible: false,
|
||||
},
|
||||
reducers: {
|
||||
setSelectedMicroagent: (state, action) => {
|
||||
state.selectedMicroagent = action.payload;
|
||||
},
|
||||
setAddMicroagentModalVisible: (state, action) => {
|
||||
state.addMicroagentModalVisible = action.payload;
|
||||
},
|
||||
setUpdateMicroagentModalVisible: (state, action) => {
|
||||
state.updateMicroagentModalVisible = action.payload;
|
||||
},
|
||||
setSelectedRepository: (state, action) => {
|
||||
state.selectedRepository = action.payload;
|
||||
},
|
||||
@@ -30,16 +33,24 @@ export const microagentManagementSlice = createSlice({
|
||||
setRepositories: (state, action) => {
|
||||
state.repositories = action.payload;
|
||||
},
|
||||
setSelectedMicroagentItem: (state, action) => {
|
||||
state.selectedMicroagentItem = action.payload;
|
||||
},
|
||||
setLearnThisRepoModalVisible: (state, action) => {
|
||||
state.learnThisRepoModalVisible = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setSelectedMicroagent,
|
||||
setAddMicroagentModalVisible,
|
||||
setUpdateMicroagentModalVisible,
|
||||
setSelectedRepository,
|
||||
setPersonalRepositories,
|
||||
setOrganizationRepositories,
|
||||
setRepositories,
|
||||
setSelectedMicroagentItem,
|
||||
setLearnThisRepoModalVisible,
|
||||
} = microagentManagementSlice.actions;
|
||||
|
||||
export default microagentManagementSlice.reducer;
|
||||
|
||||
@@ -20,3 +20,27 @@
|
||||
.heading {
|
||||
@apply text-[28px] leading-8 -tracking-[0.02em] font-bold text-content-2;
|
||||
}
|
||||
|
||||
.loader {
|
||||
background: #C9B974;
|
||||
animation: l5 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
@keyframes l5 {
|
||||
0% {
|
||||
box-shadow: 20px 0 #C9B974, -20px 0 rgba(201,185,116,0.1);
|
||||
background: #C9B974;
|
||||
}
|
||||
33% {
|
||||
box-shadow: 20px 0 #C9B974, -20px 0 rgba(201,185,116,0.1);
|
||||
background: rgba(201,185,116,0.1);
|
||||
}
|
||||
66% {
|
||||
box-shadow: 20px 0 rgba(201,185,116,0.1), -20px 0 #C9B974;
|
||||
background: rgba(201,185,116,0.1);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 20px 0 rgba(201,185,116,0.1), -20px 0 #C9B974;
|
||||
background: #C9B974;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,6 @@ export enum AgentState {
|
||||
export const RUNTIME_INACTIVE_STATES = [
|
||||
AgentState.INIT,
|
||||
AgentState.LOADING,
|
||||
AgentState.STOPPED,
|
||||
// Removed AgentState.STOPPED to allow tabs to remain visible when agent is stopped
|
||||
AgentState.ERROR,
|
||||
];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Conversation } from "#/api/open-hands.types";
|
||||
|
||||
export type TabType = "personal" | "repositories" | "organizations";
|
||||
|
||||
export interface RepositoryMicroagent {
|
||||
@@ -9,4 +11,22 @@ export interface RepositoryMicroagent {
|
||||
tools: string[];
|
||||
created_at: string;
|
||||
git_provider: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface IMicroagentItem {
|
||||
microagent?: RepositoryMicroagent;
|
||||
conversation?: Conversation;
|
||||
}
|
||||
|
||||
export interface MicroagentFormData {
|
||||
query: string;
|
||||
triggers: string[];
|
||||
selectedBranch: string;
|
||||
microagentPath: string;
|
||||
}
|
||||
|
||||
export interface LearnThisRepoFormData {
|
||||
query: string;
|
||||
selectedBranch: string;
|
||||
}
|
||||
|
||||
@@ -116,3 +116,94 @@ export const getGitProviderBaseUrl = (gitProvider: Provider): string => {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of the git provider
|
||||
* @param gitProvider The git provider
|
||||
* @returns The name of the git provider
|
||||
*/
|
||||
export const getProviderName = (gitProvider: Provider) => {
|
||||
if (gitProvider === "gitlab") return "GitLab";
|
||||
if (gitProvider === "bitbucket") return "Bitbucket";
|
||||
return "GitHub";
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of the PR
|
||||
* @param isGitLab Whether the git provider is GitLab
|
||||
* @returns The name of the PR
|
||||
*/
|
||||
export const getPR = (isGitLab: boolean) =>
|
||||
isGitLab ? "merge request" : "pull request";
|
||||
|
||||
/**
|
||||
* Get the short name of the PR
|
||||
* @param isGitLab Whether the git provider is GitLab
|
||||
* @returns The short name of the PR
|
||||
*/
|
||||
export const getPRShort = (isGitLab: boolean) => (isGitLab ? "MR" : "PR");
|
||||
|
||||
/**
|
||||
* Construct the pull request (merge request) URL for different providers
|
||||
* @param prNumber The pull request number
|
||||
* @param provider The git provider
|
||||
* @param repositoryName The repository name in format "owner/repo"
|
||||
* @returns The pull request URL
|
||||
*
|
||||
* @example
|
||||
* constructPullRequestUrl(123, "github", "owner/repo") // "https://github.com/owner/repo/pull/123"
|
||||
* constructPullRequestUrl(456, "gitlab", "owner/repo") // "https://gitlab.com/owner/repo/-/merge_requests/456"
|
||||
* constructPullRequestUrl(789, "bitbucket", "owner/repo") // "https://bitbucket.org/owner/repo/pull-requests/789"
|
||||
*/
|
||||
export const constructPullRequestUrl = (
|
||||
prNumber: number,
|
||||
provider: Provider,
|
||||
repositoryName: string,
|
||||
): string => {
|
||||
const baseUrl = getGitProviderBaseUrl(provider);
|
||||
|
||||
switch (provider) {
|
||||
case "github":
|
||||
return `${baseUrl}/${repositoryName}/pull/${prNumber}`;
|
||||
case "gitlab":
|
||||
return `${baseUrl}/${repositoryName}/-/merge_requests/${prNumber}`;
|
||||
case "bitbucket":
|
||||
return `${baseUrl}/${repositoryName}/pull-requests/${prNumber}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the microagent URL for different providers
|
||||
* @param gitProvider The git provider
|
||||
* @param repositoryName The repository name in format "owner/repo"
|
||||
* @param microagentPath The path to the microagent in the repository
|
||||
* @returns The URL to the microagent file in the Git provider
|
||||
*
|
||||
* @example
|
||||
* constructMicroagentUrl("github", "owner/repo", ".openhands/microagents/tell-me-a-joke.md")
|
||||
* // "https://github.com/owner/repo/blob/main/.openhands/microagents/tell-me-a-joke.md"
|
||||
* constructMicroagentUrl("gitlab", "owner/repo", "microagents/git-helper.md")
|
||||
* // "https://gitlab.com/owner/repo/-/blob/main/microagents/git-helper.md"
|
||||
* constructMicroagentUrl("bitbucket", "owner/repo", ".openhands/microagents/docker-helper.md")
|
||||
* // "https://bitbucket.org/owner/repo/src/main/.openhands/microagents/docker-helper.md"
|
||||
*/
|
||||
export const constructMicroagentUrl = (
|
||||
gitProvider: Provider,
|
||||
repositoryName: string,
|
||||
microagentPath: string,
|
||||
): string => {
|
||||
const baseUrl = getGitProviderBaseUrl(gitProvider);
|
||||
|
||||
switch (gitProvider) {
|
||||
case "github":
|
||||
return `${baseUrl}/${repositoryName}/blob/main/${microagentPath}`;
|
||||
case "gitlab":
|
||||
return `${baseUrl}/${repositoryName}/-/blob/main/${microagentPath}`;
|
||||
case "bitbucket":
|
||||
return `${baseUrl}/${repositoryName}/src/main/${microagentPath}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("RepositorySelectionForm", () => {
|
||||
});
|
||||
|
||||
(useCreateConversation as any).mockReturnValue({
|
||||
mutate: vi.fn(),
|
||||
mutate: vi.fn(() => (useIsCreatingConversation as any).mockReturnValue(true)),
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
});
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.12",
|
||||
"clsx": "^2.1.1",
|
||||
"deep-equal": "^2.2.3",
|
||||
"focus-trap-react": "^11.0.4",
|
||||
"react-bootstrap-icons": "^1.11.6",
|
||||
"react-select": "^5.10.2",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.10",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^4.0.0",
|
||||
@@ -23,6 +22,7 @@
|
||||
"@storybook/react-vite": "^9.0.12",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/bun": "latest",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
@@ -33,8 +33,11 @@
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
"react": ">=19.1.0",
|
||||
"react-dom": ">=19.1.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"react": ">=19.1.0",
|
||||
"tailwindcss": "^4.1.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -67,7 +70,7 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.28.2", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
|
||||
@@ -75,13 +78,13 @@
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
|
||||
"@babel/runtime": ["@babel/runtime@7.28.2", "", {}, "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
"@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
|
||||
|
||||
@@ -109,63 +112,63 @@
|
||||
|
||||
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.6", "", { "os": "aix", "cpu": "ppc64" }, "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.6", "", { "os": "android", "cpu": "arm64" }, "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.6", "", { "os": "android", "cpu": "x64" }, "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.6", "", { "os": "linux", "cpu": "arm" }, "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.6", "", { "os": "linux", "cpu": "ia32" }, "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.6", "", { "os": "linux", "cpu": "none" }, "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.6", "", { "os": "linux", "cpu": "x64" }, "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q=="],
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.6", "", { "os": "none", "cpu": "x64" }, "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.6", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg=="],
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.6", "", { "os": "openbsd", "cpu": "x64" }, "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.6", "", { "os": "none", "cpu": "arm64" }, "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA=="],
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.6", "", { "os": "sunos", "cpu": "x64" }, "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q=="],
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ=="],
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA=="],
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.2", "", { "dependencies": { "@floating-ui/core": "^1.7.2", "@floating-ui/utils": "^0.2.10" } }, "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA=="],
|
||||
|
||||
"@floating-ui/react": ["@floating-ui/react@0.27.13", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.4", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA=="],
|
||||
"@floating-ui/react": ["@floating-ui/react@0.27.14", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.4", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-aSf9JXfyXpRQWMbtuW+CJQrnhzHu4Hg1Th9AkvR1o+wSW/vCUVMrtgXaRY5ToV5Fh5w3I7lXJdvlKVvYrQrppw=="],
|
||||
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.4", "", { "dependencies": { "@floating-ui/dom": "^1.7.2" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw=="],
|
||||
|
||||
@@ -189,9 +192,9 @@
|
||||
|
||||
"@mdx-js/react": ["@mdx-js/react@3.1.0", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ=="],
|
||||
|
||||
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.52.8", "", { "dependencies": { "@microsoft/api-extractor-model": "7.30.6", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.13.1", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.15.3", "@rushstack/ts-command-line": "5.0.1", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg=="],
|
||||
"@microsoft/api-extractor": ["@microsoft/api-extractor@7.52.9", "", { "dependencies": { "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", "@rushstack/terminal": "0.15.4", "@rushstack/ts-command-line": "5.0.2", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-313nyhc6DSSMVKD43jZK6Yp5XbliGw5vjN7QOw1FHzR1V6DQ67k4dzkd3BSxMtWcm+cEs1Ux8rmDqots6EABFA=="],
|
||||
|
||||
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.30.6", "", { "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.13.1" } }, "sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg=="],
|
||||
"@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.30.7", "", { "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", "@rushstack/node-core-library": "5.14.0" } }, "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ=="],
|
||||
|
||||
"@microsoft/tsdoc": ["@microsoft/tsdoc@0.15.1", "", {}, "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw=="],
|
||||
|
||||
@@ -203,79 +206,79 @@
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.46.1", "", { "os": "android", "cpu": "arm" }, "sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.46.1", "", { "os": "android", "cpu": "arm64" }, "sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.46.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.46.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.46.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.46.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.46.1", "", { "os": "linux", "cpu": "arm" }, "sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.46.1", "", { "os": "linux", "cpu": "arm" }, "sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.46.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.46.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="],
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.46.1", "", { "os": "linux", "cpu": "none" }, "sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw=="],
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.46.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.46.1", "", { "os": "linux", "cpu": "none" }, "sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.46.1", "", { "os": "linux", "cpu": "none" }, "sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.46.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.46.1", "", { "os": "linux", "cpu": "x64" }, "sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.46.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.46.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.46.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="],
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.46.1", "", { "os": "win32", "cpu": "x64" }, "sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ=="],
|
||||
|
||||
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.13.1", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-5yXhzPFGEkVc9Fu92wsNJ9jlvdwz4RNb2bMso+/+TH0nMm1jDDDsOIf4l8GAkPxGuwPw5DH24RliWVfSPhlW/Q=="],
|
||||
"@rushstack/node-core-library": ["@rushstack/node-core-library@5.14.0", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg=="],
|
||||
|
||||
"@rushstack/rig-package": ["@rushstack/rig-package@0.5.3", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow=="],
|
||||
|
||||
"@rushstack/terminal": ["@rushstack/terminal@0.15.3", "", { "dependencies": { "@rushstack/node-core-library": "5.13.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-DGJ0B2Vm69468kZCJkPj3AH5nN+nR9SPmC0rFHtzsS4lBQ7/dgOwtwVxYP7W9JPDMuRBkJ4KHmWKr036eJsj9g=="],
|
||||
"@rushstack/terminal": ["@rushstack/terminal@0.15.4", "", { "dependencies": { "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg=="],
|
||||
|
||||
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.0.1", "", { "dependencies": { "@rushstack/terminal": "0.15.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-bsbUucn41UXrQK7wgM8CNM/jagBytEyJqXw/umtI8d68vFm1Jwxh1OtLrlW7uGZgjCWiiPH6ooUNa1aVsuVr3Q=="],
|
||||
"@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.0.2", "", { "dependencies": { "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ=="],
|
||||
|
||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-9cXNK3q/atx3hwJAt9HkJbd9vUxCXfKKiNNuSACbf8h9/j6u3jktulKOf6Xjc3B8lwn6ZpdK/x1HHZN2kTqsvg=="],
|
||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-msbsTI9TmePQ5ElVclLi7ns5WaAntouJFaj9ElNugFWME21k68RiyXnioDjDfEoi/+y8tthQNNqjsHoX/Ev0Og=="],
|
||||
|
||||
"@storybook/addon-docs": ["@storybook/addon-docs@9.0.17", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.0.17", "@storybook/icons": "^1.2.12", "@storybook/react-dom-shim": "9.0.17", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-LOX/kKgQGnyulrqZHsvf77+ZoH/nSUaplGr5hvZglW/U6ak6fO9seJyXAzVKEnC6p+F8n02kFBZbi3s+znQhSg=="],
|
||||
"@storybook/addon-docs": ["@storybook/addon-docs@9.0.18", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.0.18", "@storybook/icons": "^1.2.12", "@storybook/react-dom-shim": "9.0.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-1mLhaRDx8s1JAF51o56OmwMnIsg4BOQJ8cn+4wbMjh14pDFALrovlFl/BpAXnV1VaZqHjCB4ZWuP+y5CwXEpeQ=="],
|
||||
|
||||
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@9.0.17", "", { "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-WoZZ8d58gP6uBu6OJ2K1GjBSM4+Kcr0I9lo0z3convzYqxrhfUm9pNEwVm57KCbVVyBbIKmevddCsSFoPC5u6Q=="],
|
||||
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@9.0.18", "", { "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-A079BfJ3g3wYOtAuq9cPf2l6JHo+6UzEw1A2AbSNBBNP4hKfXpHcLadIVwuyOxuKjDUWzY5f4dJa3hCMurHXGQ=="],
|
||||
|
||||
"@storybook/addon-vitest": ["@storybook/addon-vitest@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.4.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, "peerDependencies": { "@vitest/browser": "^3.0.0", "@vitest/runner": "^3.0.0", "storybook": "^9.0.17", "vitest": "^3.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/runner", "vitest"] }, "sha512-eogqcGbACR1sTedBSE2SP/4QV1ruicHYEhYjBtoPIjvYgymN1g5KSuQNysLx4f0SvAzczrcNjX2WVVLX2DVyzA=="],
|
||||
"@storybook/addon-vitest": ["@storybook/addon-vitest@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^1.4.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, "peerDependencies": { "@vitest/browser": "^3.0.0", "@vitest/runner": "^3.0.0", "storybook": "^9.0.18", "vitest": "^3.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/runner", "vitest"] }, "sha512-uPLh9H7kRho+raxyIBCm8Ymd3j0VPuWIQ1HSAkdx8itmNafNqs4HE67Z8Cfl259YzdWU/j5BhZqoiT62BCbIDw=="],
|
||||
|
||||
"@storybook/builder-vite": ["@storybook/builder-vite@9.0.17", "", { "dependencies": { "@storybook/csf-plugin": "9.0.17", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.17", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-lyuvgGhb0NaVk1tdB4xwzky6+YXQfxlxfNQqENYZ9uYQZdPfErMa4ZTXVQTV+CQHAa2NL+p/dG2JPAeu39e9UA=="],
|
||||
"@storybook/builder-vite": ["@storybook/builder-vite@9.0.18", "", { "dependencies": { "@storybook/csf-plugin": "9.0.18", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.18", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-lfbrozA6UPVizDrgbPEe04WMtxIraESwUkmwW3+Lxh8rKEUj5cXngcrJUW+meQNNaggdZZWEqeEtweuaLIR+Hg=="],
|
||||
|
||||
"@storybook/csf-plugin": ["@storybook/csf-plugin@9.0.17", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-6Q4eo1ObrLlsnB6bIt6T8+45XAb4to2pQGNrI7QPkLQRLrZinrJcNbLY7AGkyIoCOEsEbq08n09/nClQUbu8HA=="],
|
||||
"@storybook/csf-plugin": ["@storybook/csf-plugin@9.0.18", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.18" } }, "sha512-MQ3WwXnMua5sX0uYyuO7dC5WOWuJCLqf8CsOn3zQ2ptNoH6hD7DFx5ZOa1uD6VxIuJ3LkA+YqfSRBncomJoRnA=="],
|
||||
|
||||
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
|
||||
|
||||
"@storybook/icons": ["@storybook/icons@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" } }, "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA=="],
|
||||
|
||||
"@storybook/react": ["@storybook/react@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.0.17" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17", "typescript": ">= 4.9.x" }, "optionalPeers": ["typescript"] }, "sha512-wssao+uXg72OHtEJdQmmQJGdX90x/aU/6avoP3fgVgepWdZXVgciS9mnqHjKRF/vP+vPOlNQcJjojF/zTtq5qg=="],
|
||||
"@storybook/react": ["@storybook/react@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.0.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18", "typescript": ">= 4.9.x" }, "optionalPeers": ["typescript"] }, "sha512-CCH6Vj/O6I07PrhCHxc1pvCWYMfZhRzK7CVHAtrBP9xxnYA7OoXhM2wymuDogml5HW1BKtyVMeQ3oWZXFNgDXQ=="],
|
||||
|
||||
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@9.0.17", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17" } }, "sha512-ak/x/m6MDDxdE6rCDymTltaiQF3oiKrPHSwfM+YPgQR6MVmzTTs4+qaPfeev7FZEHq23IkfDMTmSTTJtX7Vs9A=="],
|
||||
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@9.0.18", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18" } }, "sha512-qGR/d9x9qWRRxITaBVQkMnb73kwOm+N8fkbZRxc7U4lxupXRvkMIDh247nn71SYVBnvbh6//AL7P6ghiPWZYjA=="],
|
||||
|
||||
"@storybook/react-vite": ["@storybook/react-vite@9.0.17", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.0.17", "@storybook/react": "9.0.17", "find-up": "^7.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.17", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-wx1yKScni4ifOC/ccqpnnpceQbyF2xto+jHGsyua+M4UUCQdS2NYPDR8JFWp1YvBhVt2cQiD6SAltVGM9QLGnQ=="],
|
||||
"@storybook/react-vite": ["@storybook/react-vite@9.0.18", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.0.18", "@storybook/react": "9.0.18", "find-up": "^7.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "^9.0.18", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-dHzUoeY0/S35TvSYxCkPuBlNQZx4Zj9QDhAZ0qdv+nSll++uPgqSe2y2vF+2p+XVYhjDn+YX5LORv00YtuQezg=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||
|
||||
@@ -307,9 +310,9 @@
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="],
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
|
||||
|
||||
"@testing-library/jest-dom": ["@testing-library/jest-dom@6.6.3", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "lodash": "^4.17.21", "redent": "^3.0.0" } }, "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA=="],
|
||||
"@testing-library/jest-dom": ["@testing-library/jest-dom@6.6.4", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "lodash": "^4.17.21", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ=="],
|
||||
|
||||
"@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
|
||||
|
||||
@@ -325,19 +328,21 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/deep-equal": ["@types/deep-equal@1.0.4", "", {}, "sha512-tqdiS4otQP4KmY0PR3u6KbZ5EWvhNdUoS/jc93UuK23C220lOZ/9TvjfxdPcKvqwwDVtmtSCrnr0p/2dirAxkA=="],
|
||||
|
||||
"@types/doctrine": ["@types/doctrine@0.0.9", "", {}, "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
|
||||
@@ -351,7 +356,7 @@
|
||||
|
||||
"@types/resolve": ["@types/resolve@1.20.6", "", {}, "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="],
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"@vitest/browser": ["@vitest/browser@3.2.4", "", { "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", "@vitest/mocker": "3.2.4", "@vitest/utils": "3.2.4", "magic-string": "^0.30.17", "sirv": "^3.0.1", "tinyrainbow": "^2.0.0", "ws": "^8.18.2" }, "peerDependencies": { "playwright": "*", "vitest": "3.2.4", "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" }, "optionalPeers": ["playwright", "webdriverio"] }, "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw=="],
|
||||
|
||||
@@ -371,21 +376,21 @@
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"@volar/language-core": ["@volar/language-core@2.4.19", "", { "dependencies": { "@volar/source-map": "2.4.19" } }, "sha512-i0aLpNA8DYZ2uG05t5K47nUWe+zvvrN9tfz16zS5pCJV9td8F0u+rVAOVSQ1ypufDLUD+ej9BH2/lmug4+lawQ=="],
|
||||
"@volar/language-core": ["@volar/language-core@2.4.22", "", { "dependencies": { "@volar/source-map": "2.4.22" } }, "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg=="],
|
||||
|
||||
"@volar/source-map": ["@volar/source-map@2.4.19", "", {}, "sha512-ttWmO/Ld7r3ebIPPAYvAuSLrlJ96ZALPka44mD4sWA8bw2n9u7TGnMcaTUkiF0GLG8bq/K09beWmEAB1mqMy/A=="],
|
||||
"@volar/source-map": ["@volar/source-map@2.4.22", "", {}, "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA=="],
|
||||
|
||||
"@volar/typescript": ["@volar/typescript@2.4.19", "", { "dependencies": { "@volar/language-core": "2.4.19", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Xgo4QLuqusu2fqw4LCeoOY57d5UCn+fNUWZTg4PFubw07jBFFCSJIuJ7BDrRM3EZHDjCqq1RmUO9wkYihnM+8Q=="],
|
||||
"@volar/typescript": ["@volar/typescript@2.4.22", "", { "dependencies": { "@volar/language-core": "2.4.22", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.17", "", { "dependencies": { "@babel/parser": "^7.27.5", "@vue/shared": "3.5.17", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA=="],
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.18", "", { "dependencies": { "@babel/parser": "^7.28.0", "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.17", "", { "dependencies": { "@vue/compiler-core": "3.5.17", "@vue/shared": "3.5.17" } }, "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ=="],
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.18", "", { "dependencies": { "@vue/compiler-core": "3.5.18", "@vue/shared": "3.5.18" } }, "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A=="],
|
||||
|
||||
"@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="],
|
||||
|
||||
"@vue/language-core": ["@vue/language-core@2.2.0", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.17", "", {}, "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg=="],
|
||||
"@vue/shared": ["@vue/shared@3.5.18", "", {}, "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
@@ -399,18 +404,22 @@
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
||||
|
||||
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
|
||||
@@ -423,18 +432,22 @@
|
||||
|
||||
"browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="],
|
||||
|
||||
"chai": ["chai@5.2.1", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
@@ -469,8 +482,14 @@
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deep-equal": ["deep-equal@2.2.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", "which-typed-array": "^1.1.13" } }, "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
@@ -481,9 +500,11 @@
|
||||
|
||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.185", "", {}, "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.191", "", {}, "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
@@ -493,9 +514,17 @@
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-get-iterator": ["es-get-iterator@1.1.3", "", { "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", "is-string": "^1.0.7", "isarray": "^2.0.5", "stop-iteration-iterator": "^1.0.0" } }, "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.6", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.6", "@esbuild/android-arm": "0.25.6", "@esbuild/android-arm64": "0.25.6", "@esbuild/android-x64": "0.25.6", "@esbuild/darwin-arm64": "0.25.6", "@esbuild/darwin-x64": "0.25.6", "@esbuild/freebsd-arm64": "0.25.6", "@esbuild/freebsd-x64": "0.25.6", "@esbuild/linux-arm": "0.25.6", "@esbuild/linux-arm64": "0.25.6", "@esbuild/linux-ia32": "0.25.6", "@esbuild/linux-loong64": "0.25.6", "@esbuild/linux-mips64el": "0.25.6", "@esbuild/linux-ppc64": "0.25.6", "@esbuild/linux-riscv64": "0.25.6", "@esbuild/linux-s390x": "0.25.6", "@esbuild/linux-x64": "0.25.6", "@esbuild/netbsd-arm64": "0.25.6", "@esbuild/netbsd-x64": "0.25.6", "@esbuild/openbsd-arm64": "0.25.6", "@esbuild/openbsd-x64": "0.25.6", "@esbuild/openharmony-arm64": "0.25.6", "@esbuild/sunos-x64": "0.25.6", "@esbuild/win32-arm64": "0.25.6", "@esbuild/win32-ia32": "0.25.6", "@esbuild/win32-x64": "0.25.6" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg=="],
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="],
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
@@ -527,6 +556,8 @@
|
||||
|
||||
"focus-trap-react": ["focus-trap-react@11.0.4", "", { "dependencies": { "focus-trap": "^7.6.5", "tabbable": "^6.2.0" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-tC7jC/yqeAqhe4irNIzdyDf9XCtGSeECHiBSYJBO/vIN0asizbKZCt8TarB6/XqIceu42ajQ/U4lQJ9pZlWjrg=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
||||
@@ -535,14 +566,30 @@
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
@@ -557,16 +604,50 @@
|
||||
|
||||
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
||||
|
||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
|
||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
||||
|
||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
||||
|
||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
||||
|
||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
||||
|
||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||
|
||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
@@ -579,7 +660,7 @@
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
"jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="],
|
||||
|
||||
@@ -631,7 +712,7 @@
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"loupe": ["loupe@3.1.4", "", {}, "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg=="],
|
||||
"loupe": ["loupe@3.2.0", "", {}, "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
@@ -643,6 +724,8 @@
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||
|
||||
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
|
||||
@@ -671,6 +754,14 @@
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
|
||||
|
||||
"p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="],
|
||||
@@ -709,6 +800,8 @@
|
||||
|
||||
"playwright-core": ["playwright-core@1.54.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
@@ -745,22 +838,38 @@
|
||||
|
||||
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="],
|
||||
"rollup": ["rollup@4.46.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.46.1", "@rollup/rollup-android-arm64": "4.46.1", "@rollup/rollup-darwin-arm64": "4.46.1", "@rollup/rollup-darwin-x64": "4.46.1", "@rollup/rollup-freebsd-arm64": "4.46.1", "@rollup/rollup-freebsd-x64": "4.46.1", "@rollup/rollup-linux-arm-gnueabihf": "4.46.1", "@rollup/rollup-linux-arm-musleabihf": "4.46.1", "@rollup/rollup-linux-arm64-gnu": "4.46.1", "@rollup/rollup-linux-arm64-musl": "4.46.1", "@rollup/rollup-linux-loongarch64-gnu": "4.46.1", "@rollup/rollup-linux-ppc64-gnu": "4.46.1", "@rollup/rollup-linux-riscv64-gnu": "4.46.1", "@rollup/rollup-linux-riscv64-musl": "4.46.1", "@rollup/rollup-linux-s390x-gnu": "4.46.1", "@rollup/rollup-linux-x64-gnu": "4.46.1", "@rollup/rollup-linux-x64-musl": "4.46.1", "@rollup/rollup-win32-arm64-msvc": "4.46.1", "@rollup/rollup-win32-ia32-msvc": "4.46.1", "@rollup/rollup-win32-x64-msvc": "4.46.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
@@ -781,7 +890,9 @@
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"storybook": ["storybook@9.0.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "esbuild-register": "^3.5.0", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./bin/index.cjs" }, "sha512-O+9jgJ+Trlq9VGD1uY4OBLKQWHHDKM/A/pA8vMW6PVehhGHNvpzcIC1bngr6mL5gGHZP2nBv+9XG8pTMcggMmg=="],
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"storybook": ["storybook@9.0.18", "", { "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "esbuild-register": "^3.5.0", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./bin/index.cjs" }, "sha512-ruxpEpizwoYQTt1hBOrWyp9trPYWD9Apt1TJ37rs1rzmNQWpSNGJDMg91JV4mUhBChzRvnid/oRBFFCWJz/dfw=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
@@ -861,7 +972,7 @@
|
||||
|
||||
"use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="],
|
||||
|
||||
"vite": ["vite@7.0.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA=="],
|
||||
"vite": ["vite@7.0.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg=="],
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
@@ -875,6 +986,12 @@
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
@@ -911,11 +1028,11 @@
|
||||
|
||||
"@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
@@ -923,10 +1040,6 @@
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@testing-library/jest-dom/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
|
||||
|
||||
"@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||
|
||||
"@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
@@ -945,8 +1058,6 @@
|
||||
|
||||
"pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"redent/strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
||||
@@ -963,6 +1074,8 @@
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
type AccordionItemPropsPublic,
|
||||
} from "./components/AccordionItem";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
|
||||
export type AccordionProps = HTMLProps<"div"> & {
|
||||
expandedKeys: string[];
|
||||
type?: "multi" | "single";
|
||||
setExpandedKeys(keys: string[]): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
type AccordionType = React.FC<PropsWithChildren<AccordionProps>> & {
|
||||
Item: React.FC<PropsWithChildren<AccordionItemPropsPublic>>;
|
||||
@@ -23,6 +23,7 @@ const Accordion: AccordionType = ({
|
||||
setExpandedKeys,
|
||||
children,
|
||||
type = "multi",
|
||||
testId,
|
||||
...props
|
||||
}) => {
|
||||
const onChange = useCallback(
|
||||
@@ -54,6 +55,7 @@ const Accordion: AccordionType = ({
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-col gap-y-2.5 items-start", className)}
|
||||
data-testid={testId}
|
||||
{...props}
|
||||
>
|
||||
{items}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { Icon, type IconProps } from "../../icon/Icon";
|
||||
import { Typography } from "../../typography/Typography";
|
||||
@@ -10,7 +10,7 @@ export type AccordionHeaderProps = Omit<
|
||||
> & {
|
||||
icon: IconProps["icon"];
|
||||
expanded: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const AccordionHeader = ({
|
||||
className,
|
||||
@@ -43,7 +43,8 @@ export const AccordionHeader = ({
|
||||
// hover modifier
|
||||
"data-[expanded=true]:hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"data-[expanded=false]:focus:bg-light-neutral-900"
|
||||
"data-[expanded=false]:focus:bg-light-neutral-900",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Icon icon={icon} className={cn(iconCss, "w-6 h-6")} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { type IconProps } from "../../icon/Icon";
|
||||
import { AccordionHeader } from "./AccordionHeader";
|
||||
@@ -11,10 +11,10 @@ export type AccordionItemProps = HTMLProps<"div"> & {
|
||||
value: string;
|
||||
label: React.ReactNode;
|
||||
onExpandedChange(value: boolean): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
export type AccordionItemPropsPublic = Omit<
|
||||
AccordionItemProps,
|
||||
"expanded" | "onExpandedChange"
|
||||
"expanded" | "onExpandedChange" | "className" | "style" | "testId"
|
||||
>;
|
||||
|
||||
export const AccordionItem = ({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
|
||||
export type AccordionPanelProps = Omit<HTMLProps<"div">, "aria-expanded"> & {
|
||||
expanded: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const AccordionPanel = ({
|
||||
className,
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import type { PropsWithChildren, ReactElement } from "react";
|
||||
import type { ComponentVariant, HTMLProps } from "../../shared/types";
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type {
|
||||
BaseProps,
|
||||
ComponentVariant,
|
||||
HTMLProps,
|
||||
} from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
import { buttonStyles } from "./utils";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { buttonStyles, useAndApplyBoldTextWidth } from "./utils";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
import "./index.css";
|
||||
|
||||
export type ButtonProps = Omit<HTMLProps<"button">, "aria-disabled"> & {
|
||||
size?: "small" | "large";
|
||||
variant?: ComponentVariant;
|
||||
start?: ReactElement<HTMLProps<"svg">>;
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Button = ({
|
||||
size = "small",
|
||||
@@ -20,39 +28,42 @@ export const Button = ({
|
||||
children,
|
||||
start,
|
||||
end,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ButtonProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
const buttonClassNames = buttonStyles[variant];
|
||||
|
||||
const iconCss = "w-6 h-6";
|
||||
const hasIcons = start || end;
|
||||
const textRef = useAndApplyBoldTextWidth(children, "text-increase-size");
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
aria-disabled={props.disabled ? "true" : "false"}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
size === "small" ? "px-3 py-1.5 min-w-32" : "px-3 py-3 min-w-64",
|
||||
size === "small" ? "px-2 py-3 min-w-32" : "px-3 py-4 min-w-64",
|
||||
"flex flex-row items-center gap-x-8",
|
||||
hasIcons ? " justify-between" : " justify-center",
|
||||
"group enabled:cursor-pointer focus:outline-0",
|
||||
buttonClassNames.button
|
||||
buttonClassNames.button,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{cloneIcon(start, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
<Typography.Text
|
||||
fontSize="l"
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn(
|
||||
"text-center font-size-l font-normal",
|
||||
buttonClassNames.text
|
||||
"tg-family-outfit tg-lg text-center font-normal leading-[100%]",
|
||||
buttonClassNames.text,
|
||||
!props.disabled && `button-bold-text`
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
{cloneIcon(end, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
15
openhands-ui/components/button/index.css
Normal file
15
openhands-ui/components/button/index.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.group .button-bold-text::before,
|
||||
.group .button-bold-text::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: var(--text-increase-size);
|
||||
transition: width 0.2s ease;
|
||||
transition: font-weight 0.2s ease;
|
||||
}
|
||||
|
||||
.group:hover .button-bold-text::before,
|
||||
.group:hover .button-bold-text::after,
|
||||
.group:focus .button-bold-text::before,
|
||||
.group:focus .button-bold-text::after {
|
||||
width: 0;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
import type { ComponentVariant } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
@@ -14,14 +15,22 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-grey-900 enabled:hover:ring-[1.5px]",
|
||||
// focus modifier
|
||||
"enabled:hover:bg-grey-900 enabled:focus:ring-[1.5px]",
|
||||
"enabled:hover:bg-grey-900 enabled:focus-visible:ring-[1.5px]",
|
||||
// active modifier
|
||||
"enabled:active:ring-1",
|
||||
// disabled modifier
|
||||
"disabled:opacity-50",
|
||||
]),
|
||||
icon: cn(["text-primary-500"]),
|
||||
text: cn(["text-primary-500"]),
|
||||
text: cn([
|
||||
"text-primary-500",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
secondary: {
|
||||
button: cn([
|
||||
@@ -29,14 +38,22 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-900 enabled:hover:ring-[1.5px]",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-900 enabled:focus:ring-[1.5px]",
|
||||
"enabled:focus-visible:bg-light-neutral-900 enabled:focus-visible:ring-[1.5px]",
|
||||
// active modifier
|
||||
"enabled:active:ring-1",
|
||||
// disabled modifier
|
||||
"disabled:opacity-50",
|
||||
]),
|
||||
icon: cn(["text-light-neutral-300"]),
|
||||
text: cn(["text-light-neutral-300"]),
|
||||
text: cn([
|
||||
"text-light-neutral-300",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
tertiary: {
|
||||
button: cn([
|
||||
@@ -44,7 +61,7 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
// hover modifier
|
||||
"enabled:hover:bg-grey-900",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-grey-900",
|
||||
"enabled:focus-visible:bg-grey-900",
|
||||
// active modifier
|
||||
"enabled:active:bg-grey-970",
|
||||
// disabled modifier
|
||||
@@ -53,8 +70,38 @@ export const buttonStyles: Record<ComponentVariant, ButtonStyle> = {
|
||||
icon: cn(["text-primary-500"]),
|
||||
text: cn([
|
||||
"text-primary-500 underline",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus-visible:font-semibold",
|
||||
// disabled modifier
|
||||
"group-disabled:no-underline",
|
||||
// active modifier
|
||||
"group-enabled:group-active:font-normal",
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook that calculates and applies a CSS custom property (variable)
|
||||
* based on the length of a text node. Useful for adjusting spacing or layout
|
||||
* to account for changes in font weight, such as bold text rendering wider.
|
||||
*/
|
||||
const BOLD_TEXT_INCREASE = 0.15;
|
||||
export const useAndApplyBoldTextWidth = (
|
||||
textNode: ReactNode,
|
||||
varName: string
|
||||
) => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef) {
|
||||
const charCount =
|
||||
typeof textNode === "string" ? (textNode as string).length : 0;
|
||||
const textIncrease = charCount * BOLD_TEXT_INCREASE;
|
||||
textRef.current!.style.setProperty(`--${varName}`, `${textIncrease}px`);
|
||||
}
|
||||
}, [textRef.current]);
|
||||
|
||||
return textRef;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const CheckboxComponent = (props: CheckboxProps) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [checked, setChecked] = useState(props.checked);
|
||||
return (
|
||||
<Checkbox
|
||||
{...props}
|
||||
@@ -28,6 +28,7 @@ const CheckboxComponent = (props: CheckboxProps) => {
|
||||
|
||||
export const Enabled: Story = {
|
||||
args: {
|
||||
checked: false,
|
||||
label:
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting industry",
|
||||
},
|
||||
@@ -37,6 +38,7 @@ export const Enabled: Story = {
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
checked: false,
|
||||
label:
|
||||
"Lorem Ipsum is simply dummy text of the printing and typesetting industry",
|
||||
},
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { Icon } from "../icon/Icon";
|
||||
|
||||
export type CheckboxProps = HTMLProps<"input"> & {
|
||||
export type CheckboxProps = Omit<
|
||||
HTMLProps<"input">,
|
||||
"checked" | "defaultChecked"
|
||||
> & {
|
||||
label: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
};
|
||||
checked: boolean;
|
||||
} & BaseProps;
|
||||
|
||||
export const Checkbox = ({
|
||||
className,
|
||||
@@ -17,6 +21,7 @@ export const Checkbox = ({
|
||||
disabled,
|
||||
checked,
|
||||
onChange,
|
||||
testId,
|
||||
...props
|
||||
}: CheckboxProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -26,8 +31,10 @@ export const Checkbox = ({
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"flex items-center gap-2 cursor-pointer",
|
||||
disabled && "cursor-not-allowed"
|
||||
disabled && "cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
data-testid={testId}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { type PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { chipStyles, type ChipColor, type ChipVariant } from "./utils";
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
|
||||
export type ChipProps = Omit<HTMLProps<"div">, "label"> & {
|
||||
color?: ChipColor;
|
||||
variant?: ChipVariant;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Chip = ({
|
||||
className,
|
||||
color = "gray",
|
||||
variant = "pill",
|
||||
children,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ChipProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex flex-row items-center px-1.5 py-1",
|
||||
variant === "pill" ? "rounded-full" : "rounded-lg",
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import { useId, type PropsWithChildren } from "react";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Icon } from "../icon/Icon";
|
||||
import { createPortal } from "react-dom";
|
||||
import {
|
||||
FloatingOverlay,
|
||||
FloatingPortal,
|
||||
@@ -24,13 +17,14 @@ import { FocusTrap } from "focus-trap-react";
|
||||
export type DialogProps = HTMLProps<"div"> & {
|
||||
open: boolean;
|
||||
onOpenChange(value: boolean): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Dialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
className,
|
||||
children,
|
||||
testId,
|
||||
}: PropsWithChildren<DialogProps>) => {
|
||||
const id = useId();
|
||||
|
||||
@@ -80,6 +74,7 @@ export const Dialog = ({
|
||||
aria-describedby={`${id}-description`}
|
||||
{...getFloatingProps()}
|
||||
style={styles}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"rounded-4xl border-1 border-light-neutral-500 outline-none",
|
||||
"transition-all will-change-transform",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type DividerProps = Omit<
|
||||
@@ -6,11 +6,12 @@ export type DividerProps = Omit<
|
||||
"role" | "aria-orientation"
|
||||
> & {
|
||||
type?: "horizontal" | "vertical";
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Divider = ({
|
||||
type = "horizontal",
|
||||
className,
|
||||
testId,
|
||||
...props
|
||||
}: DividerProps) => {
|
||||
return (
|
||||
@@ -23,6 +24,7 @@ export const Divider = ({
|
||||
)}
|
||||
role="separator"
|
||||
aria-orientation={type}
|
||||
data-testid={testId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
@@ -19,7 +19,7 @@ export type InputProps = Omit<
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
error?: string;
|
||||
hint?: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Input = ({
|
||||
className,
|
||||
@@ -34,6 +34,7 @@ export const Input = ({
|
||||
type,
|
||||
hint,
|
||||
readOnly,
|
||||
testId,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -45,65 +46,64 @@ export const Input = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={id}
|
||||
<label
|
||||
htmlFor={id}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
{label}
|
||||
</Typography.Text>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
"flex flex-row items-center gap-x-2.5",
|
||||
"py-4.25 px-4",
|
||||
"border-light-neutral-500 border-1 rounded-2xl",
|
||||
// base
|
||||
"bg-light-neutral-950",
|
||||
// hover modifier
|
||||
"hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"focus-within:bg-light-neutral-900",
|
||||
// error state
|
||||
error && " border-red-400 bg-light-neutral-970",
|
||||
readOnly &&
|
||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
||||
// disabled modifier
|
||||
disabled && "hover:bg-light-neutral-950"
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
{label}
|
||||
</Typography.Text>
|
||||
<div
|
||||
{cloneIcon(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
readOnly={readOnly}
|
||||
className={cn(
|
||||
"flex flex-row items-center gap-x-2.5",
|
||||
"py-4.25 px-4",
|
||||
"border-light-neutral-500 border-1 rounded-2xl",
|
||||
// base
|
||||
"bg-light-neutral-950",
|
||||
// hover modifier
|
||||
"hover:bg-light-neutral-900",
|
||||
// focus modifier
|
||||
"focus-within:bg-light-neutral-900",
|
||||
// error state
|
||||
error && " border-red-400 bg-light-neutral-970",
|
||||
readOnly &&
|
||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
||||
// disabled modifier
|
||||
disabled && "hover:bg-light-neutral-950"
|
||||
"flex-1 outline-none caret-primary-500 text-white",
|
||||
"placeholder:text-light-neutral-300",
|
||||
error && "text-red-400"
|
||||
)}
|
||||
>
|
||||
{cloneIcon(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
<input
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-invalid={error ? "true" : "false"}
|
||||
readOnly={readOnly}
|
||||
className={cn(
|
||||
"flex-1 outline-none caret-primary-500 text-white",
|
||||
"placeholder:text-light-neutral-300",
|
||||
error && "text-red-400"
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{cloneIcon(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</div>
|
||||
<Typography.Text
|
||||
fontSize="xs"
|
||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||
>
|
||||
{error ?? hint}
|
||||
</Typography.Text>
|
||||
</label>
|
||||
</div>
|
||||
{...props}
|
||||
/>
|
||||
{cloneIcon(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</div>
|
||||
<Typography.Text
|
||||
fontSize="xs"
|
||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||
>
|
||||
{error ?? hint}
|
||||
</Typography.Text>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ const InteractiveChipComponent = (props: InteractiveChipProps) => {
|
||||
|
||||
export const Elevated: Story = {
|
||||
args: {
|
||||
type: "elevated",
|
||||
chipType: "elevated",
|
||||
disabled: false,
|
||||
},
|
||||
render: InteractiveChipComponent,
|
||||
@@ -37,7 +37,7 @@ export const Elevated: Story = {
|
||||
|
||||
export const Filled: Story = {
|
||||
args: {
|
||||
type: "filled",
|
||||
chipType: "filled",
|
||||
disabled: false,
|
||||
},
|
||||
render: InteractiveChipComponent,
|
||||
|
||||
@@ -1,84 +1,67 @@
|
||||
import {
|
||||
cloneElement,
|
||||
isValidElement,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import { type PropsWithChildren, type ReactElement } from "react";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
|
||||
import { invariant } from "../../shared/utils/invariant";
|
||||
import { interactiveChipStyles, type InteractiveChipType } from "./utils";
|
||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||
import "./index.css";
|
||||
import {
|
||||
buttonStyles,
|
||||
useAndApplyBoldTextWidth,
|
||||
type InteractiveChipType,
|
||||
} from "./utils";
|
||||
|
||||
export type InteractiveChipProps = Omit<
|
||||
HTMLProps<"div">,
|
||||
"label" | "aria-disabled" | "tabIndex"
|
||||
HTMLProps<"button">,
|
||||
"aria-disabled"
|
||||
> & {
|
||||
chipType?: InteractiveChipType;
|
||||
start?: ReactElement<HTMLProps<"svg">>;
|
||||
onStartClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
end?: ReactElement<HTMLProps<"svg">>;
|
||||
onEndClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
type?: InteractiveChipType;
|
||||
disabled?: boolean;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const InteractiveChip = ({
|
||||
chipType = "elevated",
|
||||
className,
|
||||
children,
|
||||
start,
|
||||
end,
|
||||
type = "elevated",
|
||||
children,
|
||||
disabled = false,
|
||||
onStartClick,
|
||||
onEndClick,
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<InteractiveChipProps>) => {
|
||||
invariant(typeof children === "string", "Children must be string");
|
||||
|
||||
const iconCss = cn("w-4 h-4 text-inherit");
|
||||
const buttonCss = cn(disabled ? "cursor-not-allowed" : "cursor-pointer");
|
||||
|
||||
const interactiveChipClassName = interactiveChipStyles[type];
|
||||
const buttonClassNames = buttonStyles[chipType];
|
||||
const iconCss = "w-6 h-6";
|
||||
const hasIcons = start || end;
|
||||
const textRef = useAndApplyBoldTextWidth(children, "text-increase-size");
|
||||
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
{...props}
|
||||
data-disabled={disabled ? "true" : "false"}
|
||||
aria-disabled={disabled ? "true" : "false"}
|
||||
data-testid={testId}
|
||||
aria-disabled={props.disabled ? "true" : "false"}
|
||||
className={cn(
|
||||
"flex flex-row items-center p-1 gap-x-1 rounded-lg",
|
||||
"active:data-[disabled=false]:scale-90 transition",
|
||||
interactiveChipClassName,
|
||||
className
|
||||
"px-1.5 py-1 min-w-32",
|
||||
"flex flex-row items-center gap-x-2",
|
||||
hasIcons ? " justify-between" : " justify-center",
|
||||
"group enabled:cursor-pointer focus:outline-0",
|
||||
buttonClassNames.button
|
||||
)}
|
||||
>
|
||||
{start && isValidElement(start) ? (
|
||||
<button
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={onStartClick}
|
||||
className={cn(buttonCss)}
|
||||
>
|
||||
{cloneElement(start, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</button>
|
||||
) : null}
|
||||
<Typography.Text fontSize="xs" className="text-inherit">
|
||||
{children}
|
||||
</Typography.Text>
|
||||
{cloneIcon(start, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
|
||||
{end && isValidElement(end) ? (
|
||||
<button
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={onEndClick}
|
||||
className={cn(buttonCss)}
|
||||
>
|
||||
{cloneElement(end, {
|
||||
className: iconCss,
|
||||
})}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn(
|
||||
"tg-family-outfit tg-xs text-center font-normal line-1",
|
||||
buttonClassNames.text,
|
||||
!props.disabled && `button-bold-text`
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{cloneIcon(end, {
|
||||
className: cn(iconCss, buttonClassNames.icon),
|
||||
})}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
15
openhands-ui/components/interactive-chip/index.css
Normal file
15
openhands-ui/components/interactive-chip/index.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.group .button-bold-text::before,
|
||||
.group .button-bold-text::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: var(--text-increase-size);
|
||||
transition: width 0.2s ease;
|
||||
transition: font-weight 0.2s ease;
|
||||
}
|
||||
|
||||
.group:hover .button-bold-text::before,
|
||||
.group:hover .button-bold-text::after,
|
||||
.group:focus .button-bold-text::before,
|
||||
.group:focus .button-bold-text::after {
|
||||
width: 0;
|
||||
}
|
||||
@@ -1,30 +1,98 @@
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type InteractiveChipType = "elevated" | "filled";
|
||||
|
||||
export const interactiveChipStyles: Record<InteractiveChipType, string> = {
|
||||
elevated: cn([
|
||||
// base
|
||||
"data-[disabled=false]:border-1 border-light-neutral-400 text-light-neutral-400 bg-light-neutral-950 font-normal",
|
||||
// hover modifier
|
||||
"hover:border-light-neutral-100 hover:data-[disabled=false]:text-light-neutral-100 hover:data-[disabled=false]:font-semibold hover:data-[disabled=false]:bg-light-neutral-800",
|
||||
// focus modifier
|
||||
"focus:border-light-neutral-100 focus:text-light-neutral-100 focus:font-semibold focus:bg-light-neutral-800",
|
||||
// active modifier
|
||||
"active:data-[disabled=false]:border-primary-500 active:data-[disabled=false]:text-primary-500",
|
||||
// disabled modifier
|
||||
"data-[disabled=true]:opacity-50 data-[disabled=true]:bg-light-neutral-900",
|
||||
]),
|
||||
filled: cn([
|
||||
// base
|
||||
"text-grey-985 bg-light-neutral-600 font-normal",
|
||||
// hover modifier
|
||||
"hover:data-[disabled=false]:font-semibold hover:data-[disabled=false]:bg-light-neutral-300",
|
||||
// focus modifier
|
||||
"focus:data-[disabled=false]:font-semibold focus:data-[disabled=false]:bg-light-neutral-300",
|
||||
// active modifier
|
||||
"active:data-[disabled=false]:bg-primary-100",
|
||||
// disabled modifier
|
||||
"data-[disabled=true]:opacity-40 data-[disabled=true]:bg-light-neutral-400",
|
||||
]),
|
||||
type ButtonStyle = {
|
||||
button: string;
|
||||
icon: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export const buttonStyles: Record<InteractiveChipType, ButtonStyle> = {
|
||||
elevated: {
|
||||
button: cn([
|
||||
"ring-1 ring-solid ring-light-neutral-400 rounded-xl bg-light-neutral-950 transition-scale duration-200",
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-800 enabled:hover:ring-light-neutral-15",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-800 enabled:focus:ring-light-neutral-15",
|
||||
// active modifier
|
||||
"enabled:active:ring-primary-500 enabled:active:scale-90 enabled:active:bg-light-neutral-900",
|
||||
// disabled modifier
|
||||
"disabled:opacity-40 disabled:bg-light-neutral-900 disabled:ring-0 disabled:font-medium",
|
||||
]),
|
||||
icon: cn([
|
||||
"text-light-neutral-400",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold group-enabled:group-hover:text-light-neutral-15",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold group-enabled:group-focus:text-light-neutral-15",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-primary-500",
|
||||
]),
|
||||
text: cn([
|
||||
"text-light-neutral-400",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold group-enabled:group-hover:text-light-neutral-15",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold group-enabled:group-focus:text-light-neutral-15",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-primary-500",
|
||||
]),
|
||||
},
|
||||
filled: {
|
||||
button: cn([
|
||||
"rounded-xl bg-light-neutral-600 transition-scale duration-200",
|
||||
// hover modifier
|
||||
"enabled:hover:bg-light-neutral-300",
|
||||
// focus modifier
|
||||
"enabled:focus:bg-light-neutral-300",
|
||||
// active modifier
|
||||
"enabled:active:scale-90 enabled:active:bg-primary-100",
|
||||
// disabled modifier
|
||||
"disabled:opacity-40 disabled:bg-light-neutral-400 disabled:font-medium",
|
||||
]),
|
||||
icon: cn([
|
||||
"text-light-neutral-985",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-light-neutral-970",
|
||||
]),
|
||||
text: cn([
|
||||
"text-light-neutral-985",
|
||||
// hover modifier
|
||||
"group-enabled:group-hover:font-semibold",
|
||||
// focus modifier
|
||||
"group-enabled:group-focus:font-semibold",
|
||||
// active modifier
|
||||
"group-enabled:group-active:text-light-neutral-970",
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook that calculates and applies a CSS custom property (variable)
|
||||
* based on the length of a text node. Useful for adjusting spacing or layout
|
||||
* to account for changes in font weight, such as bold text rendering wider.
|
||||
*/
|
||||
const BOLD_TEXT_INCREASE = 0.15;
|
||||
export const useAndApplyBoldTextWidth = (
|
||||
textNode: ReactNode,
|
||||
varName: string
|
||||
) => {
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef) {
|
||||
const charCount =
|
||||
typeof textNode === "string" ? (textNode as string).length : 0;
|
||||
const textIncrease = charCount * BOLD_TEXT_INCREASE;
|
||||
textRef.current!.style.setProperty(`--${varName}`, `${textIncrease}px`);
|
||||
}
|
||||
}, [textRef.current]);
|
||||
|
||||
return textRef;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps, IOption } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { RadioOption } from "./RadioOption";
|
||||
|
||||
@@ -11,7 +11,7 @@ export type RadioGroupProps<T extends string> = Omit<
|
||||
value: T;
|
||||
onChange: (option: IOption<T>) => void;
|
||||
labelClassName?: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const RadioGroup = <T extends string>({
|
||||
value,
|
||||
@@ -21,13 +21,17 @@ export const RadioGroup = <T extends string>({
|
||||
labelClassName,
|
||||
disabled,
|
||||
id: propId,
|
||||
testId,
|
||||
...props
|
||||
}: RadioGroupProps<T>) => {
|
||||
const generatedId = useId();
|
||||
const id = propId ?? generatedId;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-y-1", className)}>
|
||||
<div
|
||||
data-testid={testId}
|
||||
className={cn("flex flex-col gap-y-1", className)}
|
||||
>
|
||||
{options.map((o) => (
|
||||
<RadioOption
|
||||
{...props}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
@@ -7,7 +7,7 @@ type RadioOptionProps = Omit<HTMLProps<"input">, "id" | "checked"> & {
|
||||
label: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
id: string;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const RadioOption = ({
|
||||
className,
|
||||
@@ -17,6 +17,7 @@ export const RadioOption = ({
|
||||
id: propId,
|
||||
disabled,
|
||||
onChange,
|
||||
testId,
|
||||
...props
|
||||
}: RadioOptionProps) => {
|
||||
const generatedId = useId();
|
||||
@@ -25,6 +26,7 @@ export const RadioOption = ({
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"flex items-center gap-x-4",
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
|
||||
export type ScrollableMode = "auto" | "scroll";
|
||||
@@ -8,7 +8,7 @@ export type ScrollableType = "horizontal" | "vertical";
|
||||
export type ScrollableProps = HTMLProps<"div"> & {
|
||||
mode?: ScrollableMode;
|
||||
type?: ScrollableType;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
const scrollableStyles: Record<
|
||||
ScrollableType,
|
||||
@@ -30,11 +30,13 @@ export const Scrollable = ({
|
||||
tabIndex,
|
||||
mode = "auto",
|
||||
type = "vertical",
|
||||
testId,
|
||||
...props
|
||||
}: PropsWithChildren<ScrollableProps>) => {
|
||||
const style = scrollableStyles[type][mode];
|
||||
return (
|
||||
<div
|
||||
data-testid={testId}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
{...props}
|
||||
className={cn(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import type { HTMLProps, IOption } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import ReactSelect, { createFilter } from "react-select";
|
||||
import { Typography } from "../typography/Typography";
|
||||
@@ -16,7 +16,7 @@ export type SelectProps<T> = Omit<HTMLProps<"input">, "value" | "onChange"> & {
|
||||
options: IOption<T>[];
|
||||
noOptionsText?: string;
|
||||
onChange(value: IOption<T> | null): void;
|
||||
};
|
||||
} & BaseProps;
|
||||
|
||||
export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
const {
|
||||
@@ -32,6 +32,8 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
onChange,
|
||||
readOnly,
|
||||
noOptionsText,
|
||||
className,
|
||||
testId,
|
||||
} = props;
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const generatedId = useId();
|
||||
@@ -50,10 +52,12 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
|
||||
return (
|
||||
<label
|
||||
data-testid={testId}
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"flex flex-col gap-y-2",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import "./index.css";
|
||||
|
||||
@@ -15,7 +15,11 @@ export type IndeterminateSpinnerProps = BaseSpinnerProps & {
|
||||
value?: never;
|
||||
};
|
||||
|
||||
export type SpinnerProps = DeterminateSpinnerProps | IndeterminateSpinnerProps;
|
||||
export type SpinnerProps = (
|
||||
| DeterminateSpinnerProps
|
||||
| IndeterminateSpinnerProps
|
||||
) &
|
||||
BaseProps;
|
||||
|
||||
const SIZE = 48;
|
||||
const STROKE_WIDTH = 6;
|
||||
@@ -26,6 +30,7 @@ export const Spinner = ({
|
||||
value = 10,
|
||||
determinate = false,
|
||||
className,
|
||||
testId,
|
||||
...props
|
||||
}: SpinnerProps) => {
|
||||
const offset = useMemo(
|
||||
@@ -34,7 +39,13 @@ export const Spinner = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<svg width={SIZE} height={SIZE} className={className} {...props}>
|
||||
<svg
|
||||
data-testid={testId}
|
||||
width={SIZE}
|
||||
height={SIZE}
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
cx={SIZE / 2}
|
||||
cy={SIZE / 2}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import React from "react";
|
||||
import {
|
||||
@@ -16,13 +16,13 @@ import { useElementOverflow } from "./hooks/use-element-overflow";
|
||||
import { useElementScroll } from "./hooks/use-element-scroll";
|
||||
import { TabScroller } from "./components/TabScroller";
|
||||
|
||||
export type TabsProps = HTMLProps<"div">;
|
||||
export type TabsProps = HTMLProps<"div"> & BaseProps;
|
||||
|
||||
type TabsType = React.FC<PropsWithChildren<TabsProps>> & {
|
||||
Item: React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
||||
};
|
||||
|
||||
const Tabs: TabsType = ({ children, ...props }) => {
|
||||
const Tabs: TabsType = ({ children, className, testId, ...props }) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const tabListRef = useRef<HTMLDivElement>(null);
|
||||
@@ -55,7 +55,7 @@ const Tabs: TabsType = ({ children, ...props }) => {
|
||||
}) ?? [];
|
||||
|
||||
return (
|
||||
<div className={cn("w-full")}>
|
||||
<div data-testid={testId} className={cn("w-full", className)}>
|
||||
<div className={cn("flex flex-row items-stretch")} ref={containerRef}>
|
||||
{canScrollLeft && isOverflowing && (
|
||||
<TabScroller onScroll={scrollLeft} position="left" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user