Compare commits

..

49 Commits

Author SHA1 Message Date
Graham Neubig
588e838dc4 Fix CLI runtime invalid path error handling (#9814)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-26 08:36:46 +00:00
jpelletier1
2550c08749 docs: Add Known Issues section for Gemini 2.5 Pro (#9909)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-25 14:22:39 -05:00
llamantino
0651c51901 fix(llm_config): extend retry delays to respect rate limit windows (#9489) 2025-07-25 17:26:39 +00:00
bojackli
3ce19993bc Fix typo and remove redundant code in storage module. (#9862) 2025-07-25 18:24:18 +02:00
dependabot[bot]
26a9abbe82 chore(deps): bump the version-all group across 1 directory with 10 updates (#9901)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 18:22:11 +02:00
Ivan Dagelic
240017add1 feat: daytona envs for state management (#9893)
Signed-off-by: Ivan Dagelic <dagelic.ivan@gmail.com>
2025-07-25 17:49:10 +02:00
dependabot[bot]
b5958b069e chore(deps): bump the version-all group in /frontend with 5 updates (#9903)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 19:37:58 +04:00
Mislav Lukach
59b8009d7a fix(ds): add test id support (#9904) 2025-07-25 19:37:25 +04:00
Ryan H. Tran
b8b4f58a79 Update swebench version (#9897) 2025-07-25 22:33:59 +07:00
Engel Nyst
fcb190281c microagent: Add Git best practices (#9335)
Co-authored-by: OpenHands <openhands@all-hands.dev>
2025-07-25 21:45:00 +08:00
Mislav Lukach
9fcf900a23 feat(toast): custom toast component (#9898) 2025-07-25 12:24:17 +00:00
Tim O'Farrell
06ad5e30c9 feat: Optimize git change detection with performance improvement and multi-repository support (#9870)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-24 19:44:25 -06:00
llamantino
739044087b fix(mcp): workaround for ASGI error caused by duplicate http start in mcp (#9891)
Co-authored-by: Xingyao Wang <xingyaoww@gmail.com>
2025-07-24 17:44:03 +00:00
Hiep Le
fa041537c3 feat: Support the “Learn this repo” Button for the Microagent Management Page. (#9873) 2025-07-24 20:30:46 +04:00
dependabot[bot]
079f423a4b chore(deps): bump the version-all group in /frontend with 3 updates (#9883)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-24 18:50:37 +04:00
Vasi
f6060f9c53 feat: [CLI] 9392 cli improve confirmation ux - revisited (#9824)
Co-authored-by: bavg <bavg@ubuntu-server.fritz.box>
2025-07-24 16:13:19 +02:00
Graham Neubig
b7f234641c Fix system prompts to exclude tests for documentation changes (#9880)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-24 09:28:34 -04:00
mamoodi
4ac0af699f Release 0.50.0 (#9868) 2025-07-24 08:59:16 -04:00
Graham Neubig
fb9a941722 docs: Add MCP Cloud availability note and improve document structure (#9801)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: mamoodi <mamoodiha@gmail.com>
2025-07-23 21:40:35 -04:00
Rohit Malhotra
c05339cb2d Update summary prompt to avoid repetition in consecutive summaries (#9834)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-23 20:59:06 -04:00
Cansu
2ef518f063 feat: Add configurable runtime support for issue resolver and fix: Kubernetes pod naming limits (#9877) 2025-07-24 00:12:36 +02:00
Ryan H. Tran
fbd9280239 Add MCP support for CLI (#9519)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
2025-07-23 17:06:01 +00:00
Mislav Lukach
45ac6b839c fix(button): improve font-weight styling (#9819)
Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
2025-07-23 15:37:45 +00:00
Hiep Le
8b59143174 feat: Support the “Learn something new” Button in Microagent Details View. (#9866) 2025-07-23 19:08:36 +04:00
dependabot[bot]
c7b8f5d0d1 chore(deps): bump the version-all group in /frontend with 7 updates (#9869)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 15:02:35 +00:00
dependabot[bot]
09533d3cb9 chore(deps): bump the version-all group across 1 directory with 30 updates (#9852)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 10:49:51 -04:00
Graham Neubig
00582a487c Refactor get_microagents_from_org_or_user error handling (#9865)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-23 14:35:48 +00:00
Graham Neubig
7a168b9b5f Fix Docker runtime port allocation race condition (#9810)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 18:12:31 -04:00
Hiep Le
556ec9ab1a feat(frontend): add responsive UI support for the microagent management page (#9847) 2025-07-22 22:47:40 +04:00
Hiep Le
d567d22748 feat: Handle Click Events for Microagents and Conversations on the Microagent Management Page. (#9853) 2025-07-22 22:01:49 +04:00
Tim O'Farrell
e045b757fa Moved monitoring of last_execution_time to system_stats (#9851)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 11:32:59 -06:00
Hiep Le
38ffc85470 feat(frontend): Integrate with the API to add a new microagent. (#9821) 2025-07-22 16:57:05 +00:00
Xingyao Wang
58ea7b5248 Add make lint to pre-commit hook (#9795)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 12:36:54 -04:00
bojackli
f62ed911d2 Fix: Resolve cross-platform path splitting bug in search (#9732)
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-07-22 18:09:50 +02:00
dependabot[bot]
d13e32bcec chore(deps-dev): bump @types/node from 24.0.15 to 24.1.0 in /frontend in the version-all group (#9848)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 19:20:21 +04:00
Xingyao Wang
b978b71c47 Enhance run-eval workflow: Add release triggers and manual dispatch (#9742)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 23:11:59 +08:00
llamantino
dc2f5cd1b0 fix(cli): filter out LiteLLM coroutine not awaited warning at shutdown (#9842) 2025-07-22 21:53:58 +08:00
mamoodi
07041e057d fix(frontend): Add context menu state management to Controls component (#9841)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 09:49:41 -04:00
mamoodi
6e91d19f80 Fix: Prevent LLM settings from being accessible in SaaS mode via double-click (#9831)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 09:49:31 -04:00
dependabot[bot]
936510e219 chore(deps): bump the version-all group in /frontend with 2 updates (#9829)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 17:41:02 +04:00
Boxuan Li
7af35ab827 Evaluation: disable browser when NOT run_with_browsing (#9837) 2025-07-22 01:45:52 +00:00
Xingyao Wang
a7245f2de2 fix(CLI): alias persistence issue (#9828)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-07-22 05:45:14 +08:00
Tim O'Farrell
6d7ab8a022 Fix for issue where some cases use WORK_PORT and some use APP_PORT (#9830) 2025-07-21 20:24:24 +00:00
Hiep Le
bbfa37fd97 feat(frontend): Allow searching/filtering repositories. (#9791) 2025-07-21 16:05:32 +00:00
dependabot[bot]
d0cf12e474 chore(deps-dev): bump the eslint group in /frontend with 3 updates (#9825)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 16:02:35 +00:00
sp.wack
78306b1ee7 hotfix(frontend): Fix context menu closing (#9822) 2025-07-21 19:44:08 +04:00
sp.wack
f6d99234f1 fix(frontend): Fix auth modal tests by adding required providersConfigured prop (#9823) 2025-07-21 19:40:54 +04:00
Boxuan Li
19ca52f954 Skip browser dependency build in Dockerfile when browser is disabled (#9815) 2025-07-21 08:34:11 -07:00
Hiep Le
df75116184 feat(frontend): Integrate with API to display repositories and their associated microagents. (#9784)
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
2025-07-21 19:19:34 +04:00
189 changed files with 11919 additions and 2972 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -1,29 +1,32 @@
#!/bin/bash
echo "Running OpenHands pre-commit hook..."
echo "This hook runs 'make lint' to ensure code quality before committing."
# Store the exit code to return at the end
# This allows us to be additive to existing pre-commit hooks
EXIT_CODE=0
# Run make lint to check both frontend and backend code
echo "Running linting checks with 'make lint'..."
make lint
if [ $? -ne 0 ]; then
echo "Linting failed. Please fix the issues before committing."
EXIT_CODE=1
else
echo "Linting checks passed!"
fi
# 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..."
echo "Frontend changes detected. Running additional frontend checks..."
# Check if frontend directory exists
if [ -d "frontend" ]; then
# Change to frontend directory
cd frontend || exit 1
# Run lint:fix
echo "Running npm lint:fix..."
npm run lint:fix
if [ $? -ne 0 ]; then
echo "Frontend linting failed. Please fix the issues before committing."
EXIT_CODE=1
fi
# Run build
echo "Running npm build..."
npm run build
@@ -50,7 +53,7 @@ if [ -n "$frontend_changes" ]; then
echo "Frontend directory not found. Skipping frontend checks."
fi
else
echo "No frontend changes detected. Skipping frontend checks."
echo "No frontend changes detected. Skipping additional frontend checks."
fi
# Run any existing pre-commit hooks that might have been installed by the user

View File

@@ -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.50-nikolaik`
## Develop inside Docker container

View File

@@ -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.50-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.50-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.50
```
> **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.

View File

@@ -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.50-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.50-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.50
```
> **注意**: 如果您在0.44版本之前使用过OpenHands您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。

View File

@@ -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.50-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.50-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.50
```
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。

View File

@@ -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.50-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:

View File

@@ -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.50-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:

View File

@@ -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.50-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.50 \
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.

View File

@@ -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.50-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.50 \
python -m openhands.core.main -t "write a bash script that prints hi"
```

View File

@@ -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

View File

@@ -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.50-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.50-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.50
```
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.50
Starting OpenHands...
Running OpenHands as root
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None

View File

@@ -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.50-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.50-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.50
```
> **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.

View File

@@ -10,6 +10,25 @@ 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:
@@ -104,21 +123,6 @@ 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:
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.
## Transport Protocols
OpenHands supports three different MCP transport protocols:

View File

@@ -117,6 +117,7 @@ def get_config(
default_agent=metadata.agent_class,
run_as_openhands=False,
max_iterations=metadata.max_iterations,
enable_browser=RUN_WITH_BROWSING,
runtime=os.environ.get('RUNTIME', 'docker'),
sandbox=sandbox_config,
# do not mount workspace

View File

@@ -345,6 +345,7 @@ def get_config(
default_agent=metadata.agent_class,
run_as_openhands=False,
max_iterations=metadata.max_iterations,
enable_browser=RUN_WITH_BROWSING,
runtime=os.environ.get('RUNTIME', 'docker'),
sandbox=sandbox_config,
# do not mount workspace

View File

@@ -226,6 +226,7 @@ def get_config(
default_agent=metadata.agent_class,
run_as_openhands=False,
max_iterations=metadata.max_iterations,
enable_browser=RUN_WITH_BROWSING,
runtime=os.environ.get('RUNTIME', 'docker'),
sandbox=sandbox_config,
# do not mount workspace

View File

@@ -203,6 +203,7 @@ def get_config(
default_agent=metadata.agent_class,
run_as_openhands=False,
max_iterations=metadata.max_iterations,
enable_browser=RUN_WITH_BROWSING,
runtime=os.environ.get('RUNTIME', 'docker'),
sandbox=sandbox_config,
# do not mount workspace

View File

@@ -164,6 +164,7 @@ def get_config(
default_agent=metadata.agent_class,
run_as_openhands=False,
max_iterations=metadata.max_iterations,
enable_browser=RUN_WITH_BROWSING,
runtime=os.environ.get('RUNTIME', 'docker'),
sandbox=sandbox_config,
# do not mount workspace

View File

@@ -19,7 +19,13 @@ describe("AuthModal", () => {
});
it("should render the GitHub and GitLab buttons", () => {
render(<AuthModal githubAuthUrl="mock-url" appMode="saas" />);
render(
<AuthModal
githubAuthUrl="mock-url"
appMode="saas"
providersConfigured={["github", "gitlab"]}
/>,
);
const githubButton = screen.getByRole("button", {
name: "GITHUB$CONNECT_TO_GITHUB",
@@ -35,7 +41,13 @@ describe("AuthModal", () => {
it("should redirect to GitHub auth URL when GitHub button is clicked", async () => {
const user = userEvent.setup();
const mockUrl = "https://github.com/login/oauth/authorize";
render(<AuthModal githubAuthUrl={mockUrl} appMode="saas" />);
render(
<AuthModal
githubAuthUrl={mockUrl}
appMode="saas"
providersConfigured={["github"]}
/>,
);
const githubButton = screen.getByRole("button", {
name: "GITHUB$CONNECT_TO_GITHUB",
@@ -52,7 +64,6 @@ describe("AuthModal", () => {
const termsSection = screen.getByTestId("auth-modal-terms-of-service");
expect(termsSection).toBeInTheDocument();
// Check that all text content is present in the paragraph
expect(termsSection).toHaveTextContent(
"AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR",

View File

@@ -16,8 +16,6 @@ import { ConversationCard } from "#/components/features/conversation-panel/conve
import { clickOnEditButton } from "./utils";
// We'll use the actual i18next implementation but override the translation function
import { I18nextProvider } from "react-i18next";
import i18n from "i18next";
// Mock the t function to return our custom translations
vi.mock("react-i18next", async () => {
@@ -124,7 +122,8 @@ describe("ConversationCard", () => {
it("should toggle a context menu when clicking the ellipsis button", async () => {
const user = userEvent.setup();
renderWithProviders(
const onContextMenuToggle = vi.fn();
const { rerender } = renderWithProviders(
<ConversationCard
onDelete={onDelete}
onChangeTitle={onChangeTitle}
@@ -132,6 +131,8 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen={false}
onContextMenuToggle={onContextMenuToggle}
/>,
);
@@ -140,15 +141,32 @@ describe("ConversationCard", () => {
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
expect(onContextMenuToggle).toHaveBeenCalledWith(true);
// Simulate context menu being opened by parent
rerender(
<ConversationCard
onDelete={onDelete}
onChangeTitle={onChangeTitle}
isActive
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
screen.getByTestId("context-menu");
await user.click(ellipsisButton);
expect(screen.queryByTestId("context-menu")).not.toBeInTheDocument();
expect(onContextMenuToggle).toHaveBeenCalledWith(false);
});
it("should call onDelete when the delete button is clicked", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -157,18 +175,18 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
const menu = screen.getByTestId("context-menu");
const deleteButton = within(menu).getByTestId("delete-button");
await user.click(deleteButton);
expect(onDelete).toHaveBeenCalled();
expect(onContextMenuToggle).toHaveBeenCalledWith(false);
});
test("clicking the selectedRepository should not trigger the onClick handler", async () => {
@@ -198,7 +216,11 @@ describe("ConversationCard", () => {
test("conversation title should call onChangeTitle when changed and blurred", async () => {
const user = userEvent.setup();
renderWithProviders(
let menuOpen = true;
const onContextMenuToggle = vi.fn((isOpen: boolean) => {
menuOpen = isOpen;
});
const { rerender } = renderWithProviders(
<ConversationCard
onDelete={onDelete}
isActive
@@ -206,10 +228,27 @@ describe("ConversationCard", () => {
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
onChangeTitle={onChangeTitle}
contextMenuOpen={menuOpen}
onContextMenuToggle={onContextMenuToggle}
/>,
);
await clickOnEditButton(user);
// Re-render with updated state
rerender(
<ConversationCard
onDelete={onDelete}
isActive
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
onChangeTitle={onChangeTitle}
contextMenuOpen={menuOpen}
onContextMenuToggle={onContextMenuToggle}
/>,
);
const title = screen.getByTestId("conversation-card-title");
expect(title).toBeEnabled();
@@ -227,6 +266,7 @@ describe("ConversationCard", () => {
it("should reset title and not call onChangeTitle when the title is empty", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -235,6 +275,8 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
@@ -271,6 +313,7 @@ describe("ConversationCard", () => {
test("clicking the title should not trigger the onClick handler if edit mode", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -279,6 +322,8 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
@@ -292,6 +337,7 @@ describe("ConversationCard", () => {
test("clicking the delete button should not trigger the onClick handler", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -300,12 +346,11 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
const menu = screen.getByTestId("context-menu");
const deleteButton = within(menu).getByTestId("delete-button");
@@ -315,7 +360,7 @@ describe("ConversationCard", () => {
});
it("should show display cost button only when showOptions is true", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
const { rerender } = renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -324,21 +369,17 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
// Wait for context menu to appear
const menu = await screen.findByTestId("context-menu");
expect(
within(menu).queryByTestId("display-cost-button"),
).not.toBeInTheDocument();
// Close menu
await user.click(ellipsisButton);
rerender(
<ConversationCard
onDelete={onDelete}
@@ -348,12 +389,11 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
// Open menu again
await user.click(ellipsisButton);
// Wait for context menu to appear and check for display cost button
const newMenu = await screen.findByTestId("context-menu");
within(newMenu).getByTestId("display-cost-button");
@@ -361,6 +401,7 @@ describe("ConversationCard", () => {
it("should show metrics modal when clicking the display cost button", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
renderWithProviders(
<ConversationCard
onDelete={onDelete}
@@ -370,12 +411,11 @@ describe("ConversationCard", () => {
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
showOptions
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
const menu = screen.getByTestId("context-menu");
const displayCostButton = within(menu).getByTestId("display-cost-button");
@@ -386,7 +426,7 @@ describe("ConversationCard", () => {
});
it("should not display the edit or delete options if the handler is not provided", async () => {
const user = userEvent.setup();
const onContextMenuToggle = vi.fn();
const { rerender } = renderWithProviders(
<ConversationCard
onClick={onClick}
@@ -394,19 +434,15 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
const ellipsisButton = screen.getByTestId("ellipsis-button");
await user.click(ellipsisButton);
const menu = await screen.findByTestId("context-menu");
expect(within(menu).queryByTestId("edit-button")).toBeInTheDocument();
expect(within(menu).queryByTestId("delete-button")).not.toBeInTheDocument();
// toggle to hide the context menu
await user.click(ellipsisButton);
rerender(
<ConversationCard
onClick={onClick}
@@ -414,10 +450,11 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
contextMenuOpen
onContextMenuToggle={onContextMenuToggle}
/>,
);
await user.click(ellipsisButton);
const newMenu = await screen.findByTestId("context-menu");
expect(
within(newMenu).queryByTestId("edit-button"),

View File

@@ -1,32 +1,32 @@
{
"name": "openhands-frontend",
"version": "0.49.0",
"version": "0.50.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openhands-frontend",
"version": "0.49.0",
"version": "0.50.0",
"dependencies": {
"@heroui/react": "^2.8.1",
"@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.0",
"@stripe/stripe-js": "^7.6.1",
"@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.9",
"i18next": "^25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
@@ -34,16 +34,16 @@
"jose": "^6.0.12",
"lucide-react": "^0.525.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.257.0",
"posthog-js": "^1.258.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"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",
@@ -51,24 +51,24 @@
"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"
},
"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/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.0.14",
"@types/node": "^24.1.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-highlight": "^0.12.8",
@@ -82,11 +82,11 @@
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-i18next": "^6.1.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-i18next": "^6.1.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-unused-imports": "^4.1.4",
@@ -630,9 +630,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.28.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
"version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
@@ -1399,6 +1399,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/accordion/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/alert": {
"version": "2.2.23",
"resolved": "https://registry.npmjs.org/@heroui/alert/-/alert-2.2.23.tgz",
@@ -1433,6 +1441,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/aria-utils/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/autocomplete": {
"version": "2.3.25",
"resolved": "https://registry.npmjs.org/@heroui/autocomplete/-/autocomplete-2.3.25.tgz",
@@ -1463,6 +1479,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/autocomplete/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/avatar": {
"version": "2.2.19",
"resolved": "https://registry.npmjs.org/@heroui/avatar/-/avatar-2.2.19.tgz",
@@ -1537,6 +1561,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/button/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/calendar": {
"version": "2.2.23",
"resolved": "https://registry.npmjs.org/@heroui/calendar/-/calendar-2.2.23.tgz",
@@ -1570,6 +1602,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/calendar/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/card": {
"version": "2.2.22",
"resolved": "https://registry.npmjs.org/@heroui/card/-/card-2.2.22.tgz",
@@ -1591,6 +1631,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/card/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/checkbox": {
"version": "2.3.23",
"resolved": "https://registry.npmjs.org/@heroui/checkbox/-/checkbox-2.3.23.tgz",
@@ -1616,6 +1664,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/checkbox/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/chip": {
"version": "2.2.19",
"resolved": "https://registry.npmjs.org/@heroui/chip/-/chip-2.2.19.tgz",
@@ -1671,6 +1727,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/date-input/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/date-picker": {
"version": "2.3.24",
"resolved": "https://registry.npmjs.org/@heroui/date-picker/-/date-picker-2.3.24.tgz",
@@ -1701,6 +1765,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/date-picker/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/divider": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/@heroui/divider/-/divider-2.2.16.tgz",
@@ -1716,6 +1788,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/divider/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/dom-animation": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@heroui/dom-animation/-/dom-animation-2.1.10.tgz",
@@ -1783,6 +1863,14 @@
"react-dom": ">=18"
}
},
"node_modules/@heroui/form/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/framer-utils": {
"version": "2.1.19",
"resolved": "https://registry.npmjs.org/@heroui/framer-utils/-/framer-utils-2.1.19.tgz",
@@ -1861,6 +1949,14 @@
"react-dom": ">=18"
}
},
"node_modules/@heroui/input/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/kbd": {
"version": "2.2.18",
"resolved": "https://registry.npmjs.org/@heroui/kbd/-/kbd-2.2.18.tgz",
@@ -1919,6 +2015,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/listbox/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/menu": {
"version": "2.2.22",
"resolved": "https://registry.npmjs.org/@heroui/menu/-/menu-2.2.22.tgz",
@@ -1943,6 +2047,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/menu/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/modal": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/@heroui/modal/-/modal-2.2.20.tgz",
@@ -2024,6 +2136,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/number-input/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/pagination": {
"version": "2.2.21",
"resolved": "https://registry.npmjs.org/@heroui/pagination/-/pagination-2.2.21.tgz",
@@ -2116,6 +2236,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/radio/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/react": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@heroui/react/-/react-2.8.1.tgz",
@@ -2263,6 +2391,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/select/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/shared-icons": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@heroui/shared-icons/-/shared-icons-2.1.10.tgz",
@@ -2415,6 +2551,14 @@
"react": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/system-rsc/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/system-rsc/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
@@ -2474,6 +2618,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/tabs/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/theme": {
"version": "2.4.19",
"resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.19.tgz",
@@ -2573,6 +2725,14 @@
"react": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/use-aria-accordion/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/use-aria-button": {
"version": "2.2.17",
"resolved": "https://registry.npmjs.org/@heroui/use-aria-button/-/use-aria-button-2.2.17.tgz",
@@ -2588,6 +2748,14 @@
"react": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/use-aria-button/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/use-aria-link": {
"version": "2.2.18",
"resolved": "https://registry.npmjs.org/@heroui/use-aria-link/-/use-aria-link-2.2.18.tgz",
@@ -2603,6 +2771,14 @@
"react": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/use-aria-link/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/use-aria-modal-overlay": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/@heroui/use-aria-modal-overlay/-/use-aria-modal-overlay-2.2.16.tgz",
@@ -2642,6 +2818,14 @@
"react-dom": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/use-aria-multiselect/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/use-aria-overlay": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@heroui/use-aria-overlay/-/use-aria-overlay-2.0.1.tgz",
@@ -2657,6 +2841,14 @@
"react-dom": ">=18"
}
},
"node_modules/@heroui/use-aria-overlay/node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@heroui/use-callback-ref": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@heroui/use-callback-ref/-/use-callback-ref-2.1.8.tgz",
@@ -4103,9 +4295,9 @@
}
},
"node_modules/@react-router/dev": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.7.0.tgz",
"integrity": "sha512-z6tJ0US20pS/YpaPz59SJgSH+1BJ6xvQmQ/u4Y4HM1uLOa4b3Mleg3KujqAvwGP5wkMkNFz3Ae2g6/kDTFyuCA==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.7.1.tgz",
"integrity": "sha512-ByfgHmAyfx/JQYN/QwUx1sFJlBA5Z3HQAZ638wHSb+m6khWtHqSaKCvPqQh1P00wdEAeV3tX5L1aUM/ceCF6+w==",
"dev": true,
"dependencies": {
"@babel/core": "^7.27.7",
@@ -4116,7 +4308,7 @@
"@babel/traverse": "^7.27.7",
"@babel/types": "^7.27.7",
"@npmcli/package-json": "^4.0.1",
"@react-router/node": "7.7.0",
"@react-router/node": "7.7.1",
"arg": "^5.0.1",
"babel-dead-code-elimination": "^1.0.6",
"chokidar": "^4.0.0",
@@ -4128,7 +4320,7 @@
"lodash": "^4.17.21",
"pathe": "^1.1.2",
"picocolors": "^1.1.1",
"prettier": "^2.7.1",
"prettier": "^3.6.2",
"react-refresh": "^0.14.0",
"semver": "^7.3.7",
"set-cookie-parser": "^2.6.0",
@@ -4143,8 +4335,8 @@
"node": ">=20.0.0"
},
"peerDependencies": {
"@react-router/serve": "^7.7.0",
"react-router": "^7.7.0",
"@react-router/serve": "^7.7.1",
"react-router": "^7.7.1",
"typescript": "^5.1.0",
"vite": "^5.1.0 || ^6.0.0 || ^7.0.0",
"wrangler": "^3.28.2 || ^4.0.0"
@@ -4174,26 +4366,10 @@
"node": ">=6"
}
},
"node_modules/@react-router/dev/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/@react-router/node": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.7.0.tgz",
"integrity": "sha512-PTl4C+QjWsbTfp+9mybOzIH10ZM/pjZrAlcoxc/KGYxcfWDEe2GDFFBQN6nGZgJe/0SwSjHsVwqo2haMKgTbvQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.7.1.tgz",
"integrity": "sha512-EHd6PEcw2nmcJmcYTPA0MmRWSqOaJ/meycfCp0ADA9T/6b7+fUHfr9XcNyf7UeZtYwu4zGyuYfPmLU5ic6Ugyg==",
"dependencies": {
"@mjackson/node-fetch-server": "^0.2.0"
},
@@ -4201,7 +4377,7 @@
"node": ">=20.0.0"
},
"peerDependencies": {
"react-router": "7.7.0",
"react-router": "7.7.1",
"typescript": "^5.1.0"
},
"peerDependenciesMeta": {
@@ -4211,12 +4387,12 @@
}
},
"node_modules/@react-router/serve": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.7.0.tgz",
"integrity": "sha512-XvJAY4Sgv7HxdSuLgkBP8bFXxfI97HJSk+p2BisdtK6JT/nSZugEe0gju4xAkgtsncNJJBVndJTcfUtTDNLTUQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.7.1.tgz",
"integrity": "sha512-LyAiX+oI+6O6j2xWPUoKW+cgayUf3USBosSMv73Jtwi99XUhSDu2MUhM+BB+AbrYRubauZ83QpZTROiXoaf8jA==",
"dependencies": {
"@react-router/express": "7.7.0",
"@react-router/node": "7.7.0",
"@react-router/express": "7.7.1",
"@react-router/node": "7.7.1",
"compression": "^1.7.4",
"express": "^4.19.2",
"get-port": "5.1.1",
@@ -4230,22 +4406,22 @@
"node": ">=20.0.0"
},
"peerDependencies": {
"react-router": "7.7.0"
"react-router": "7.7.1"
}
},
"node_modules/@react-router/serve/node_modules/@react-router/express": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.7.0.tgz",
"integrity": "sha512-R86v1qAbj3i/tG00gFM90P3nXR+B66qkp3bbaqm9VnTkbkqUCcHnVaQn64qBOl5g34FdJUMt84UsLS6v2mT/iQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.7.1.tgz",
"integrity": "sha512-OEZwIM7i/KPSDjwVRg3LqeNIwG41U+SeFOwMjhZRFfyrnwghHfvWsDajf73r4ccMh+RRHcP1GIN6VSU3XZk7MA==",
"dependencies": {
"@react-router/node": "7.7.0"
"@react-router/node": "7.7.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"express": "^4.17.1 || ^5",
"react-router": "7.7.0",
"react-router": "7.7.1",
"typescript": "^5.1.0"
},
"peerDependenciesMeta": {
@@ -4802,10 +4978,9 @@
}
},
"node_modules/@react-types/shared": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.30.0.tgz",
"integrity": "sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==",
"license": "Apache-2.0",
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.31.0.tgz",
"integrity": "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
@@ -5238,10 +5413,9 @@
"license": "MIT"
},
"node_modules/@stripe/react-stripe-js": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.7.0.tgz",
"integrity": "sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==",
"license": "MIT",
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.8.0.tgz",
"integrity": "sha512-yd130NpwvJpXK+Tleg5P3iYcg/pwFmPDMbrpBOi++S0h/dQtztsCktJswdAcaBMW143V7mj4PBhBANePP5nAzA==",
"dependencies": {
"prop-types": "^15.7.2"
},
@@ -5252,9 +5426,9 @@
}
},
"node_modules/@stripe/stripe-js": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.5.0.tgz",
"integrity": "sha512-Cq3KKe+G1o7PSBMbmrgpT2JgBeyH2THHr3RdIX2MqF7AnBuspIMgtZ3ktcCgP7kZsTMvnmWymr7zZCT1zeWbMw==",
"version": "7.6.1",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.6.1.tgz",
"integrity": "sha512-BUDj5gujbtx53/Cexws0+aPrEBsKAN8ExPf9UfuTCivVU6ug2PjqI0zUeL1jon3795eOLlyqvCDjp6VNknjE0A==",
"engines": {
"node": ">=12.16"
}
@@ -6160,9 +6334,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"devOptional": true,
"dependencies": {
"undici-types": "~7.8.0"
@@ -7213,8 +7387,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.21",
@@ -7281,13 +7454,12 @@
}
},
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"license": "MIT",
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -7908,7 +8080,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -8375,7 +8546,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
@@ -9017,11 +9187,10 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.5",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"version": "10.1.8",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -9083,11 +9252,10 @@
}
},
"node_modules/eslint-plugin-i18next": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.1.2.tgz",
"integrity": "sha512-hvTmws4kouNHkk314+9MHNj+RQmsqrkejWhTXGlRC0j8H+EXq2qDRLe6UqIjrFZo7/ogyd4btuqsnKCBi8wHbw==",
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.1.3.tgz",
"integrity": "sha512-z/h4oBRd9wI1ET60HqcLSU6XPeAh/EPOrBBTyCdkWeMoYrWAaUVA+DOQkWTiNIyCltG4NTmy62SQisVXxoXurw==",
"dev": true,
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21",
"requireindex": "~1.1.0"
@@ -9252,11 +9420,10 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz",
"integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.7"
@@ -9893,10 +10060,9 @@
}
},
"node_modules/form-data": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"license": "MIT",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -9939,11 +10105,11 @@
}
},
"node_modules/framer-motion": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.6.tgz",
"integrity": "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==",
"version": "12.23.9",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.9.tgz",
"integrity": "sha512-TqEHXj8LWfQSKqfdr5Y4mYltYLw96deu6/K9kGDd+ysqRJPNwF9nb5mZcrLmybHbU7gcJ+HQar41U3UTGanbbQ==",
"dependencies": {
"motion-dom": "^12.23.6",
"motion-dom": "^12.23.9",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
@@ -13414,9 +13580,9 @@
}
},
"node_modules/motion-dom": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.6.tgz",
"integrity": "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==",
"version": "12.23.9",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.9.tgz",
"integrity": "sha512-6Sv++iWS8XMFCgU1qwKj9l4xuC47Hp4+2jvPfyTXkqDg2tTzSgX6nWKD4kNFXk0k7llO59LZTPuJigza4A2K1A==",
"dependencies": {
"motion-utils": "^12.23.6"
}
@@ -14268,10 +14434,9 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.257.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.257.0.tgz",
"integrity": "sha512-Ujg9RGtWVCu+4tmlRpALSy2ZOZI6JtieSYXIDDdgMWm167KYKvTtbMPHdoBaPWcNu0Km+1hAIBnQFygyn30KhA==",
"license": "SEE LICENSE IN LICENSE",
"version": "1.258.2",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.258.2.tgz",
"integrity": "sha512-XBSeiN4HjiYsy3tW5zss8WOJF2JXTQXAYw2wZ+zjqQuzzi7kkLEXjIgsVrBnt5Opwhqn0krZVsb0ZBw34dIiyQ==",
"dependencies": {
"core-js": "^3.38.1",
"fflate": "^0.4.8",
@@ -14635,10 +14800,9 @@
}
},
"node_modules/react-i18next": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz",
"integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==",
"license": "MIT",
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.1.tgz",
"integrity": "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg==",
"dependencies": {
"@babel/runtime": "^7.27.6",
"html-parse-stringify": "^3.0.1"
@@ -14743,9 +14907,9 @@
}
},
"node_modules/react-router": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz",
"integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.1.tgz",
"integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@@ -17193,13 +17357,13 @@
}
},
"node_modules/vite": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.6",
"picomatch": "^4.0.2",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.40.0",
"tinyglobby": "^0.2.14"
@@ -17359,10 +17523,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"engines": {
"node": ">=12"
},

View File

@@ -1,6 +1,6 @@
{
"name": "openhands-frontend",
"version": "0.49.0",
"version": "0.50.0",
"private": true,
"type": "module",
"engines": {
@@ -10,22 +10,22 @@
"@heroui/react": "^2.8.1",
"@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.0",
"@stripe/stripe-js": "^7.6.1",
"@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.9",
"i18next": "^25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
@@ -33,16 +33,16 @@
"jose": "^6.0.12",
"lucide-react": "^0.525.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.257.0",
"posthog-js": "^1.258.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"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,17 +82,17 @@
"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/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.0.14",
"@types/node": "^24.1.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-highlight": "^0.12.8",
@@ -106,11 +106,11 @@
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-i18next": "^6.1.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-i18next": "^6.1.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-unused-imports": "^4.1.4",

View File

@@ -13,11 +13,13 @@ import {
GitChange,
GetMicroagentsResponse,
GetMicroagentPromptResponse,
CreateMicroagent,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { ApiSettings, PostApiSettings, Provider } from "#/types/settings";
import { GitUser, GitRepository, Branch } from "#/types/git";
import { SuggestedTask } from "#/components/features/home/tasks/task.types";
import { RepositoryMicroagent } from "#/types/microagent-management";
class OpenHands {
private static currentConversation: Conversation | null = null;
@@ -250,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}`);
}
@@ -261,6 +285,7 @@ class OpenHands {
suggested_task?: SuggestedTask,
selected_branch?: string,
conversationInstructions?: string,
createMicroagent?: CreateMicroagent,
): Promise<Conversation> {
const body = {
repository: selectedRepository,
@@ -269,6 +294,7 @@ class OpenHands {
initial_user_msg: initialUserMsg,
suggested_task,
conversation_instructions: conversationInstructions,
create_microagent: createMicroagent,
};
const { data } = await openHands.post<Conversation>(
@@ -464,6 +490,22 @@ class OpenHands {
return data;
}
/**
* Get the available microagents for a specific repository
* @param owner The repository owner
* @param repo The repository name
* @returns The available microagents for the repository
*/
static async getRepositoryMicroagents(
owner: string,
repo: string,
): Promise<RepositoryMicroagent[]> {
const { data } = await openHands.get<RepositoryMicroagent[]>(
`/api/user/repository/${owner}/${repo}/microagents`,
);
return data;
}
static async getMicroagentPrompt(
conversationId: string,
eventId: number,
@@ -489,24 +531,6 @@ class OpenHands {
return data;
}
/**
* Get the GitHub user installation IDs
* @returns List of GitHub installation IDs
*/
static async getGitHubUserInstallationIds(): Promise<string[]> {
const { data } = await openHands.get<string[]>("/github/installations");
return data;
}
/**
* Get the BitBucket workspaces
* @returns List of BitBucket workspaces
*/
static async getBitBucketWorkspaces(): Promise<string[]> {
const { data } = await openHands.get<string[]>("/bitbucket/installations");
return data;
}
}
export default OpenHands;

View File

@@ -79,7 +79,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 +98,7 @@ export interface Conversation {
trigger?: ConversationTrigger;
url: string | null;
session_api_key: string | null;
pr_number?: number[] | null;
}
export interface ResultSet<T> {
@@ -133,3 +138,9 @@ export interface GetMicroagentPromptResponse {
status: string;
prompt: string;
}
export interface CreateMicroagent {
repo: string;
git_provider?: Provider;
title?: string;
}

View File

@@ -13,6 +13,7 @@ interface ControlsProps {
export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) {
const { data: conversation } = useActiveConversation();
const [contextMenuOpen, setContextMenuOpen] = React.useState(false);
return (
<div className="flex flex-col gap-2 md:items-center md:justify-between md:flex-row">
@@ -37,6 +38,8 @@ export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) {
}}
conversationStatus={conversation?.status}
conversationId={conversation?.conversation_id}
contextMenuOpen={contextMenuOpen}
onContextMenuToggle={setContextMenuOpen}
/>
</div>
);

View File

@@ -35,6 +35,8 @@ interface ConversationCardProps {
conversationStatus?: ConversationStatus;
variant?: "compact" | "default";
conversationId?: string; // Optional conversation ID for VS Code URL
contextMenuOpen?: boolean;
onContextMenuToggle?: (isOpen: boolean) => void;
}
const MAX_TIME_BETWEEN_CREATION_AND_UPDATE = 1000 * 60 * 30; // 30 minutes
@@ -55,10 +57,11 @@ export function ConversationCard({
conversationStatus = "STOPPED",
variant = "default",
conversationId,
contextMenuOpen = false,
onContextMenuToggle,
}: ConversationCardProps) {
const { t } = useTranslation();
const { parsedEvents } = useWsClient();
const [contextMenuVisible, setContextMenuVisible] = React.useState(false);
const [titleMode, setTitleMode] = React.useState<"view" | "edit">("view");
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
@@ -101,21 +104,21 @@ export function ConversationCard({
event.preventDefault();
event.stopPropagation();
onDelete?.();
setContextMenuVisible(false);
onContextMenuToggle?.(false);
};
const handleStop = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
onStop?.();
setContextMenuVisible(false);
onContextMenuToggle?.(false);
};
const handleEdit = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
setTitleMode("edit");
setContextMenuVisible(false);
onContextMenuToggle?.(false);
};
const handleDownloadViaVSCode = async (
@@ -141,7 +144,7 @@ export function ConversationCard({
}
}
setContextMenuVisible(false);
onContextMenuToggle?.(false);
};
const handleDisplayCost = (event: React.MouseEvent<HTMLButtonElement>) => {
@@ -224,15 +227,15 @@ export function ConversationCard({
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
setContextMenuVisible((prev) => !prev);
onContextMenuToggle?.(!contextMenuOpen);
}}
/>
</div>
)}
<div className="relative">
{contextMenuVisible && (
{contextMenuOpen && (
<ConversationCardContextMenu
onClose={() => setContextMenuVisible(false)}
onClose={() => onContextMenuToggle?.(false)}
onDelete={onDelete && handleDelete}
onStop={
conversationStatus !== "STOPPED"

View File

@@ -36,6 +36,9 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
const [selectedConversationId, setSelectedConversationId] = React.useState<
string | null
>(null);
const [openContextMenuId, setOpenContextMenuId] = React.useState<
string | null
>(null);
const { data: conversations, isFetching, error } = useUserConversations();
@@ -144,6 +147,10 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
createdAt={project.created_at}
conversationStatus={project.status}
conversationId={project.conversation_id}
contextMenuOpen={openContextMenuId === project.conversation_id}
onContextMenuToggle={(isOpen) =>
setOpenContextMenuId(isOpen ? project.conversation_id : null)
}
/>
)}
</NavLink>

View File

@@ -10,9 +10,6 @@ import { BrandButton } from "../settings/brand-button";
import { useSearchRepositories } from "#/hooks/query/use-search-repositories";
import { useDebounce } from "#/hooks/use-debounce";
import { sanitizeQuery } from "#/utils/sanitize-query";
import { useUserProviders } from "#/hooks/use-user-providers";
import { Provider } from "#/types/settings";
import { SettingsDropdownInput } from "../settings/settings-dropdown-input";
import {
RepositoryDropdown,
RepositoryLoadingState,
@@ -35,10 +32,8 @@ export function RepositorySelectionForm({
const [selectedBranch, setSelectedBranch] = React.useState<Branch | null>(
null,
);
const [selectedProvider, setSelectedProvider] = React.useState<Provider | null>(null);
// Add a ref to track if the branch was manually cleared by the user
const branchManuallyClearedRef = React.useRef<boolean>(false);
const { providers } = useUserProviders();
const {
data: repositories,
isLoading: isLoadingRepositories,
@@ -61,13 +56,6 @@ export function RepositorySelectionForm({
const debouncedSearchQuery = useDebounce(searchQuery, 300);
const { data: searchedRepos } = useSearchRepositories(debouncedSearchQuery);
// Auto-select provider if there's only one
React.useEffect(() => {
if (providers.length === 1 && !selectedProvider) {
setSelectedProvider(providers[0]);
}
}, [providers, selectedProvider]);
// Auto-select main or master branch if it exists, but only if the branch wasn't manually cleared
React.useEffect(() => {
if (
@@ -95,10 +83,8 @@ export function RepositorySelectionForm({
const isCreatingConversation =
isPending || isSuccess || isCreatingConversationElsewhere;
// Use all repositories without filtering by provider for now
const allRepositories = repositories?.concat(searchedRepos || []);
const repositoriesItems = (allRepositories || []).map((repo) => ({
const repositoriesItems = allRepositories?.map((repo) => ({
key: repo.id,
label: decodeURIComponent(repo.full_name),
}));
@@ -108,14 +94,6 @@ export function RepositorySelectionForm({
label: branch.name,
}));
// Create provider dropdown items
const providerItems = React.useMemo(() => {
return providers.map(provider => ({
key: provider,
label: provider.charAt(0).toUpperCase() + provider.slice(1), // Capitalize first letter
}));
}, [providers]);
const handleRepoSelection = (key: React.Key | null) => {
const selectedRepo = allRepositories?.find((repo) => repo.id === key);
if (selectedRepo) onRepoSelection(selectedRepo);
@@ -124,14 +102,6 @@ export function RepositorySelectionForm({
branchManuallyClearedRef.current = false; // Reset the flag when repo changes
};
const handleProviderSelection = (key: React.Key | null) => {
const provider = key as Provider | null;
setSelectedProvider(provider);
setSelectedRepository(null); // Reset repository selection when provider changes
setSelectedBranch(null); // Reset branch selection when provider changes
onRepoSelection(null); // Reset parent component's selected repo
};
const handleBranchSelection = (key: React.Key | null) => {
const selectedBranchObj = branches?.find((branch) => branch.name === key);
setSelectedBranch(selectedBranchObj || null);
@@ -163,26 +133,6 @@ export function RepositorySelectionForm({
}
};
// Render the provider dropdown
const renderProviderSelector = () => {
// Only render if there are multiple providers
if (providers.length <= 1) {
return null;
}
return (
<SettingsDropdownInput
testId="provider-dropdown"
name="provider-dropdown"
placeholder="Select Provider"
items={providerItems}
wrapperClassName="max-w-[500px]"
onSelectionChange={handleProviderSelection}
selectedKey={selectedProvider || undefined}
/>
);
};
// Render the appropriate UI based on the loading/error state
const renderRepositorySelector = () => {
if (isLoadingRepositories) {
@@ -193,15 +143,11 @@ export function RepositorySelectionForm({
return <RepositoryErrorState />;
}
// For now, don't disable the repo dropdown based on provider selection
const isDisabled = false;
return (
<RepositoryDropdown
items={repositoriesItems || []}
onSelectionChange={handleRepoSelection}
onInputChange={handleRepoInputChange}
isDisabled={isDisabled}
defaultFilter={(textValue, inputValue) => {
if (!inputValue) return true;
@@ -249,8 +195,8 @@ export function RepositorySelectionForm({
return (
<div className="flex flex-col gap-4">
{renderProviderSelector()}
{renderRepositorySelector()}
{renderBranchSelector()}
<BrandButton

View File

@@ -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}
/>
);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -8,7 +8,6 @@ export interface RepositoryDropdownProps {
onSelectionChange: (key: React.Key | null) => void;
onInputChange: (value: string) => void;
defaultFilter?: (textValue: string, inputValue: string) => boolean;
isDisabled?: boolean;
}
export function RepositoryDropdown({
@@ -16,7 +15,6 @@ export function RepositoryDropdown({
onSelectionChange,
onInputChange,
defaultFilter,
isDisabled = false,
}: RepositoryDropdownProps) {
const { t } = useTranslation();
@@ -24,13 +22,12 @@ export function RepositoryDropdown({
<SettingsDropdownInput
testId="repo-dropdown"
name="repo-dropdown"
placeholder={isDisabled ? t("Please select a provider first") : t(I18nKey.REPOSITORY$SELECT_REPO)}
placeholder={t(I18nKey.REPOSITORY$SELECT_REPO)}
items={items}
wrapperClassName="max-w-[500px]"
onSelectionChange={onSelectionChange}
onInputChange={onInputChange}
defaultFilter={defaultFilter}
isDisabled={isDisabled}
/>
);
}

View File

@@ -0,0 +1,30 @@
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;
}
export function MicroagentManagementAccordionTitle({
repository,
}: MicroagentManagementAccordionTitleProps) {
return (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<GitProviderIcon gitProvider={repository.git_provider} />
<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"
>
<span>{repository.full_name}</span>
</TooltipButton>
</div>
<MicroagentManagementAddMicroagentButton repository={repository} />
</div>
);
}

View File

@@ -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,17 +25,23 @@ export function MicroagentManagementAddMicroagentButton() {
const dispatch = useDispatch();
const handleClick = () => {
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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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 />;
}

View File

@@ -1,32 +1,142 @@
import { useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
export interface Microagent {
id: string;
name: string;
repositoryUrl: string;
createdAt: string;
}
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: Microagent;
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 = microagent
? `.openhands/microagents/${microagent.name}`
: "";
// Format the createdAt date using MM/DD/YYYY format
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">
{microagent.repositoryUrl}
</div>
<div className="text-white text-sm font-normal">
{t(I18nKey.COMMON$CREATED_ON)} {microagent.createdAt}
<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>
);

View File

@@ -1,38 +0,0 @@
import { MicroagentManagementMicroagentCard } from "./microagent-management-microagent-card";
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
export function MicroagentManagementMicroagents() {
const microagents = [
{
id: "no-comments",
name: "No comments",
repositoryUrl: "fairwinds/polaris/Repo Overview",
createdAt: "05/30/2025",
},
{
id: "tell-me-a-joke",
name: "Tell me a joke",
repositoryUrl: ".openhands/microagents/Repo Overview",
createdAt: "05/30/2025",
},
];
const numberOfMicroagents = microagents.length;
if (numberOfMicroagents === 0) {
return null;
}
return (
<div>
<div className="flex items-center justify-end pb-4">
<MicroagentManagementAddMicroagentButton />
</div>
{microagents.map((microagent) => (
<div key={microagent.id} className="pb-4">
<MicroagentManagementMicroagentCard microagent={microagent} />
</div>
))}
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { FaCircleInfo } from "react-icons/fa6";
interface MicroagentManagementNoRepositoriesProps {
title: string;
documentationUrl: string;
}
export function MicroagentManagementNoRepositories({
title,
documentationUrl,
}: MicroagentManagementNoRepositoriesProps) {
return (
<div className="flex items-center justify-center pt-10">
<div className="flex items-center gap-2">
<h2 className="text-white text-sm font-medium">{title}</h2>
<a href={documentationUrl} target="_blank" rel="noopener noreferrer">
<FaCircleInfo className="text-primary" />
</a>
</div>
</div>
);
}

View File

@@ -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>
);
}

View File

@@ -1,49 +0,0 @@
import {
Microagent,
MicroagentManagementMicroagentCard,
} from "./microagent-management-microagent-card";
import { MicroagentManagementLearnThisRepo } from "./microagent-management-learn-this-repo";
import { MicroagentManagementAddMicroagentButton } from "./microagent-management-add-microagent-button";
export interface RepoMicroagent {
id: string;
repositoryName: string;
repositoryUrl: string;
microagents: Microagent[];
}
interface MicroagentManagementRepoMicroagentProps {
repoMicroagent: RepoMicroagent;
}
export function MicroagentManagementRepoMicroagent({
repoMicroagent,
}: MicroagentManagementRepoMicroagentProps) {
const { microagents } = repoMicroagent;
const numberOfMicroagents = microagents.length;
return (
<div className="pb-12">
<div className="flex items-center justify-between pb-4">
<div className="text-white text-base font-normal">
{repoMicroagent.repositoryName}
</div>
<MicroagentManagementAddMicroagentButton />
</div>
{numberOfMicroagents === 0 && (
<MicroagentManagementLearnThisRepo
repositoryUrl={repoMicroagent.repositoryUrl}
/>
)}
{numberOfMicroagents > 0 && (
<>
{microagents.map((microagent) => (
<div key={microagent.id} className="pb-4 last:pb-0">
<MicroagentManagementMicroagentCard microagent={microagent} />
</div>
))}
</>
)}
</div>
);
}

View File

@@ -1,42 +1,124 @@
import { MicroagentManagementRepoMicroagent } from "./microagent-management-repo-microagent";
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 { useSearchConversations } from "#/hooks/query/use-search-conversations";
import { GitRepository } from "#/types/git";
import { RootState } from "#/store";
import { setSelectedMicroagentItem } from "#/state/microagent-management-slice";
export function MicroagentManagementRepoMicroagents() {
const repoMicroagents = [
{
id: "rbren/rss-parser",
repositoryName: "rbren/rss-parser",
repositoryUrl: "https://github.com/rbren/rss-parser",
microagents: [],
},
{
id: "fairwinds/polaris",
repositoryName: "fairwinds/polaris",
repositoryUrl: "https://github.com/fairwinds/polaris",
microagents: [
{
id: "no-comments",
name: "No comments",
repositoryUrl: "fairwinds/polaris/Repo Overview",
createdAt: "05/30/2025",
},
],
},
];
interface MicroagentManagementRepoMicroagentsProps {
repository: GitRepository;
}
const numberOfRepoMicroagents = repoMicroagents.length;
export function MicroagentManagementRepoMicroagents({
repository,
}: MicroagentManagementRepoMicroagentsProps) {
const { selectedMicroagentItem } = useSelector(
(state: RootState) => state.microagentManagement,
);
if (numberOfRepoMicroagents === 0) {
return null;
const dispatch = useDispatch();
const { full_name: repositoryName } = repository;
// Extract owner and repo from repositoryName (format: "owner/repo")
const [owner, repo] = repositoryName.split("/");
const {
data: microagents,
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">
<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 repository={repository} />
</div>
);
}
const numberOfMicroagents = microagents?.length || 0;
const numberOfConversations = conversations?.length || 0;
const totalItems = numberOfMicroagents + numberOfConversations;
return (
<div>
{repoMicroagents.map((repoMicroagent) => (
<MicroagentManagementRepoMicroagent
key={repoMicroagent.id}
repoMicroagent={repoMicroagent}
/>
))}
<div className="pb-4">
{totalItems === 0 && (
<MicroagentManagementLearnThisRepo repository={repository} />
)}
{/* Render microagents */}
{numberOfMicroagents > 0 &&
microagents?.map((microagent) => (
<div key={microagent.name} className="pb-4 last:pb-0">
<MicroagentManagementMicroagentCard
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>
))}
</div>
);
}

View File

@@ -0,0 +1,119 @@
import { useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Accordion, AccordionItem } from "@heroui/react";
import { MicroagentManagementRepoMicroagents } from "./microagent-management-repo-microagents";
import { GitRepository } from "#/types/git";
import { cn } from "#/utils/utils";
import { TabType } from "#/types/microagent-management";
import { MicroagentManagementNoRepositories } from "./microagent-management-no-repositories";
import { I18nKey } from "#/i18n/declaration";
import { DOCUMENTATION_URL } from "#/utils/constants";
import { MicroagentManagementAccordionTitle } from "./microagent-management-accordion-title";
import { sanitizeQuery } from "#/utils/sanitize-query";
type MicroagentManagementRepositoriesProps = {
repositories: GitRepository[];
tabType: TabType;
};
export function MicroagentManagementRepositories({
repositories,
tabType,
}: MicroagentManagementRepositoriesProps) {
const { t } = useTranslation();
const [searchQuery, setSearchQuery] = useState("");
const numberOfRepoMicroagents = repositories.length;
// Filter repositories based on search query
const filteredRepositories = useMemo(() => {
if (!searchQuery.trim()) {
return repositories;
}
const sanitizedQuery = sanitizeQuery(searchQuery);
return repositories.filter((repository) => {
const sanitizedRepoName = sanitizeQuery(repository.full_name);
return sanitizedRepoName.includes(sanitizedQuery);
});
}, [repositories, searchQuery]);
if (numberOfRepoMicroagents === 0) {
if (tabType === "personal") {
return (
<MicroagentManagementNoRepositories
title={t(
I18nKey.MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_USER_LEVEL_MICROAGENTS,
)}
documentationUrl={DOCUMENTATION_URL.MICROAGENTS.MICROAGENTS_OVERVIEW}
/>
);
}
if (tabType === "repositories") {
return (
<MicroagentManagementNoRepositories
title={t(I18nKey.MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_MICROAGENTS)}
documentationUrl={DOCUMENTATION_URL.MICROAGENTS.MICROAGENTS_OVERVIEW}
/>
);
}
if (tabType === "organizations") {
return (
<MicroagentManagementNoRepositories
title={t(
I18nKey.MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_ORGANIZATION_LEVEL_MICROAGENTS,
)}
documentationUrl={
DOCUMENTATION_URL.MICROAGENTS.ORGANIZATION_AND_USER_MICROAGENTS
}
/>
);
}
}
return (
<div className="flex flex-col gap-4 w-full">
{/* Search Input */}
<div className="flex flex-col gap-2 w-full">
<label htmlFor="repository-search" className="sr-only">
{t(I18nKey.COMMON$SEARCH_REPOSITORIES)}
</label>
<input
id="repository-search"
name="repository-search"
type="text"
placeholder={`${t(I18nKey.COMMON$SEARCH_REPOSITORIES)}...`}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className={cn(
"bg-tertiary border border-[#717888] bg-[#454545] w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt",
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
)}
/>
</div>
{/* Repositories Accordion */}
<Accordion
variant="splitted"
className="w-full px-0 gap-3"
itemClasses={{
base: "shadow-none bg-transparent border border-[#ffffff40] rounded-[6px] cursor-pointer",
trigger: "cursor-pointer gap-1",
}}
selectionMode="multiple"
>
{filteredRepositories.map((repository) => (
<AccordionItem
key={repository.id}
aria-label={repository.full_name}
title={
<MicroagentManagementAccordionTitle repository={repository} />
}
>
<MicroagentManagementRepoMicroagents repository={repository} />
</AccordionItem>
))}
</Accordion>
</div>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);

View File

@@ -1,12 +1,16 @@
import { Tab, Tabs } from "@heroui/react";
import { useTranslation } from "react-i18next";
import { MicroagentManagementMicroagents } from "./microagent-management-microagents";
import { MicroagentManagementRepoMicroagents } from "./microagent-management-repo-microagents";
import { useSelector } from "react-redux";
import { MicroagentManagementRepositories } from "./microagent-management-repositories";
import { I18nKey } from "#/i18n/declaration";
import { RootState } from "#/store";
export function MicroagentManagementSidebarTabs() {
const { t } = useTranslation();
const { repositories, personalRepositories, organizationRepositories } =
useSelector((state: RootState) => state.microagentManagement);
return (
<div className="flex w-full flex-col">
<Tabs
@@ -17,18 +21,27 @@ export function MicroagentManagementSidebarTabs() {
"w-full bg-transparent border border-[#ffffff40] rounded-[6px]",
tab: "px-2 h-[22px]",
tabContent: "text-white text-[12px] font-normal",
panel: "py-0",
panel: "p-0",
cursor: "bg-[#C9B97480] rounded-sm",
}}
>
<Tab key="personal" title={t(I18nKey.COMMON$PERSONAL)}>
<MicroagentManagementMicroagents />
<MicroagentManagementRepositories
repositories={personalRepositories}
tabType="personal"
/>
</Tab>
<Tab key="repositories" title={t(I18nKey.COMMON$REPOSITORIES)}>
<MicroagentManagementRepoMicroagents />
<MicroagentManagementRepositories
repositories={repositories}
tabType="repositories"
/>
</Tab>
<Tab key="organizations" title={t(I18nKey.COMMON$ORGANIZATIONS)}>
<MicroagentManagementMicroagents />
<MicroagentManagementRepositories
repositories={organizationRepositories}
tabType="organizations"
/>
</Tab>
</Tabs>
</div>

View File

@@ -1,11 +1,71 @@
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";
import {
setPersonalRepositories,
setOrganizationRepositories,
setRepositories,
} from "#/state/microagent-management-slice";
import { GitRepository } from "#/types/git";
import { cn } from "#/utils/utils";
interface MicroagentManagementSidebarProps {
isSmallerScreen?: boolean;
}
export function MicroagentManagementSidebar({
isSmallerScreen = false,
}: MicroagentManagementSidebarProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const { data: repositories, isLoading } = useUserRepositories();
useEffect(() => {
if (repositories) {
const personalRepos: GitRepository[] = [];
const organizationRepos: GitRepository[] = [];
const otherRepos: GitRepository[] = [];
repositories.forEach((repo: GitRepository) => {
const hasOpenHandsSuffix = repo.full_name.endsWith("/.openhands");
if (repo.owner_type === "user" && hasOpenHandsSuffix) {
personalRepos.push(repo);
} else if (repo.owner_type === "organization" && hasOpenHandsSuffix) {
organizationRepos.push(repo);
} else {
otherRepos.push(repo);
}
});
dispatch(setPersonalRepositories(personalRepos));
dispatch(setOrganizationRepositories(organizationRepos));
dispatch(setRepositories(otherRepos));
}
}, [repositories, dispatch]);
export function MicroagentManagementSidebar() {
return (
<div className="w-[418px] h-full border-r border-[#525252] bg-[#24272E] rounded-tl-lg rounded-bl-lg py-10 px-6">
<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 />
<MicroagentManagementSidebarTabs />
{isLoading ? (
<div className="flex flex-col items-center justify-center gap-4 flex-1">
<Spinner size="sm" />
<span className="text-sm text-white">
{t("HOME$LOADING_REPOSITORIES")}
</span>
</div>
) : (
<MicroagentManagementSidebarTabs />
)}
</div>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next";
import SettingsIcon from "#/icons/settings.svg?react";
import { TooltipButton } from "./tooltip-button";
import { I18nKey } from "#/i18n/declaration";
import { useConfig } from "#/hooks/query/use-config";
interface SettingsButtonProps {
onClick?: () => void;
@@ -13,6 +14,12 @@ export function SettingsButton({
disabled = false,
}: SettingsButtonProps) {
const { t } = useTranslation();
const { data: config } = useConfig();
// Determine the correct settings path based on app mode
// In SaaS mode, navigate directly to user settings to avoid the LLM settings page
const settingsPath =
config?.APP_MODE === "saas" ? "/settings/user" : "/settings";
return (
<TooltipButton
@@ -20,7 +27,7 @@ export function SettingsButton({
tooltip={t(I18nKey.SETTINGS$TITLE)}
ariaLabel={t(I18nKey.SETTINGS$TITLE)}
onClick={onClick}
navLinkTo="/settings"
navLinkTo={settingsPath}
disabled={disabled}
>
<SettingsIcon width={28} height={28} />

View File

@@ -0,0 +1,16 @@
import { FaBitbucket, FaGithub, FaGitlab } from "react-icons/fa6";
import { Provider } from "#/types/settings";
interface GitProviderIconProps {
gitProvider: Provider;
}
export function GitProviderIcon({ gitProvider }: GitProviderIconProps) {
return (
<>
{gitProvider === "github" && <FaGithub size={14} />}
{gitProvider === "gitlab" && <FaGitlab />}
{gitProvider === "bitbucket" && <FaBitbucket />}
</>
);
}

View 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>
);
}

View File

@@ -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 }) => {

View File

@@ -1,23 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { useConfig } from "./use-config";
import { useIsAuthed } from "./use-is-authed";
import OpenHands from "#/api/open-hands";
import { useUserProviders } from "../use-user-providers";
export const useAppInstallations = () => {
const { data: config } = useConfig();
const { data: userIsAuthenticated } = useIsAuthed();
const { providers } = useUserProviders();
return useQuery({
queryKey: ["installations", providers, config?.GITHUB_CLIENT_ID],
queryFn: OpenHands.getGitHubUserInstallationIds,
enabled:
userIsAuthenticated &&
providers.includes("github") &&
!!config?.GITHUB_CLIENT_ID &&
config?.APP_MODE === "saas",
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
});
};

View File

@@ -1,22 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { useConfig } from "./use-config";
import { useIsAuthed } from "./use-is-authed";
import OpenHands from "#/api/open-hands";
import { useUserProviders } from "../use-user-providers";
export const useBitbucketWorkspaces = () => {
const { data: config } = useConfig();
const { data: userIsAuthenticated } = useIsAuthed();
const { providers } = useUserProviders();
return useQuery({
queryKey: ["workspaces", providers],
queryFn: OpenHands.getBitBucketWorkspaces,
enabled:
userIsAuthenticated &&
providers.includes("bitbucket") &&
config?.APP_MODE === "saas",
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
});
};

View File

@@ -0,0 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import OpenHands from "#/api/open-hands";
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: cacheDisabled ? 0 : 1000 * 60 * 5, // 5 minutes
gcTime: cacheDisabled ? 0 : 1000 * 60 * 15, // 15 minutes
});

View 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
});

View File

@@ -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) => {

View File

@@ -12,6 +12,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 +699,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",
@@ -708,4 +710,27 @@ export enum I18nKey {
COMMON$RUN_TEST = "COMMON$RUN_TEST",
COMMON$RUN_APP = "COMMON$RUN_APP",
COMMON$LEARN_FILE_STRUCTURE = "COMMON$LEARN_FILE_STRUCTURE",
MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_USER_LEVEL_MICROAGENTS = "MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_USER_LEVEL_MICROAGENTS",
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",
}

View File

@@ -191,6 +191,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 +11183,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はあなたの指示に基づいて新しいマイクロエージェントを作成します。",
@@ -11197,7 +11229,7 @@
"fr": "Que souhaitez-vous que le microagent fasse ?",
"tr": "Mikro ajanın ne yapmasını istersiniz?",
"de": "Was soll der Microagent tun?",
"uk": "Що ви хочете, щоб зробив мікроагент?"
"uk": "Що в,и хочете, щоб зробив мікроагент?"
},
"MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO": {
"en": "Describe what you would like the Microagent to do.",
@@ -11326,5 +11358,373 @@
"tr": "Dosya yapısını öğren",
"de": "Dateistruktur lernen",
"uk": "Вивчити структуру файлів"
},
"MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_USER_LEVEL_MICROAGENTS": {
"en": "You do not have user-level microagents",
"ja": "ユーザーレベルのマイクロエージェントがありません",
"zh-CN": "您没有用户级微代理",
"zh-TW": "您沒有使用者層級的微代理",
"ko-KR": "사용자 수준의 마이크로에이전트가 없습니다",
"no": "Du har ikke mikroagenter på brukernivå",
"it": "Non hai microagenti a livello utente",
"pt": "Você não possui microagentes de nível de usuário",
"es": "No tienes microagentes a nivel de usuario",
"ar": "ليس لديك وكلاء دقيقون على مستوى المستخدم",
"fr": "Vous n'avez pas de microagents au niveau utilisateur",
"tr": "Kullanıcı düzeyinde mikro ajanınız yok",
"de": "Sie haben keine Mikroagenten auf Benutzerebene",
"uk": "У вас немає мікроагентів на рівні користувача"
},
"MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_MICROAGENTS": {
"en": "You do not have microagents",
"ja": "マイクロエージェントがありません",
"zh-CN": "您没有微代理",
"zh-TW": "您沒有微代理",
"ko-KR": "마이크로에이전트가 없습니다",
"no": "Du har ingen mikroagenter",
"it": "Non hai microagenti",
"pt": "Você não possui microagentes",
"es": "No tienes microagentes",
"ar": "ليس لديك وكلاء دقيقون",
"fr": "Vous n'avez pas de microagents",
"tr": "Mikro ajanınız yok",
"de": "Sie haben keine Mikroagenten",
"uk": "У вас немає мікроагентів"
},
"MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_ORGANIZATION_LEVEL_MICROAGENTS": {
"en": "You do not have organization-level microagents",
"ja": "組織レベルのマイクロエージェントがありません",
"zh-CN": "您没有组织级微代理",
"zh-TW": "您沒有組織層級的微代理",
"ko-KR": "조직 수준의 마이크로에이전트가 없습니다",
"no": "Du har ikke mikroagenter på organisasjonsnivå",
"it": "Non hai microagenti a livello organizzazione",
"pt": "Você não possui microagentes de nível organizacional",
"es": "No tienes microagentes a nivel de organización",
"ar": "ليس لديك وكلاء دقيقون على مستوى المؤسسة",
"fr": "Vous n'avez pas de microagents au niveau organisation",
"tr": "Organizasyon düzeyinde mikro ajanınız yok",
"de": "Sie haben keine Mikroagenten auf Organisationsebene",
"uk": "У вас немає мікроагентів на рівні організації"
},
"COMMON$SEARCH_REPOSITORIES": {
"en": "Search repositories",
"ja": "リポジトリを検索",
"zh-CN": "搜索仓库",
"zh-TW": "搜尋存儲庫",
"ko-KR": "저장소 검색",
"no": "Søk i repositories",
"it": "Cerca repository",
"pt": "Pesquisar repositórios",
"es": "Buscar repositorios",
"ar": "البحث في المستودعات",
"fr": "Rechercher des dépôts",
"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 оновить мікроагента відповідно до ваших інструкцій."
}
}

View File

@@ -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>
);
}

View File

@@ -1,29 +1,56 @@
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;
},
setPersonalRepositories: (state, action) => {
state.personalRepositories = action.payload;
},
setOrganizationRepositories: (state, action) => {
state.organizationRepositories = action.payload;
},
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;

View File

@@ -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;
}
}

View File

@@ -30,6 +30,7 @@ interface GitRepository {
stargazers_count?: number;
link_header?: string;
pushed_at?: string;
owner_type?: "user" | "organization";
}
interface GitHubCommit {

View File

@@ -0,0 +1,32 @@
import { Conversation } from "#/api/open-hands.types";
export type TabType = "personal" | "repositories" | "organizations";
export interface RepositoryMicroagent {
name: string;
type: "repo" | "knowledge";
content: string;
triggers: string[];
inputs: string[];
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;
}

View File

@@ -28,3 +28,12 @@ export const JSON_VIEW_THEME = {
base0E: "#c792ea", // keywords, purple
base0F: "#ff5370", // deprecated, red
};
export const DOCUMENTATION_URL = {
MICROAGENTS: {
MICROAGENTS_OVERVIEW:
"https://docs.all-hands.dev/usage/prompting/microagents-overview",
ORGANIZATION_AND_USER_MICROAGENTS:
"https://docs.all-hands.dev/usage/prompting/microagents-org",
},
};

View File

@@ -26,3 +26,19 @@ export const formatTimeDelta = (date: Date) => {
if (months < 12) return `${months}mo`;
return `${years}y`;
};
/**
* Formats a date into a MM/DD/YYYY string format.
* @param date The date to format
* @returns A string in MM/DD/YYYY format
*
* @example
* formatDateMMDDYYYY(new Date("2025-05-30T00:15:08")); // "05/30/2025"
* formatDateMMDDYYYY(new Date("2024-12-25T10:30:00")); // "12/25/2024"
*/
export const formatDateMMDDYYYY = (date: Date) =>
date.toLocaleDateString("en-US", {
month: "2-digit",
day: "2-digit",
year: "numeric",
});

View File

@@ -1,5 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { Provider } from "#/types/settings";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -102,3 +103,107 @@ export const formatTimestamp = (timestamp: string) =>
minute: "2-digit",
second: "2-digit",
});
export const getGitProviderBaseUrl = (gitProvider: Provider): string => {
switch (gitProvider) {
case "github":
return "https://github.com";
case "gitlab":
return "https://gitlab.com";
case "bitbucket":
return "https://bitbucket.org";
default:
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 "";
}
};

View File

@@ -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,10 @@
"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",
"tailwindcss": "^4.1.10",
},
},
},
@@ -67,7 +69,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 +77,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,57 +111,57 @@
"@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=="],
@@ -189,9 +191,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,7 +205,7 @@
"@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=="],
@@ -247,35 +249,35 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="],
"@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=="],
@@ -325,19 +327,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 +355,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 +375,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.20", "", { "dependencies": { "@volar/source-map": "2.4.20" } }, "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA=="],
"@volar/source-map": ["@volar/source-map@2.4.19", "", {}, "sha512-ttWmO/Ld7r3ebIPPAYvAuSLrlJ96ZALPka44mD4sWA8bw2n9u7TGnMcaTUkiF0GLG8bq/K09beWmEAB1mqMy/A=="],
"@volar/source-map": ["@volar/source-map@2.4.20", "", {}, "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg=="],
"@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.20", "", { "dependencies": { "@volar/language-core": "2.4.20", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ=="],
"@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=="],
@@ -405,12 +409,16 @@
"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,10 +431,16 @@
"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=="],
@@ -469,8 +483,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 +501,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 +515,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 +557,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 +567,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 +605,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 +661,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 +713,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 +725,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 +755,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 +801,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,6 +839,8 @@
"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=="],
@@ -753,14 +849,28 @@
"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=="],
"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 +891,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 +973,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 +987,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 +1029,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,8 +1041,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=="],

View File

@@ -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}

View File

@@ -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")} />

View File

@@ -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 = ({

View File

@@ -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,

View File

@@ -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),
})}

View 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;
}

View File

@@ -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;
};

View File

@@ -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",
},

View File

@@ -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}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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}
/>
);
};

View File

@@ -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>
);
};

View File

@@ -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,

View File

@@ -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>
);
};

View 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;
}

View File

@@ -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;
};

View File

@@ -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}

View File

@@ -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"

View File

@@ -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(

Some files were not shown because too many files have changed in this diff Show More