mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57d07df83f | |||
| 8f594310fa | |||
| a41280f3b8 | |||
| 385accd267 | |||
| 72a462681c | |||
| 27caa0b5fd | |||
| e8cabe6def | |||
| 8aa1ae712f | |||
| 993804dd4f | |||
| d5aaa8a67a | |||
| 9cc7823723 | |||
| def357024e | |||
| d868eb5dee | |||
| a88f3744da | |||
| acfb01d743 | |||
| 0daaf21607 | |||
| 95bda09cd9 | |||
| 1243612694 | |||
| aff8aba5e6 | |||
| aea37e52f7 | |||
| f5674d7c76 | |||
| 9c68146b04 | |||
| ee14f1ea41 | |||
| b96301061d | |||
| 1281f2d6c2 | |||
| dc41e0e90c | |||
| 793786130a | |||
| 59f03122c7 | |||
| 67edc66da7 | |||
| cb910e6863 | |||
| 4c39e92351 | |||
| e65e0a98f0 | |||
| eecc00fa4a | |||
| 5654e251a8 | |||
| d9694aabcd | |||
| bc8ef37192 | |||
| 5f141f7712 | |||
| 30e3011cb0 | |||
| 3475d8021b | |||
| 32cd50db2f | |||
| f0a6db936c | |||
| 11c37d8d70 | |||
| 7e1367057a | |||
| 3bbb0c6279 | |||
| eed71c21bd | |||
| 4f46826de9 | |||
| ea50fe4e3c | |||
| b057af8d63 | |||
| fba2218760 | |||
| 6147cbdc18 | |||
| 802acb3c7e | |||
| 376dc21e34 | |||
| 387318385c | |||
| 553f0a0918 | |||
| 0d1e21ae45 | |||
| a885e9e4d2 | |||
| 4c10848e8d | |||
| 1d95b01514 | |||
| cd32b5508c | |||
| 9a3bf0f2aa | |||
| 1d04a83e08 | |||
| 17e9b0fd6a | |||
| 54986c9841 |
@@ -54,7 +54,7 @@ jobs:
|
||||
Hi! I started running the integration tests on your PR. You will receive a comment with the results shortly.
|
||||
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install --with dev,test,runtime
|
||||
run: poetry install --with dev,test,runtime,evaluation
|
||||
|
||||
- name: Configure config.toml for testing with Haiku
|
||||
env:
|
||||
@@ -179,8 +179,8 @@ jobs:
|
||||
id: create_comment
|
||||
uses: KeisukeYamashita/create-comment@v1
|
||||
with:
|
||||
# if triggered by PR, use PR number, otherwise use 5318 as fallback issue number for manual triggers
|
||||
number: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || 5318 }}
|
||||
# if triggered by PR, use PR number, otherwise use 9745 as fallback issue number for manual triggers
|
||||
number: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || 9745 }}
|
||||
unique: false
|
||||
comment: |
|
||||
Trigger by: ${{ github.event_name == 'pull_request' && format('Pull Request (integration-test label on PR #{0})', github.event.pull_request.number) || (github.event_name == 'workflow_dispatch' && format('Manual Trigger: {0}', github.event.inputs.reason)) || 'Nightly Scheduled Run' }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Workflow that runs python unit tests
|
||||
name: Run Python Unit Tests
|
||||
# Workflow that runs python tests
|
||||
name: Run Python Tests
|
||||
|
||||
# The jobs in this workflow are required, so they must run at all times
|
||||
# * Always run on "main"
|
||||
@@ -16,9 +16,9 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Run python unit tests on Linux
|
||||
# Run python tests on Linux
|
||||
test-on-linux:
|
||||
name: Python Unit Tests on Linux
|
||||
name: Python Tests on Linux
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
env:
|
||||
INSTALL_DOCKER: '0' # Set to '0' to skip Docker installation
|
||||
@@ -51,6 +51,8 @@ jobs:
|
||||
run: poetry run pytest --forked -n auto -svv ./tests/unit
|
||||
- name: Run Runtime Tests with CLIRuntime
|
||||
run: TEST_RUNTIME=cli poetry run pytest -svv tests/runtime/test_bash.py
|
||||
- name: Run E2E Tests
|
||||
run: poetry run pytest -svv tests/e2e
|
||||
|
||||
# Run specific Windows python tests
|
||||
test-on-windows:
|
||||
@@ -137,3 +137,65 @@ Your specialized knowledge and instructions here...
|
||||
2. Add the setting to the backend:
|
||||
- Add the setting to the `Settings` model in `openhands/storage/data_models/settings.py`
|
||||
- Update any relevant backend code to apply the setting (e.g., in session creation)
|
||||
|
||||
### Adding New LLM Models
|
||||
|
||||
To add a new LLM model to OpenHands, you need to update multiple files across both frontend and backend:
|
||||
|
||||
#### Model Configuration Procedure:
|
||||
|
||||
1. **Frontend Model Arrays** (`frontend/src/utils/verified-models.ts`):
|
||||
- Add the model to `VERIFIED_MODELS` array (main list of all verified models)
|
||||
- Add to provider-specific arrays based on the model's provider:
|
||||
- `VERIFIED_OPENAI_MODELS` for OpenAI models
|
||||
- `VERIFIED_ANTHROPIC_MODELS` for Anthropic models
|
||||
- `VERIFIED_MISTRAL_MODELS` for Mistral models
|
||||
- `VERIFIED_OPENHANDS_MODELS` for models available through OpenHands provider
|
||||
|
||||
2. **Backend CLI Integration** (`openhands/cli/utils.py`):
|
||||
- Add the model to the appropriate `VERIFIED_*_MODELS` arrays
|
||||
- This ensures the model appears in CLI model selection
|
||||
|
||||
3. **Backend Model List** (`openhands/utils/llm.py`):
|
||||
- **CRITICAL**: Add the model to the `openhands_models` list (lines 57-66) if using OpenHands provider
|
||||
- This is required for the model to appear in the frontend model selector
|
||||
- Format: `'openhands/model-name'` (e.g., `'openhands/o3'`)
|
||||
|
||||
4. **Backend LLM Configuration** (`openhands/llm/llm.py`):
|
||||
- Add to feature-specific arrays based on model capabilities:
|
||||
- `FUNCTION_CALLING_SUPPORTED_MODELS` if the model supports function calling
|
||||
- `REASONING_EFFORT_SUPPORTED_MODELS` if the model supports reasoning effort parameters
|
||||
- `CACHE_PROMPT_SUPPORTED_MODELS` if the model supports prompt caching
|
||||
- `MODELS_WITHOUT_STOP_WORDS` if the model doesn't support stop words
|
||||
|
||||
5. **Validation**:
|
||||
- Run backend linting: `pre-commit run --config ./dev_config/python/.pre-commit-config.yaml`
|
||||
- Run frontend linting: `cd frontend && npm run lint:fix`
|
||||
- Run frontend build: `cd frontend && npm run build`
|
||||
|
||||
#### Model Verification Arrays:
|
||||
|
||||
- **VERIFIED_MODELS**: Main array of all verified models shown in the UI
|
||||
- **VERIFIED_OPENAI_MODELS**: OpenAI models (LiteLLM doesn't return provider prefix)
|
||||
- **VERIFIED_ANTHROPIC_MODELS**: Anthropic models (LiteLLM doesn't return provider prefix)
|
||||
- **VERIFIED_MISTRAL_MODELS**: Mistral models (LiteLLM doesn't return provider prefix)
|
||||
- **VERIFIED_OPENHANDS_MODELS**: Models available through OpenHands managed provider
|
||||
|
||||
#### Model Feature Support Arrays:
|
||||
|
||||
- **FUNCTION_CALLING_SUPPORTED_MODELS**: Models that support structured function calling
|
||||
- **REASONING_EFFORT_SUPPORTED_MODELS**: Models that support reasoning effort parameters (like o1, o3)
|
||||
- **CACHE_PROMPT_SUPPORTED_MODELS**: Models that support prompt caching for efficiency
|
||||
- **MODELS_WITHOUT_STOP_WORDS**: Models that don't support stop word parameters
|
||||
|
||||
#### Frontend Model Integration:
|
||||
|
||||
- Models are automatically available in the model selector UI once added to verified arrays
|
||||
- The `extractModelAndProvider` utility automatically detects provider from model arrays
|
||||
- Provider-specific models are grouped and prioritized in the UI selection
|
||||
|
||||
#### CLI Model Integration:
|
||||
|
||||
- Models appear in CLI provider selection based on the verified arrays
|
||||
- The `organize_models_and_providers` function groups models by provider
|
||||
- Default model selection prioritizes verified models for each provider
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"usage/cloud/bitbucket-installation",
|
||||
"usage/cloud/github-installation",
|
||||
"usage/cloud/gitlab-installation",
|
||||
"usage/cloud/slack-installation"
|
||||
@@ -68,6 +69,7 @@
|
||||
"usage/llms/litellm-proxy",
|
||||
"usage/llms/moonshot",
|
||||
"usage/llms/openai-llms",
|
||||
"usage/llms/openhands-llms",
|
||||
"usage/llms/openrouter"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1827,6 +1827,11 @@
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"owner_type": {
|
||||
"type": "string",
|
||||
"enum": ["user", "organization"],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Bitbucket Integration
|
||||
description: This guide walks you through the process of installing OpenHands Cloud for your Bitbucket repositories. Once
|
||||
set up, it will allow OpenHands to work with your Bitbucket repository.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a Bitbucket account](/usage/cloud/openhands-cloud).
|
||||
|
||||
## Adding Bitbucket Repository Access
|
||||
|
||||
Upon signing into OpenHands Cloud with a Bitbucket account, OpenHands will have access to your repositories.
|
||||
|
||||
## Working With Bitbucket Repos in Openhands Cloud
|
||||
|
||||
After signing in with a Bitbucket account, use the `select a repo` and `select a branch` dropdowns to select the
|
||||
appropriate repository and branch you'd like OpenHands to work on. Then click on `Launch` to start the conversation!
|
||||
|
||||

|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
@@ -9,8 +9,9 @@ description: The Cloud UI provides a web interface for interacting with OpenHand
|
||||
The landing page is where you can:
|
||||
|
||||
- [Add GitHub repository access](/usage/cloud/github-installation#adding-github-repository-access) to OpenHands.
|
||||
- [Select a GitHub repo](/usage/cloud/github-installation#working-with-github-repos-in-openhands-cloud) or
|
||||
[a GitLab repo](/usage/cloud/gitlab-installation#working-with-gitlab-repos-in-openhands-cloud) to start working on.
|
||||
- [Select a GitHub repo](/usage/cloud/github-installation#working-with-github-repos-in-openhands-cloud),
|
||||
[a GitLab repo](/usage/cloud/gitlab-installation#working-with-gitlab-repos-in-openhands-cloud) or
|
||||
[a Bitbucket repo](/usage/cloud/bitbucket-installation#working-with-bitbucket-repos-in-openhands-cloud) to start working on.
|
||||
- See `Suggested Tasks` for repositories that OpenHands has access to.
|
||||
- Launch an empty conversation using `Launch from Scratch`.
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ Giving GitHub repository access to OpenHands also allows you to work on GitHub i
|
||||
|
||||
### Working with Issues
|
||||
|
||||
On your repository, label an issue with `openhands` or add a message starting with
|
||||
`@openhands`. OpenHands will:
|
||||
On your repository, label an issue with `openhands` or add a message starting with `@openhands`. OpenHands will:
|
||||
1. Comment on the issue to let you know it is working on it.
|
||||
- You can click on the link to track the progress on OpenHands Cloud.
|
||||
2. Open a pull request if it determines that the issue has been successfully resolved.
|
||||
@@ -65,6 +64,8 @@ To get OpenHands to work on pull requests, mention `@openhands` in the comments
|
||||
- Request updates
|
||||
- Get code explanations
|
||||
|
||||
**Important Note**: The `@openhands` mention functionality in pull requests only works if the pull request is both *to* and *from* a repository that you have added through the interface. This is because OpenHands needs appropriate permissions to access both repositories.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: GitLab Integration
|
||||
description: This guide walks you through the process of installing OpenHands Cloud for your GitLab repositories. Once
|
||||
set up, it will allow OpenHands to work with your GitLab repository.
|
||||
set up, it will allow OpenHands to work with your GitLab repository through the Cloud UI or straight from GitLab!.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
@@ -17,7 +17,7 @@ Upon signing into OpenHands Cloud with a GitLab account, OpenHands will have acc
|
||||
After signing in with a Gitlab account, use the `select a repo` and `select a branch` dropdowns to select the
|
||||
appropriate repository and branch you'd like OpenHands to work on. Then click on `Launch` to start the conversation!
|
||||
|
||||

|
||||

|
||||
|
||||
## Using Tokens with Reduced Scopes
|
||||
|
||||
@@ -25,6 +25,33 @@ OpenHands requests an API-scoped token during OAuth authentication. By default,
|
||||
To restrict the agent's permissions, you can define a custom secret `GITLAB_TOKEN`, which will override the default token assigned to the agent.
|
||||
While the high-permission API token is still requested and used for other components of the application (e.g. opening merge requests), the agent will not have access to it.
|
||||
|
||||
## Working on GitLab Issues and Merge Requests Using Openhands
|
||||
|
||||
<Note>
|
||||
This feature works for personal projects and is available for group projects with a
|
||||
[Premium or Ultimate tier subscription](https://docs.gitlab.com/user/project/integrations/webhooks/#group-webhooks).
|
||||
|
||||
A webhook is automatically installed within a few minutes after the owner/maintainer of the project or group logs into
|
||||
OpenHands Cloud. If you decide to delete the webhook, then re-installing will require the support of All Hands AI but we are planning to improve this in a future release.
|
||||
</Note>
|
||||
|
||||
Giving GitLab repository access to OpenHands also allows you to work on GitLab issues and merge requests directly.
|
||||
|
||||
### Working with Issues
|
||||
|
||||
On your repository, label an issue with `openhands` or add a message starting with `@openhands`. OpenHands will:
|
||||
1. Comment on the issue to let you know it is working on it.
|
||||
- You can click on the link to track the progress on OpenHands Cloud.
|
||||
2. Open a merge request if it determines that the issue has been successfully resolved.
|
||||
3. Comment on the issue with a summary of the performed tasks and a link to the PR.
|
||||
|
||||
### Working with Merge Requests
|
||||
|
||||
To get OpenHands to work on merge requests, mention `@openhands` in the comments to:
|
||||
- Ask questions
|
||||
- Request updates
|
||||
- Get code explanations
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
|
||||
@@ -8,9 +8,9 @@ description: Getting started with OpenHands Cloud.
|
||||
OpenHands Cloud is the hosted cloud version of All Hands AI's OpenHands. To get started with OpenHands Cloud,
|
||||
visit [app.all-hands.dev](https://app.all-hands.dev).
|
||||
|
||||
You'll be prompted to connect with your GitHub or GitLab account:
|
||||
You'll be prompted to connect with your GitHub, GitLab or Bitbucket account:
|
||||
|
||||
1. Click `Log in with GitHub` or `Log in with GitLab`.
|
||||
1. Click `Log in with GitHub`, `Log in with GitLab` or `Log in with Bitbucket`.
|
||||
2. Review the permissions requested by OpenHands and authorize the application.
|
||||
- OpenHands will require certain permissions from your account. To read more about these permissions,
|
||||
you can click the `Learn more` link on the authorization page.
|
||||
@@ -22,5 +22,6 @@ Once you've connected your account, you can:
|
||||
|
||||
- [Install GitHub Integration](/usage/cloud/github-installation) to use OpenHands with your GitHub repositories.
|
||||
- [Install GitLab Integration](/usage/cloud/gitlab-installation) to use OpenHands with your GitLab repositories.
|
||||
- [Install Bitbucket Integration](/usage/cloud/bitbucket-installation) to use OpenHands with your Bitbucket repositories.
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@ icon: question
|
||||
[GitHub](/usage/cloud/github-installation), [GitLab](/usage/cloud/gitlab-installation),
|
||||
and [Slack](/usage/cloud/slack-installation) integrations.
|
||||
2. **Run on your own**: If you prefer to run it on your own hardware, follow our [Getting Started guide](/usage/local-setup).
|
||||
3. **First steps**: Complete the [start building tutorial](/usage/getting-started) to learn the basics.
|
||||
3. **First steps**: Read over the [start building guidelines](/usage/getting-started) and
|
||||
[prompting best practices](/usage/prompting/prompting-best-practices) to learn the basics.
|
||||
|
||||
### Can I use OpenHands for production workloads?
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ docker run -it \
|
||||
|
||||
This launches the CLI in Docker, allowing you to interact with OpenHands.
|
||||
|
||||
The `-e SANDBOX_USER_ID=$(id -u)` ensures files created by the agent in your workspace have the correct permissions.
|
||||
The `-e SANDBOX_USER_ID=$(id -u)` is passed to the Docker command to ensure the sandbox user matches the host user’s
|
||||
permissions. This prevents the agent from creating root-owned files in the mounted workspace.
|
||||
|
||||
The conversation history will be saved in `~/.openhands/sessions`.
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ You can use the Settings page at any time to:
|
||||
- Setup the LLM provider and model for OpenHands.
|
||||
- [Setup the search engine](/usage/search-engine-setup).
|
||||
- [Configure MCP servers](/usage/mcp).
|
||||
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup) and [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup).
|
||||
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup), [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup)
|
||||
and [connect to Bitbucket](/usage/how-to/gui-mode#bitbucket-setup).
|
||||
- Set application settings like your preferred language, notifications and other preferences.
|
||||
- [Manage custom secrets](/usage/common-settings#secrets-management).
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ docker run -it \
|
||||
> **Note**: If you used OpenHands before version 0.44, run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history.
|
||||
|
||||
The `-e SANDBOX_USER_ID=$(id -u)` is passed to the Docker command to ensure the sandbox user matches the host user’s
|
||||
permissions. This prevents the agent from creating root-owned files in the mounted workspace.
|
||||
|
||||
## Additional Options
|
||||
|
||||
@@ -90,6 +91,6 @@ Common command-line options:
|
||||
- `-b 10.0` - Set budget limit (USD)
|
||||
- `--no-auto-continue` - Interactive mode
|
||||
|
||||
Run `poetry run python -m openhands.core.main --help` for all options, or use a [`config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml) for more flexibility.
|
||||
Run `poetry run python -m openhands.core.main --help` for all options.
|
||||
|
||||
Set `export LOG_ALL_EVENTS=true` to log all agent actions.
|
||||
|
||||
@@ -10,7 +10,8 @@ This section is for users who want to connect OpenHands to different LLMs.
|
||||
## Model Recommendations
|
||||
|
||||
Based on our evaluations of language models for coding tasks (using the SWE-bench dataset), we can provide some
|
||||
recommendations for model selection. Our latest benchmarking results can be found in [this spreadsheet](https://docs.google.com/spreadsheets/d/1wOUdFCMyY6Nt0AIqF705KN4JKOWgeI4wUGUP60krXXs/edit?gid=0).
|
||||
recommendations for model selection. Our latest benchmarking results can be found in
|
||||
[this spreadsheet](https://docs.google.com/spreadsheets/d/1wOUdFCMyY6Nt0AIqF705KN4JKOWgeI4wUGUP60krXXs/edit?gid=0).
|
||||
|
||||
Based on these findings and community feedback, these are the latest models that have been verified to work reasonably well with OpenHands:
|
||||
|
||||
@@ -73,16 +74,18 @@ We have a few guides for running OpenHands with specific model providers:
|
||||
- [LiteLLM Proxy](/usage/llms/litellm-proxy)
|
||||
- [Moonshot AI](/usage/llms/moonshot)
|
||||
- [OpenAI](/usage/llms/openai-llms)
|
||||
- [OpenHands](/usage/llms/openhands-llms)
|
||||
- [OpenRouter](/usage/llms/openrouter)
|
||||
|
||||
## Model Customization
|
||||
|
||||
LLM providers have specific settings that can be customized to optimize their performance with OpenHands, such as:
|
||||
|
||||
- **Custom Tokenizers**: For specialized models, you can add a suitable tokenizer
|
||||
- **Native Tool Calling**: Toggle native function/tool calling capabilities
|
||||
- **Custom Tokenizers**: For specialized models, you can add a suitable tokenizer.
|
||||
- **Native Tool Calling**: Toggle native function/tool calling capabilities.
|
||||
|
||||
For detailed information about model customization, see [LLM Configuration Options](configuration-options#llm-customization).
|
||||
For detailed information about model customization, see
|
||||
[LLM Configuration Options](/usage/configuration-options#llm-configuration).
|
||||
|
||||
### API retries and rate limits
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: OpenHands
|
||||
description: OpenHands LLM provider with access to state-of-the-art (SOTA) agentic coding models.
|
||||
---
|
||||
|
||||
## Obtain Your OpenHands LLM API Key
|
||||
|
||||
1. [Log in to OpenHands Cloud](/usage/cloud/openhands-cloud).
|
||||
2. Go to the Settings page and navigate to the `API Keys` tab.
|
||||
3. Copy your `LLM API Key`.
|
||||
|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings under the `LLM` tab:
|
||||
- `LLM Provider` to `OpenHands`
|
||||
- `LLM Model` to the model you will be using (e.g. claude-sonnet-4-20250514)
|
||||
- `API Key` to your OpenHands LLM API key copied from above
|
||||
|
||||
## Using OpenHands LLM Provider in the CLI
|
||||
|
||||
1. [Run OpenHands CLI](/usage/how-to/cli-mode).
|
||||
2. To select OpenHands as the LLM provider:
|
||||
- If this is your first time running the CLI, choose `openhands` and then select the model that you would like to use.
|
||||
- If you have previously run the CLI, run the `/settings` command and select to modify the `Basic` settings. Then
|
||||
choose `openhands` and finally the model.
|
||||
|
||||

|
||||
|
||||
## Pricing
|
||||
|
||||
Pricing follows official API provider rates.
|
||||
[You can view model prices here.](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json)
|
||||
+46
-1
@@ -29,6 +29,15 @@ sse_servers = [
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
|
||||
# SHTTP Servers - External servers that communicate via Streamable HTTP
|
||||
shttp_servers = [
|
||||
# Basic SHTTP server with just a URL
|
||||
"http://example.com:8080/mcp",
|
||||
|
||||
# SHTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
|
||||
# Stdio Servers - Local processes that communicate via standard input/output
|
||||
stdio_servers = [
|
||||
# Basic stdio server
|
||||
@@ -57,6 +66,22 @@ SSE servers are configured using either a string URL or an object with the follo
|
||||
- Type: `str`
|
||||
- Description: The URL of the SSE server
|
||||
|
||||
- `api_key` (optional)
|
||||
- Type: `str`
|
||||
- Description: API key for authentication
|
||||
|
||||
### SHTTP Servers
|
||||
|
||||
SHTTP (Streamable HTTP) servers are configured using either a string URL or an object with the following properties:
|
||||
|
||||
- `url` (required)
|
||||
- Type: `str`
|
||||
- Description: The URL of the SHTTP server
|
||||
|
||||
- `api_key` (optional)
|
||||
- Type: `str`
|
||||
- Description: API key for authentication
|
||||
|
||||
### Stdio Servers
|
||||
|
||||
Stdio servers are configured using an object with the following properties:
|
||||
@@ -84,7 +109,7 @@ Stdio servers are configured using an object with the following properties:
|
||||
When OpenHands starts, it:
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE servers.
|
||||
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.
|
||||
|
||||
@@ -93,3 +118,23 @@ The agent can then use these tools just like any built-in tool. When the agent c
|
||||
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:
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
SSE is a legacy HTTP-based transport that uses Server-Sent Events for server-to-client communication and HTTP POST requests for client-to-server communication. This transport is suitable for basic streaming scenarios but has limitations in session management and connection resumability.
|
||||
|
||||
### Streamable HTTP (SHTTP)
|
||||
SHTTP is the modern HTTP-based transport protocol that provides enhanced features over SSE:
|
||||
|
||||
- **Improved Session Management**: Supports stateful sessions with session IDs for maintaining context across requests
|
||||
- **Connection Resumability**: Can resume broken connections and replay missed messages using event IDs
|
||||
- **Bidirectional Communication**: Uses HTTP POST for client-to-server and optional SSE streams for server-to-client communication
|
||||
- **Better Error Handling**: Enhanced error reporting and recovery mechanisms
|
||||
|
||||
SHTTP is the recommended transport for HTTP-based MCP servers as it provides better reliability and features compared to the legacy SSE transport.
|
||||
|
||||
### Standard Input/Output (stdio)
|
||||
Stdio transport enables communication through standard input and output streams, making it ideal for local integrations and command-line tools. This transport is used for locally executed MCP servers that run as separate processes.
|
||||
|
||||
@@ -41,6 +41,10 @@ default, it is set to 1.
|
||||
- `language`, the language of your evaluating dataset.
|
||||
- `dataset`, the absolute position of the dataset jsonl.
|
||||
|
||||
**Skipping errors on build**
|
||||
|
||||
For debugging purposes, you can set `export EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED=true` to continue evaluation even when instances reach maximum retries. After evaluation completes, check `maximum_retries_exceeded.jsonl` for a list of failed instances, fix those issues, and then run the evaluation again with `export EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED=false`.
|
||||
|
||||
The results will be generated in evaluation/evaluation_outputs/outputs/XXX/CodeActAgent/YYY/output.jsonl, you can refer to the [example](examples/output.jsonl).
|
||||
|
||||
## Runing evaluation
|
||||
|
||||
@@ -17,6 +17,7 @@ from evaluation.utils.shared import (
|
||||
EvalMetadata,
|
||||
EvalOutput,
|
||||
assert_and_raise,
|
||||
check_maximum_retries_exceeded,
|
||||
codeact_user_response,
|
||||
get_default_sandbox_config_for_eval,
|
||||
get_metrics,
|
||||
@@ -843,3 +844,5 @@ if __name__ == '__main__':
|
||||
timeout_seconds=120 * 60, # 2 hour PER instance should be more than enough
|
||||
max_retries=5,
|
||||
)
|
||||
# Check if any instances reached maximum retries
|
||||
check_maximum_retries_exceeded(metadata.eval_output_dir)
|
||||
|
||||
@@ -38,6 +38,10 @@ Please follow instruction [here](../../README.md#setup) to setup your local deve
|
||||
> - If your LLM config has temperature=0, we will automatically use temperature=0.1 for the 2nd and 3rd attempts
|
||||
>
|
||||
> To enable this iterative protocol, set `export ITERATIVE_EVAL_MODE=true`
|
||||
>
|
||||
> **Skipping errors on build**
|
||||
>
|
||||
> For debugging purposes, you can set `export EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED=true` to continue evaluation even when instances reach maximum retries. After evaluation completes, check `maximum_retries_exceeded.jsonl` for a list of failed instances, fix those issues, and then run the evaluation again with `export EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED=false`.
|
||||
|
||||
|
||||
### Running Locally with Docker
|
||||
|
||||
@@ -28,6 +28,7 @@ from evaluation.utils.shared import (
|
||||
EvalMetadata,
|
||||
EvalOutput,
|
||||
assert_and_raise,
|
||||
check_maximum_retries_exceeded,
|
||||
codeact_user_response,
|
||||
get_default_sandbox_config_for_eval,
|
||||
get_metrics,
|
||||
@@ -968,3 +969,5 @@ if __name__ == '__main__':
|
||||
logger.info(
|
||||
f'Done! Total {len(added_instance_ids)} instances added to {output_file}'
|
||||
)
|
||||
# Check if any instances reached maximum retries
|
||||
check_maximum_retries_exceeded(metadata.eval_output_dir)
|
||||
|
||||
@@ -25,7 +25,8 @@ class Test(BaseIntegrationTest):
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to run command: {obs.content}')
|
||||
|
||||
# git add
|
||||
action = CmdRunAction(command='git add hello.py .vscode/')
|
||||
cmd_str = 'git add hello.py'
|
||||
action = CmdRunAction(command=cmd_str)
|
||||
obs = runtime.run_action(action)
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to run command: {obs.content}')
|
||||
|
||||
@@ -40,15 +41,6 @@ class Test(BaseIntegrationTest):
|
||||
reason=f'Failed to cat /workspace/hello.py: {obs.content}.',
|
||||
)
|
||||
|
||||
# check if the file /workspace/.vscode/settings.json exists
|
||||
action = CmdRunAction(command='cat /workspace/.vscode/settings.json')
|
||||
obs = runtime.run_action(action)
|
||||
if obs.exit_code != 0:
|
||||
return TestResult(
|
||||
success=False,
|
||||
reason=f'Failed to cat /workspace/.vscode/settings.json: {obs.content}.',
|
||||
)
|
||||
|
||||
# check if the staging area is empty
|
||||
action = CmdRunAction(command='git status')
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
@@ -311,6 +311,76 @@ def assert_and_raise(condition: bool, msg: str):
|
||||
raise EvalException(msg)
|
||||
|
||||
|
||||
def log_skipped_maximum_retries_exceeded(instance, metadata, error, max_retries=5):
|
||||
"""Log and skip the instance when maximum retries are exceeded.
|
||||
|
||||
Args:
|
||||
instance: The instance that failed
|
||||
metadata: The evaluation metadata
|
||||
error: The error that occurred
|
||||
max_retries: The maximum number of retries that were attempted
|
||||
|
||||
Returns:
|
||||
EvalOutput with the error information
|
||||
"""
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
# Log the error
|
||||
logger.exception(error)
|
||||
logger.error(
|
||||
f'Maximum error retries reached for instance {instance.instance_id}. '
|
||||
f'Check maximum_retries_exceeded.jsonl, fix the issue and run evaluation again. '
|
||||
f'Skipping this instance and continuing with others.'
|
||||
)
|
||||
|
||||
# Add the instance name to maximum_retries_exceeded.jsonl in the same folder as output.jsonl
|
||||
if metadata and metadata.eval_output_dir:
|
||||
retries_file_path = os.path.join(
|
||||
metadata.eval_output_dir,
|
||||
'maximum_retries_exceeded.jsonl',
|
||||
)
|
||||
try:
|
||||
# Write the instance info as a JSON line
|
||||
with open(retries_file_path, 'a') as f:
|
||||
import json
|
||||
|
||||
# No need to get Docker image as we're not including it in the error entry
|
||||
|
||||
error_entry = {
|
||||
'instance_id': instance.instance_id,
|
||||
'error': str(error),
|
||||
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}
|
||||
f.write(json.dumps(error_entry) + '\n')
|
||||
logger.info(f'Added instance {instance.instance_id} to {retries_file_path}')
|
||||
except Exception as write_error:
|
||||
logger.error(
|
||||
f'Failed to write to maximum_retries_exceeded.jsonl: {write_error}'
|
||||
)
|
||||
|
||||
return EvalOutput(
|
||||
instance_id=instance.instance_id,
|
||||
test_result={},
|
||||
error=f'Maximum retries ({max_retries}) reached: {str(error)}',
|
||||
status='error',
|
||||
)
|
||||
|
||||
|
||||
def check_maximum_retries_exceeded(eval_output_dir):
|
||||
"""Check if maximum_retries_exceeded.jsonl exists and output a message."""
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
retries_file_path = os.path.join(eval_output_dir, 'maximum_retries_exceeded.jsonl')
|
||||
if os.path.exists(retries_file_path):
|
||||
logger.info(
|
||||
'ATTENTION: Some instances reached maximum error retries and were skipped.'
|
||||
)
|
||||
logger.info(f'These instances are listed in: {retries_file_path}')
|
||||
logger.info(
|
||||
'Fix these instances and run evaluation again with EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED=false'
|
||||
)
|
||||
|
||||
|
||||
def _process_instance_wrapper(
|
||||
process_instance_func: Callable[[pd.Series, EvalMetadata, bool], EvalOutput],
|
||||
instance: pd.Series,
|
||||
@@ -363,11 +433,26 @@ def _process_instance_wrapper(
|
||||
+ f'[Encountered after {max_retries} retries. Please check the logs and report the issue.]'
|
||||
+ '-' * 10
|
||||
)
|
||||
# Raise an error after all retries & stop the evaluation
|
||||
logger.exception(e)
|
||||
raise RuntimeError(
|
||||
f'Maximum error retries reached for instance {instance.instance_id}'
|
||||
) from e
|
||||
|
||||
# Check if EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED is set to true
|
||||
skip_errors = (
|
||||
os.environ.get(
|
||||
'EVAL_SKIP_MAXIMUM_RETRIES_EXCEEDED', 'false'
|
||||
).lower()
|
||||
== 'true'
|
||||
)
|
||||
|
||||
if skip_errors:
|
||||
# Use the dedicated function to log and skip maximum retries exceeded
|
||||
return log_skipped_maximum_retries_exceeded(
|
||||
instance, metadata, e, max_retries
|
||||
)
|
||||
else:
|
||||
# Raise an error after all retries & stop the evaluation
|
||||
logger.exception(e)
|
||||
raise RuntimeError(
|
||||
f'Maximum error retries reached for instance {instance.instance_id}'
|
||||
) from e
|
||||
msg = (
|
||||
'-' * 10
|
||||
+ '\n'
|
||||
@@ -456,6 +541,10 @@ def run_evaluation(
|
||||
output_fp.close()
|
||||
logger.info('\nEvaluation finished.\n')
|
||||
|
||||
# Check if any instances reached maximum retries
|
||||
if metadata and metadata.eval_output_dir:
|
||||
check_maximum_retries_exceeded(metadata.eval_output_dir)
|
||||
|
||||
|
||||
def reset_logger_for_multiprocessing(
|
||||
logger: logging.Logger, instance_id: str, log_dir: str
|
||||
|
||||
@@ -129,4 +129,159 @@ describe("ActionSuggestions", () => {
|
||||
expect(createPRPrompt).toContain("meaningful branch name");
|
||||
expect(createPRPrompt).not.toContain("SAME branch name");
|
||||
});
|
||||
|
||||
it("should use correct provider name based on conversation git_provider, not user authenticated providers", async () => {
|
||||
// Test case for GitHub repository
|
||||
const getConversationSpy = vi.spyOn(OpenHands, "getConversation");
|
||||
getConversationSpy.mockResolvedValue({
|
||||
conversation_id: "test-github",
|
||||
title: "GitHub Test",
|
||||
selected_repository: "test-repo",
|
||||
git_provider: "github",
|
||||
selected_branch: "main",
|
||||
last_updated_at: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
status: "RUNNING",
|
||||
runtime_status: "STATUS$READY",
|
||||
url: null,
|
||||
session_api_key: null,
|
||||
});
|
||||
|
||||
// Mock user having both GitHub and Bitbucket tokens
|
||||
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
||||
getSettingsSpy.mockResolvedValue({
|
||||
...MOCK_DEFAULT_USER_SETTINGS,
|
||||
provider_tokens_set: {
|
||||
github: "github-token",
|
||||
bitbucket: "bitbucket-token",
|
||||
},
|
||||
});
|
||||
|
||||
const onSuggestionsClick = vi.fn();
|
||||
render(<ActionSuggestions onSuggestionsClick={onSuggestionsClick} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
),
|
||||
});
|
||||
|
||||
const buttons = await screen.findAllByTestId("suggestion");
|
||||
const prButton = buttons.find((button) =>
|
||||
button.textContent?.includes("Push & Create PR"),
|
||||
);
|
||||
|
||||
expect(prButton).toBeInTheDocument();
|
||||
|
||||
if (prButton) {
|
||||
prButton.click();
|
||||
}
|
||||
|
||||
// The suggestion should mention GitHub, not Bitbucket
|
||||
expect(onSuggestionsClick).toHaveBeenCalledWith(
|
||||
expect.stringContaining("GitHub")
|
||||
);
|
||||
expect(onSuggestionsClick).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("Bitbucket")
|
||||
);
|
||||
});
|
||||
|
||||
it("should use GitLab terminology when git_provider is gitlab", async () => {
|
||||
const getConversationSpy = vi.spyOn(OpenHands, "getConversation");
|
||||
getConversationSpy.mockResolvedValue({
|
||||
conversation_id: "test-gitlab",
|
||||
title: "GitLab Test",
|
||||
selected_repository: "test-repo",
|
||||
git_provider: "gitlab",
|
||||
selected_branch: "main",
|
||||
last_updated_at: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
status: "RUNNING",
|
||||
runtime_status: "STATUS$READY",
|
||||
url: null,
|
||||
session_api_key: null,
|
||||
});
|
||||
|
||||
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
||||
getSettingsSpy.mockResolvedValue({
|
||||
...MOCK_DEFAULT_USER_SETTINGS,
|
||||
provider_tokens_set: {
|
||||
gitlab: "gitlab-token",
|
||||
},
|
||||
});
|
||||
|
||||
const onSuggestionsClick = vi.fn();
|
||||
render(<ActionSuggestions onSuggestionsClick={onSuggestionsClick} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
),
|
||||
});
|
||||
|
||||
const buttons = await screen.findAllByTestId("suggestion");
|
||||
const prButton = buttons.find((button) =>
|
||||
button.textContent?.includes("Push & Create PR"),
|
||||
);
|
||||
|
||||
if (prButton) {
|
||||
prButton.click();
|
||||
}
|
||||
|
||||
// Should mention GitLab and "merge request" instead of "pull request"
|
||||
expect(onSuggestionsClick).toHaveBeenCalledWith(
|
||||
expect.stringContaining("GitLab")
|
||||
);
|
||||
expect(onSuggestionsClick).toHaveBeenCalledWith(
|
||||
expect.stringContaining("merge request")
|
||||
);
|
||||
});
|
||||
|
||||
it("should use Bitbucket terminology when git_provider is bitbucket", async () => {
|
||||
const getConversationSpy = vi.spyOn(OpenHands, "getConversation");
|
||||
getConversationSpy.mockResolvedValue({
|
||||
conversation_id: "test-bitbucket",
|
||||
title: "Bitbucket Test",
|
||||
selected_repository: "test-repo",
|
||||
git_provider: "bitbucket",
|
||||
selected_branch: "main",
|
||||
last_updated_at: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
status: "RUNNING",
|
||||
runtime_status: "STATUS$READY",
|
||||
url: null,
|
||||
session_api_key: null,
|
||||
});
|
||||
|
||||
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
||||
getSettingsSpy.mockResolvedValue({
|
||||
...MOCK_DEFAULT_USER_SETTINGS,
|
||||
provider_tokens_set: {
|
||||
bitbucket: "bitbucket-token",
|
||||
},
|
||||
});
|
||||
|
||||
const onSuggestionsClick = vi.fn();
|
||||
render(<ActionSuggestions onSuggestionsClick={onSuggestionsClick} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
),
|
||||
});
|
||||
|
||||
const buttons = await screen.findAllByTestId("suggestion");
|
||||
const prButton = buttons.find((button) =>
|
||||
button.textContent?.includes("Push & Create PR"),
|
||||
);
|
||||
|
||||
if (prButton) {
|
||||
prButton.click();
|
||||
}
|
||||
|
||||
// Should mention Bitbucket
|
||||
expect(onSuggestionsClick).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Bitbucket")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,4 +110,29 @@ describe("TaskCard", () => {
|
||||
expect(launchButton).toHaveTextContent(/Loading/i);
|
||||
expect(launchButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should navigate to the conversation page after creating a conversation", async () => {
|
||||
const createConversationSpy = vi.spyOn(OpenHands, "createConversation");
|
||||
createConversationSpy.mockResolvedValue({
|
||||
conversation_id: "test-conversation-id",
|
||||
title: "Test Conversation",
|
||||
selected_repository: "repo1",
|
||||
selected_branch: "main",
|
||||
git_provider: "github",
|
||||
last_updated_at: "2023-01-01T00:00:00Z",
|
||||
created_at: "2023-01-01T00:00:00Z",
|
||||
status: "RUNNING",
|
||||
runtime_status: "STATUS$READY",
|
||||
url: null,
|
||||
session_api_key: null
|
||||
});
|
||||
|
||||
renderTaskCard();
|
||||
|
||||
const launchButton = screen.getByTestId("task-launch-button");
|
||||
await userEvent.click(launchButton);
|
||||
|
||||
// Wait for navigation to the conversation page
|
||||
await screen.findByTestId("conversation-screen");
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+739
-779
File diff suppressed because it is too large
Load Diff
+13
-12
@@ -1,36 +1,36 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.49.1",
|
||||
"version": "0.49.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.0-beta.15",
|
||||
"@heroui/react": "^2.8.1",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.6.3",
|
||||
"@react-router/serve": "^7.6.3",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@react-types/shared": "^3.29.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.4.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.10.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.23.5",
|
||||
"framer-motion": "^12.23.6",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"isbot": "^5.1.28",
|
||||
"jose": "^6.0.11",
|
||||
"jose": "^6.0.12",
|
||||
"lucide-react": "^0.525.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.257.0",
|
||||
@@ -42,14 +42,15 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.6.3",
|
||||
"react-router": "^7.7.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"vite": "^7.0.4",
|
||||
"vite": "^7.0.5",
|
||||
"web-vitals": "^5.0.3",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
@@ -84,14 +85,14 @@
|
||||
"@babel/types": "^7.28.1",
|
||||
"@mswjs/socket.io-binding": "^0.2.0",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@react-router/dev": "^7.6.3",
|
||||
"@react-router/dev": "^7.7.0",
|
||||
"@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.13",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* - Please do NOT modify this file.
|
||||
*/
|
||||
|
||||
const PACKAGE_VERSION = '2.10.2'
|
||||
const PACKAGE_VERSION = '2.10.3'
|
||||
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
|
||||
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||
const activeClientIds = new Set()
|
||||
|
||||
@@ -489,6 +489,24 @@ 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;
|
||||
|
||||
@@ -50,9 +50,11 @@ export interface GetConfigResponse {
|
||||
GITHUB_CLIENT_ID: string;
|
||||
POSTHOG_CLIENT_KEY: string;
|
||||
STRIPE_PUBLISHABLE_KEY?: string;
|
||||
PROVIDERS_CONFIGURED?: Provider[];
|
||||
FEATURE_FLAGS: {
|
||||
ENABLE_BILLING: boolean;
|
||||
HIDE_LLM_SETTINGS: boolean;
|
||||
HIDE_MICROAGENT_MANAGEMENT?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,11 @@ export function ActionSuggestions({
|
||||
const [hasPullRequest, setHasPullRequest] = React.useState(false);
|
||||
|
||||
const providersAreSet = providers.length > 0;
|
||||
const isGitLab = providers.includes("gitlab");
|
||||
const isBitbucket = providers.includes("bitbucket");
|
||||
|
||||
// Use the git_provider from the conversation, not the user's authenticated providers
|
||||
const currentGitProvider = conversation?.git_provider;
|
||||
const isGitLab = currentGitProvider === "gitlab";
|
||||
const isBitbucket = currentGitProvider === "bitbucket";
|
||||
|
||||
const pr = isGitLab ? "merge request" : "pull request";
|
||||
const prShort = isGitLab ? "MR" : "PR";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { ul, ol } from "../markdown/list";
|
||||
@@ -94,7 +95,7 @@ export function ChatMessage({
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{message}
|
||||
</Markdown>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
@@ -46,7 +47,7 @@ export function ErrorMessage({ errorId, defaultMessage }: ErrorMessageProps) {
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{defaultMessage}
|
||||
</Markdown>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Trans, useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import { Link } from "react-router";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
@@ -199,7 +200,7 @@ export function ExpandableMessage({
|
||||
ol,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
@@ -52,7 +53,7 @@ export function GenericEventMessage({
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
|
||||
@@ -10,6 +10,9 @@ 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,
|
||||
@@ -32,8 +35,10 @@ 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,
|
||||
@@ -56,6 +61,13 @@ 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 (
|
||||
@@ -83,8 +95,10 @@ 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),
|
||||
}));
|
||||
@@ -94,6 +108,14 @@ 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);
|
||||
@@ -102,6 +124,14 @@ 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);
|
||||
@@ -133,6 +163,26 @@ 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) {
|
||||
@@ -143,11 +193,15 @@ 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;
|
||||
|
||||
@@ -195,8 +249,8 @@ export function RepositorySelectionForm({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{renderProviderSelector()}
|
||||
{renderRepositorySelector()}
|
||||
|
||||
{renderBranchSelector()}
|
||||
|
||||
<BrandButton
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface RepositoryDropdownProps {
|
||||
onSelectionChange: (key: React.Key | null) => void;
|
||||
onInputChange: (value: string) => void;
|
||||
defaultFilter?: (textValue: string, inputValue: string) => boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export function RepositoryDropdown({
|
||||
@@ -15,6 +16,7 @@ export function RepositoryDropdown({
|
||||
onSelectionChange,
|
||||
onInputChange,
|
||||
defaultFilter,
|
||||
isDisabled = false,
|
||||
}: RepositoryDropdownProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,12 +24,13 @@ export function RepositoryDropdown({
|
||||
<SettingsDropdownInput
|
||||
testId="repo-dropdown"
|
||||
name="repo-dropdown"
|
||||
placeholder={t(I18nKey.REPOSITORY$SELECT_REPO)}
|
||||
placeholder={isDisabled ? t("Please select a provider first") : t(I18nKey.REPOSITORY$SELECT_REPO)}
|
||||
items={items}
|
||||
wrapperClassName="max-w-[500px]"
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
defaultFilter={defaultFilter}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import { SuggestedTask } from "./task.types";
|
||||
import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation";
|
||||
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
|
||||
@@ -24,17 +25,25 @@ export function TaskCard({ task }: TaskCardProps) {
|
||||
const { mutate: createConversation, isPending } = useCreateConversation();
|
||||
const isCreatingConversation = useIsCreatingConversation();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLaunchConversation = () => {
|
||||
setOptimisticUserMessage(t("TASK$ADDRESSING_TASK"));
|
||||
|
||||
return createConversation({
|
||||
repository: {
|
||||
name: task.repo,
|
||||
gitProvider: task.git_provider,
|
||||
return createConversation(
|
||||
{
|
||||
repository: {
|
||||
name: task.repo,
|
||||
gitProvider: task.git_provider,
|
||||
},
|
||||
suggestedTask: task,
|
||||
},
|
||||
suggestedTask: task,
|
||||
});
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
navigate(`/conversations/${data.conversation_id}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Determine the correct URL format based on git provider
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { setAddMicroagentModalVisible } from "#/state/microagent-management-slice";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
export function MicroagentManagementAddMicroagentButton() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { addMicroagentModalVisible } = useSelector(
|
||||
(state: RootState) => state.microagentManagement,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = () => {
|
||||
dispatch(setAddMicroagentModalVisible(!addMicroagentModalVisible));
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm font-normal text-[#8480FF] cursor-pointer"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MicroagentManagementLearnThisRepoProps {
|
||||
repositoryUrl: string;
|
||||
}
|
||||
|
||||
export function MicroagentManagementLearnThisRepo({
|
||||
repositoryUrl,
|
||||
}: MicroagentManagementLearnThisRepoProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
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"
|
||||
>
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$LEARN_THIS_REPO)}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export function MicroagentManagementMain() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { selectedMicroagent } = 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>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export interface Microagent {
|
||||
id: string;
|
||||
name: string;
|
||||
repositoryUrl: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface MicroagentManagementMicroagentCardProps {
|
||||
microagent: Microagent;
|
||||
}
|
||||
|
||||
export function MicroagentManagementMicroagentCard({
|
||||
microagent,
|
||||
}: MicroagentManagementMicroagentCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { MicroagentManagementRepoMicroagent } from "./microagent-management-repo-microagent";
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const numberOfRepoMicroagents = repoMicroagents.length;
|
||||
|
||||
if (numberOfRepoMicroagents === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{repoMicroagents.map((repoMicroagent) => (
|
||||
<MicroagentManagementRepoMicroagent
|
||||
key={repoMicroagent.id}
|
||||
repoMicroagent={repoMicroagent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import QuestionCircleIcon from "#/icons/question-circle.svg?react";
|
||||
|
||||
export function MicroagentManagementSidebarHeader() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-white text-[28px] font-bold">
|
||||
{t(I18nKey.MICROAGENT_MANAGEMENT$DESCRIPTION)}
|
||||
</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" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
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 { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export function MicroagentManagementSidebarTabs() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Tabs
|
||||
aria-label="Options"
|
||||
classNames={{
|
||||
base: "py-6",
|
||||
tabList:
|
||||
"w-full bg-transparent border border-[#ffffff40] rounded-[6px]",
|
||||
tab: "px-2 h-[22px]",
|
||||
tabContent: "text-white text-[12px] font-normal",
|
||||
panel: "py-0",
|
||||
cursor: "bg-[#C9B97480] rounded-sm",
|
||||
}}
|
||||
>
|
||||
<Tab key="personal" title={t(I18nKey.COMMON$PERSONAL)}>
|
||||
<MicroagentManagementMicroagents />
|
||||
</Tab>
|
||||
<Tab key="repositories" title={t(I18nKey.COMMON$REPOSITORIES)}>
|
||||
<MicroagentManagementRepoMicroagents />
|
||||
</Tab>
|
||||
<Tab key="organizations" title={t(I18nKey.COMMON$ORGANIZATIONS)}>
|
||||
<MicroagentManagementMicroagents />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { MicroagentManagementSidebarHeader } from "./microagent-management-sidebar-header";
|
||||
import { MicroagentManagementSidebarTabs } from "./microagent-management-sidebar-tabs";
|
||||
|
||||
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">
|
||||
<MicroagentManagementSidebarHeader />
|
||||
<MicroagentManagementSidebarTabs />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,32 +32,26 @@ export function MCPConfigEditor({ mcpConfig, onChange }: MCPConfigEditorProps) {
|
||||
{t(I18nKey.SETTINGS$MCP_DESCRIPTION)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/mcp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-blue-400 hover:underline mr-3"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{t(I18nKey.COMMON$DOCUMENTATION)}
|
||||
</a>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
>
|
||||
{isEditing
|
||||
? t(I18nKey.SETTINGS$MCP_CANCEL)
|
||||
: t(I18nKey.SETTINGS$MCP_EDIT_CONFIGURATION)}
|
||||
</BrandButton>
|
||||
{!isEditing && (
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex items-center">
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
{t(I18nKey.SETTINGS$MCP_EDIT_CONFIGURATION)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)}
|
||||
<div>
|
||||
{isEditing ? (
|
||||
<MCPJsonEditor mcpConfig={mcpConfig} onChange={handleConfigChange} />
|
||||
<MCPJsonEditor
|
||||
mcpConfig={mcpConfig}
|
||||
onChange={handleConfigChange}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-6">
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { MCPConfig } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../brand-button";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
interface MCPJsonEditorProps {
|
||||
mcpConfig?: MCPConfig;
|
||||
onChange: (config: MCPConfig) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) {
|
||||
export function MCPJsonEditor({
|
||||
mcpConfig,
|
||||
onChange,
|
||||
onCancel,
|
||||
}: MCPJsonEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [configText, setConfigText] = useState(() =>
|
||||
mcpConfig
|
||||
@@ -65,11 +71,31 @@ export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2 text-sm text-gray-400">
|
||||
{t(I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION)}
|
||||
</div>
|
||||
<p className="mb-2 text-sm text-gray-400">
|
||||
<Trans
|
||||
i18nKey={I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION}
|
||||
components={{
|
||||
a: (
|
||||
<a
|
||||
href="https://docs.all-hands.dev/usage/mcp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:underline"
|
||||
>
|
||||
documentation
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<textarea
|
||||
className="w-full h-64 p-2 text-sm font-mono bg-base-tertiary rounded-md focus:border-blue-500 focus:outline-hidden"
|
||||
className={cn(
|
||||
"w-full h-64 resize-y p-2 rounded-sm text-sm font-mono",
|
||||
"bg-tertiary border border-[#717888]",
|
||||
"placeholder:italic placeholder:text-tertiary-alt",
|
||||
"focus:outline-none focus:ring-1 focus:ring-primary",
|
||||
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
|
||||
)}
|
||||
value={configText}
|
||||
onChange={handleTextChange}
|
||||
spellCheck="false"
|
||||
@@ -87,9 +113,12 @@ export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) {
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<div className="mt-4 flex justify-end gap-3">
|
||||
<BrandButton type="button" variant="secondary" onClick={onCancel}>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
<BrandButton type="button" variant="primary" onClick={handleSave}>
|
||||
{t(I18nKey.SETTINGS$MCP_APPLY_CHANGES)}
|
||||
{t(I18nKey.SETTINGS$MCP_CONFIRM_CHANGES)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ConversationPanelWrapper } from "../conversation-panel/conversation-pan
|
||||
import { useLogout } from "#/hooks/mutation/use-logout";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
import { MicroagentManagementButton } from "#/components/shared/buttons/microagent-management-button";
|
||||
|
||||
export function Sidebar() {
|
||||
const location = useLocation();
|
||||
@@ -36,6 +37,9 @@ export function Sidebar() {
|
||||
const shouldHideLlmSettings =
|
||||
config?.FEATURE_FLAGS.HIDE_LLM_SETTINGS && config?.APP_MODE === "saas";
|
||||
|
||||
const shouldHideMicroagentManagement =
|
||||
config?.FEATURE_FLAGS.HIDE_MICROAGENT_MANAGEMENT;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldHideLlmSettings) return;
|
||||
|
||||
@@ -79,6 +83,11 @@ export function Sidebar() {
|
||||
}
|
||||
disabled={settings?.EMAIL_VERIFIED === false}
|
||||
/>
|
||||
{!shouldHideMicroagentManagement && (
|
||||
<MicroagentManagementButton
|
||||
disabled={settings?.EMAIL_VERIFIED === false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row md:flex-col md:items-center gap-[26px] md:mb-4">
|
||||
|
||||
@@ -10,13 +10,19 @@ import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
|
||||
import BitbucketLogo from "#/assets/branding/bitbucket-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
appMode?: GetConfigResponse["APP_MODE"] | null;
|
||||
providersConfigured?: Provider[];
|
||||
}
|
||||
|
||||
export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
export function AuthModal({
|
||||
githubAuthUrl,
|
||||
appMode,
|
||||
providersConfigured,
|
||||
}: AuthModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const gitlabAuthUrl = useAuthUrl({
|
||||
@@ -50,6 +56,24 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Only show buttons if providers are configured and include the specific provider
|
||||
const showGithub =
|
||||
providersConfigured &&
|
||||
providersConfigured.length > 0 &&
|
||||
providersConfigured.includes("github");
|
||||
const showGitlab =
|
||||
providersConfigured &&
|
||||
providersConfigured.length > 0 &&
|
||||
providersConfigured.includes("gitlab");
|
||||
const showBitbucket =
|
||||
providersConfigured &&
|
||||
providersConfigured.length > 0 &&
|
||||
providersConfigured.includes("bitbucket");
|
||||
|
||||
// Check if no providers are configured
|
||||
const noProvidersConfigured =
|
||||
!providersConfigured || providersConfigured.length === 0;
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="border border-tertiary">
|
||||
@@ -61,35 +85,49 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitHubAuth}
|
||||
className="w-full"
|
||||
startContent={<GitHubLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITHUB$CONNECT_TO_GITHUB)}
|
||||
</BrandButton>
|
||||
{noProvidersConfigured ? (
|
||||
<div className="text-center p-4 text-muted-foreground">
|
||||
{t(I18nKey.AUTH$NO_PROVIDERS_CONFIGURED)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{showGithub && (
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitHubAuth}
|
||||
className="w-full"
|
||||
startContent={<GitHubLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITHUB$CONNECT_TO_GITHUB)}
|
||||
</BrandButton>
|
||||
)}
|
||||
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitLabAuth}
|
||||
className="w-full"
|
||||
startContent={<GitLabLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITLAB$CONNECT_TO_GITLAB)}
|
||||
</BrandButton>
|
||||
{showGitlab && (
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitLabAuth}
|
||||
className="w-full"
|
||||
startContent={<GitLabLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITLAB$CONNECT_TO_GITLAB)}
|
||||
</BrandButton>
|
||||
)}
|
||||
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleBitbucketAuth}
|
||||
className="w-full"
|
||||
startContent={<BitbucketLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
|
||||
</BrandButton>
|
||||
{showBitbucket && (
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleBitbucketAuth}
|
||||
className="w-full"
|
||||
startContent={<BitbucketLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
|
||||
</BrandButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { TooltipButton } from "./tooltip-button";
|
||||
import UnionIcon from "#/icons/union.svg?react";
|
||||
|
||||
interface MicroagentManagementButtonProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentManagementButton({
|
||||
disabled = false,
|
||||
}: MicroagentManagementButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const microagentManagement = t(I18nKey.MICROAGENT_MANAGEMENT$TITLE);
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltip={microagentManagement}
|
||||
ariaLabel={microagentManagement}
|
||||
navLinkTo="/microagent-management"
|
||||
testId="microagent-management-button"
|
||||
disabled={disabled}
|
||||
>
|
||||
<UnionIcon />
|
||||
</TooltipButton>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { FaX } from "react-icons/fa6";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { BrandBadge } from "../badge";
|
||||
import XIcon from "#/icons/x.svg?react";
|
||||
|
||||
interface BadgeInputProps {
|
||||
name?: string;
|
||||
@@ -49,14 +49,15 @@ export function BadgeInput({
|
||||
>
|
||||
{value.map((badge, index) => (
|
||||
<div key={index}>
|
||||
<BrandBadge className="flex items-center gap-0.5">
|
||||
<BrandBadge className="flex items-center gap-0.5 py-1 px-2.5 text-sm text-[#0D0F11] font-semibold leading-[16px]">
|
||||
{badge}
|
||||
<button
|
||||
data-testid="remove-button"
|
||||
type="button"
|
||||
onClick={() => removeBadge(index)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<FaX className="w-3 h-3 text-black" />
|
||||
<XIcon width={14} height={14} color="#000000" />
|
||||
</button>
|
||||
</BrandBadge>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,11 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { mapProvider } from "#/utils/map-provider";
|
||||
import { VERIFIED_MODELS, VERIFIED_PROVIDERS } from "#/utils/verified-models";
|
||||
import {
|
||||
VERIFIED_MODELS,
|
||||
VERIFIED_PROVIDERS,
|
||||
VERIFIED_OPENHANDS_MODELS,
|
||||
} from "#/utils/verified-models";
|
||||
import { extractModelAndProvider } from "#/utils/extract-model-and-provider";
|
||||
|
||||
interface ModelSelectorProps {
|
||||
@@ -29,6 +33,14 @@ export function ModelSelector({
|
||||
);
|
||||
const [selectedModel, setSelectedModel] = React.useState<string | null>(null);
|
||||
|
||||
// Get the appropriate verified models array based on the selected provider
|
||||
const getVerifiedModels = () => {
|
||||
if (selectedProvider === "openhands") {
|
||||
return VERIFIED_OPENHANDS_MODELS;
|
||||
}
|
||||
return VERIFIED_MODELS;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (currentModel) {
|
||||
// runs when resetting to defaults
|
||||
@@ -151,18 +163,20 @@ export function ModelSelector({
|
||||
}}
|
||||
>
|
||||
<AutocompleteSection title={t(I18nKey.MODEL_SELECTOR$VERIFIED)}>
|
||||
{VERIFIED_MODELS.filter((model) =>
|
||||
models[selectedProvider || ""]?.models?.includes(model),
|
||||
).map((model) => (
|
||||
<AutocompleteItem key={model}>{model}</AutocompleteItem>
|
||||
))}
|
||||
{getVerifiedModels()
|
||||
.filter((model) =>
|
||||
models[selectedProvider || ""]?.models?.includes(model),
|
||||
)
|
||||
.map((model) => (
|
||||
<AutocompleteItem key={model}>{model}</AutocompleteItem>
|
||||
))}
|
||||
</AutocompleteSection>
|
||||
{models[selectedProvider || ""]?.models?.some(
|
||||
(model) => !VERIFIED_MODELS.includes(model),
|
||||
(model) => !getVerifiedModels().includes(model),
|
||||
) ? (
|
||||
<AutocompleteSection title={t(I18nKey.MODEL_SELECTOR$OTHERS)}>
|
||||
{models[selectedProvider || ""]?.models
|
||||
.filter((model) => !VERIFIED_MODELS.includes(model))
|
||||
.filter((model) => !getVerifiedModels().includes(model))
|
||||
.map((model) => (
|
||||
<AutocompleteItem
|
||||
data-testid={`model-item-${model}`}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
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
|
||||
});
|
||||
};
|
||||
@@ -50,8 +50,7 @@ export enum I18nKey {
|
||||
SETTINGS$NAV_MCP = "SETTINGS$NAV_MCP",
|
||||
SETTINGS$MCP_CONFIGURATION = "SETTINGS$MCP_CONFIGURATION",
|
||||
SETTINGS$MCP_EDIT_CONFIGURATION = "SETTINGS$MCP_EDIT_CONFIGURATION",
|
||||
SETTINGS$MCP_CANCEL = "SETTINGS$MCP_CANCEL",
|
||||
SETTINGS$MCP_APPLY_CHANGES = "SETTINGS$MCP_APPLY_CHANGES",
|
||||
SETTINGS$MCP_CONFIRM_CHANGES = "SETTINGS$MCP_CONFIRM_CHANGES",
|
||||
SETTINGS$MCP_CONFIG_DESCRIPTION = "SETTINGS$MCP_CONFIG_DESCRIPTION",
|
||||
SETTINGS$MCP_CONFIG_ERROR = "SETTINGS$MCP_CONFIG_ERROR",
|
||||
SETTINGS$MCP_CONFIG_EXAMPLE = "SETTINGS$MCP_CONFIG_EXAMPLE",
|
||||
@@ -579,7 +578,6 @@ export enum I18nKey {
|
||||
BITBUCKET$TOKEN_LINK_TEXT = "BITBUCKET$TOKEN_LINK_TEXT",
|
||||
BITBUCKET$INSTRUCTIONS_LINK_TEXT = "BITBUCKET$INSTRUCTIONS_LINK_TEXT",
|
||||
GITLAB$OR_SEE = "GITLAB$OR_SEE",
|
||||
COMMON$DOCUMENTATION = "COMMON$DOCUMENTATION",
|
||||
AGENT_ERROR$ERROR_ACTION_NOT_EXECUTED = "AGENT_ERROR$ERROR_ACTION_NOT_EXECUTED",
|
||||
DIFF_VIEWER$LOADING = "DIFF_VIEWER$LOADING",
|
||||
DIFF_VIEWER$GETTING_LATEST_CHANGES = "DIFF_VIEWER$GETTING_LATEST_CHANGES",
|
||||
@@ -682,8 +680,32 @@ export enum I18nKey {
|
||||
COMMON$OPTIONAL = "COMMON$OPTIONAL",
|
||||
BROWSER$SERVER_MESSAGE = "BROWSER$SERVER_MESSAGE",
|
||||
API$NO_KEY_AVAILABLE = "API$NO_KEY_AVAILABLE",
|
||||
MICROAGENT_MANAGEMENT$TITLE = "MICROAGENT_MANAGEMENT$TITLE",
|
||||
MICROAGENT_MANAGEMENT$DESCRIPTION = "MICROAGENT_MANAGEMENT$DESCRIPTION",
|
||||
MICROAGENT_MANAGEMENT$USE_MICROAGENTS = "MICROAGENT_MANAGEMENT$USE_MICROAGENTS",
|
||||
AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR = "AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR",
|
||||
AUTH$NO_PROVIDERS_CONFIGURED = "AUTH$NO_PROVIDERS_CONFIGURED",
|
||||
COMMON$TERMS_OF_SERVICE = "COMMON$TERMS_OF_SERVICE",
|
||||
COMMON$AND = "COMMON$AND",
|
||||
COMMON$PRIVACY_POLICY = "COMMON$PRIVACY_POLICY",
|
||||
COMMON$PERSONAL = "COMMON$PERSONAL",
|
||||
COMMON$REPOSITORIES = "COMMON$REPOSITORIES",
|
||||
COMMON$ORGANIZATIONS = "COMMON$ORGANIZATIONS",
|
||||
COMMON$ADD_MICROAGENT = "COMMON$ADD_MICROAGENT",
|
||||
COMMON$CREATED_ON = "COMMON$CREATED_ON",
|
||||
MICROAGENT_MANAGEMENT$LEARN_THIS_REPO = "MICROAGENT_MANAGEMENT$LEARN_THIS_REPO",
|
||||
MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT = "MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT",
|
||||
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$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",
|
||||
MICROAGENT_MANAGEMENT$ADD_TRIGGERS = "MICROAGENT_MANAGEMENT$ADD_TRIGGERS",
|
||||
MICROAGENT_MANAGEMENT$HELP_TEXT_DESCRIBING_VALID_TRIGGERS = "MICROAGENT_MANAGEMENT$HELP_TEXT_DESCRIBING_VALID_TRIGGERS",
|
||||
COMMON$FOR_EXAMPLE = "COMMON$FOR_EXAMPLE",
|
||||
COMMON$TEST_DB_MIGRATION = "COMMON$TEST_DB_MIGRATION",
|
||||
COMMON$RUN_TEST = "COMMON$RUN_TEST",
|
||||
COMMON$RUN_APP = "COMMON$RUN_APP",
|
||||
COMMON$LEARN_FILE_STRUCTURE = "COMMON$LEARN_FILE_STRUCTURE",
|
||||
}
|
||||
|
||||
@@ -799,53 +799,37 @@
|
||||
"de": "Konfiguration bearbeiten",
|
||||
"uk": "Редагувати налаштування"
|
||||
},
|
||||
"SETTINGS$MCP_CANCEL": {
|
||||
"en": "Cancel",
|
||||
"ja": "キャンセル",
|
||||
"zh-CN": "取消",
|
||||
"zh-TW": "取消",
|
||||
"ko-KR": "취소",
|
||||
"no": "Avbryt",
|
||||
"it": "Annulla",
|
||||
"pt": "Cancelar",
|
||||
"es": "Cancelar",
|
||||
"ar": "إلغاء",
|
||||
"fr": "Annuler",
|
||||
"tr": "İptal",
|
||||
"de": "Abbrechen",
|
||||
"uk": "Скасувати"
|
||||
},
|
||||
"SETTINGS$MCP_APPLY_CHANGES": {
|
||||
"en": "Apply Changes",
|
||||
"ja": "変更を適用",
|
||||
"zh-CN": "应用更改",
|
||||
"zh-TW": "應用更改",
|
||||
"ko-KR": "변경 사항 적용",
|
||||
"no": "Bruk endringer",
|
||||
"it": "Applica modifiche",
|
||||
"pt": "Aplicar alterações",
|
||||
"es": "Aplicar cambios",
|
||||
"ar": "تطبيق التغييرات",
|
||||
"fr": "Appliquer les modifications",
|
||||
"tr": "Değişiklikleri Uygula",
|
||||
"de": "Änderungen anwenden",
|
||||
"uk": "Застосувати зміни"
|
||||
"SETTINGS$MCP_CONFIRM_CHANGES": {
|
||||
"en": "Confirm Changes",
|
||||
"ja": "変更を確定",
|
||||
"zh-CN": "确认更改",
|
||||
"zh-TW": "確認變更",
|
||||
"ko-KR": "변경 사항 확인",
|
||||
"no": "Bekreft endringer",
|
||||
"it": "Conferma modifiche",
|
||||
"pt": "Confirmar alterações",
|
||||
"es": "Confirmar cambios",
|
||||
"ar": "تأكيد التغييرات",
|
||||
"fr": "Confirmer les modifications",
|
||||
"tr": "Değişiklikleri Onayla",
|
||||
"de": "Änderungen bestätigen",
|
||||
"uk": "Підтвердити зміни"
|
||||
},
|
||||
"SETTINGS$MCP_CONFIG_DESCRIPTION": {
|
||||
"en": "Edit the JSON configuration for MCP servers below. The configuration must include both sse_servers and stdio_servers arrays.",
|
||||
"ja": "以下のMCPサーバーのJSON設定を編集してください。設定にはsse_serversとstdio_serversの両方の配列を含める必要があります。",
|
||||
"zh-CN": "在下方编辑MCP服务器的JSON配置。配置必须包含sse_servers和stdio_servers数组。",
|
||||
"zh-TW": "在下方編輯MCP服務器的JSON配置。配置必須包含sse_servers和stdio_servers數組。",
|
||||
"ko-KR": "아래에서 MCP 서버의 JSON 구성을 편집하세요. 구성에는 sse_servers와 stdio_servers 배열이 모두 포함되어야 합니다.",
|
||||
"no": "Rediger JSON-konfigurasjonen for MCP-servere nedenfor. Konfigurasjonen må inkludere både sse_servers og stdio_servers-matriser.",
|
||||
"it": "Modifica la configurazione JSON per i server MCP qui sotto. La configurazione deve includere sia gli array sse_servers che stdio_servers.",
|
||||
"pt": "Edite a configuração JSON para servidores MCP abaixo. A configuração deve incluir os arrays sse_servers e stdio_servers.",
|
||||
"es": "Edite la configuración JSON para los servidores MCP a continuación. La configuración debe incluir tanto los arrays sse_servers como stdio_servers.",
|
||||
"ar": "قم بتحرير تكوين JSON لخوادم MCP أدناه. يجب أن يتضمن التكوين كلاً من مصفوفات sse_servers و stdio_servers.",
|
||||
"fr": "Modifiez la configuration JSON pour les serveurs MCP ci-dessous. La configuration doit inclure à la fois les tableaux sse_servers et stdio_servers.",
|
||||
"tr": "Aşağıdaki MCP sunucuları için JSON yapılandırmasını düzenleyin. Yapılandırma hem sse_servers hem de stdio_servers dizilerini içermelidir.",
|
||||
"de": "Bearbeiten Sie die JSON-Konfiguration für MCP-Server unten. Die Konfiguration muss sowohl sse_servers- als auch stdio_servers-Arrays enthalten.",
|
||||
"uk": "Відредагуйте JSON-конфігурацію для серверів MCP нижче. Конфігурація повинна включати масиви sse_servers та stdio_servers."
|
||||
"en": "Edit the JSON configuration for MCP servers below. The configuration must include both sse_servers and stdio_servers arrays. For full configuration details and integration examples, see the <a>documentation</a>.",
|
||||
"ja": "以下のMCPサーバーのJSON設定を編集してください。設定にはsse_serversとstdio_serversの両方の配列を含める必要があります。詳細な設定と統合の例については、<a>ドキュメント</a>を参照してください。",
|
||||
"zh-CN": "在下方编辑MCP服务器的JSON配置。配置必须包含sse_servers和stdio_servers数组。有关完整的配置详情和集成示例,请参阅<a>文档</a>。",
|
||||
"zh-TW": "在下方編輯MCP服務器的JSON配置。配置必須包含sse_servers和stdio_servers數組。有關完整配置詳情與整合範例,請參閱<a>文件</a>。",
|
||||
"ko-KR": "아래에서 MCP 서버의 JSON 구성을 편집하세요. 구성에는 sse_servers와 stdio_servers 배열이 모두 포함되어야 합니다. 전체 구성 세부 정보와 통합 예시는 <a>문서</a>를 참조하세요.",
|
||||
"no": "Rediger JSON-konfigurasjonen for MCP-servere nedenfor. Konfigurasjonen må inkludere både sse_servers og stdio_servers-matriser. For detaljer om konfigurasjon og integrasjon, se <a>dokumentasjonen</a>.",
|
||||
"it": "Modifica la configurazione JSON per i server MCP qui sotto. La configurazione deve includere sia gli array sse_servers che stdio_servers. Per i dettagli completi sulla configurazione e gli esempi di integrazione, vedi la <a>documentazione</a>.",
|
||||
"pt": "Edite a configuração JSON para servidores MCP abaixo. A configuração deve incluir os arrays sse_servers e stdio_servers. Para detalhes completos de configuração e exemplos de integração, veja a <a>documentação</a>.",
|
||||
"es": "Edite la configuración JSON para los servidores MCP a continuación. La configuración debe incluir tanto los arrays sse_servers como stdio_servers. Para ver detalles completos de configuración y ejemplos de integración, consulte la <a>documentación</a>.",
|
||||
"ar": "قم بتحرير تكوين JSON لخوادم MCP أدناه. يجب أن يتضمن التكوين كلاً من مصفوفات sse_servers و stdio_servers. للحصول على تفاصيل التكوين الكاملة وأمثلة التكامل، راجع <a>التوثيق</a>.",
|
||||
"fr": "Modifiez la configuration JSON pour les serveurs MCP ci-dessous. La configuration doit inclure à la fois les tableaux sse_servers et stdio_servers. Pour plus de détails sur la configuration et des exemples d'intégration, voir la <a>documentation</a>.",
|
||||
"tr": "Aşağıdaki MCP sunucuları için JSON yapılandırmasını düzenleyin. Yapılandırma hem sse_servers hem de stdio_servers dizilerini içermelidir. Tam yapılandırma ayrıntıları ve entegrasyon örnekleri için <a>belgeler</a>'e bakın.",
|
||||
"de": "Bearbeiten Sie die JSON-Konfiguration für MCP-Server unten. Die Konfiguration muss sowohl sse_servers- als auch stdio_servers-Arrays enthalten. Weitere Konfigurationsdetails und Integrationsbeispiele finden Sie in der <a>Dokumentation</a>.",
|
||||
"uk": "Відредагуйте JSON-конфігурацію для серверів MCP нижче. Конфігурація повинна включати масиви sse_servers та stdio_servers. Повну інформацію про конфігурацію та приклади інтеграції дивіться в <a>документації</a>."
|
||||
},
|
||||
"SETTINGS$MCP_CONFIG_ERROR": {
|
||||
"en": "Error:",
|
||||
@@ -9263,22 +9247,6 @@
|
||||
"de": "oder siehe",
|
||||
"uk": "або перегляньте"
|
||||
},
|
||||
"COMMON$DOCUMENTATION": {
|
||||
"en": "documentation",
|
||||
"ja": "ドキュメント",
|
||||
"zh-CN": "文档",
|
||||
"zh-TW": "文件",
|
||||
"ko-KR": "문서",
|
||||
"no": "dokumentasjon",
|
||||
"it": "documentazione",
|
||||
"pt": "documentação",
|
||||
"es": "documentación",
|
||||
"ar": "التوثيق",
|
||||
"fr": "documentation",
|
||||
"tr": "belgelendirme",
|
||||
"de": "Dokumentation",
|
||||
"uk": "документація"
|
||||
},
|
||||
"AGENT_ERROR$ERROR_ACTION_NOT_EXECUTED": {
|
||||
"en": "The action has not been executed. This may have occurred because the user pressed the stop button, or because the runtime system crashed and restarted due to resource constraints. Any previously established system state, dependencies, or environment variables may have been lost.",
|
||||
"ja": "アクションは実行されていません。これはユーザーが停止ボタンを押したか、リソース制約によりランタイムシステムがクラッシュして再起動したことが原因かもしれません。以前に確立されたシステム状態、依存関係、または環境変数は失われている可能性があります。",
|
||||
@@ -10911,6 +10879,54 @@
|
||||
"de": "Kein API-Schlüssel verfügbar",
|
||||
"uk": "Немає доступного API-ключа"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$TITLE": {
|
||||
"en": "Microagent Management",
|
||||
"ja": "マイクロエージェント管理",
|
||||
"zh-CN": "微代理管理",
|
||||
"zh-TW": "微代理管理",
|
||||
"ko-KR": "마이크로에이전트 관리",
|
||||
"no": "Microagent-administrasjon",
|
||||
"it": "Gestione Microagent",
|
||||
"pt": "Gerenciamento de Microagente",
|
||||
"es": "Gestión de microagentes",
|
||||
"ar": "إدارة الميكرووكيل",
|
||||
"fr": "Gestion des microagents",
|
||||
"tr": "Mikroajan Yönetimi",
|
||||
"de": "Microagent-Verwaltung",
|
||||
"uk": "Управління мікроагентами"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$DESCRIPTION": {
|
||||
"en": "Manage Microagents",
|
||||
"ja": "マイクロエージェントを管理する",
|
||||
"zh-CN": "管理微代理",
|
||||
"zh-TW": "管理微代理",
|
||||
"ko-KR": "마이크로에이전트 관리",
|
||||
"no": "Administrer mikroagenter",
|
||||
"it": "Gestisci i Microagent",
|
||||
"pt": "Gerenciar Microagentes",
|
||||
"es": "Gestionar microagentes",
|
||||
"ar": "إدارة الميكرووكلاء",
|
||||
"fr": "Gérer les microagents",
|
||||
"tr": "Mikro ajanları yönet",
|
||||
"de": "Microagents verwalten",
|
||||
"uk": "Керування мікроагентами"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$USE_MICROAGENTS": {
|
||||
"en": "Use microagents to customize the behavior of OpenHands, teach it about your repositories, and help it work faster.",
|
||||
"ja": "マイクロエージェントを使ってOpenHandsの動作をカスタマイズし、リポジトリについて教え、より速く作業できるようにしましょう。",
|
||||
"zh-CN": "使用微代理自定义OpenHands的行为,让其了解您的仓库,并帮助其更快地工作。",
|
||||
"zh-TW": "使用微代理自訂 OpenHands 的行為,讓其了解您的倉庫,並幫助其更快地工作。",
|
||||
"ko-KR": "마이크로에이전트를 사용하여 OpenHands의 동작을 사용자 정의하고, 저장소에 대해 학습시키며, 더 빠르게 작업할 수 있도록 도와주세요.",
|
||||
"no": "Bruk mikroagenter for å tilpasse oppførselen til OpenHands, lære den om dine repositories, og hjelpe den å jobbe raskere.",
|
||||
"it": "Usa i microagent per personalizzare il comportamento di OpenHands, insegnargli i tuoi repository e aiutarlo a lavorare più velocemente.",
|
||||
"pt": "Use microagentes para personalizar o comportamento do OpenHands, ensiná-lo sobre seus repositórios e ajudá-lo a trabalhar mais rápido.",
|
||||
"es": "Utiliza microagentes para personalizar el comportamiento de OpenHands, enseñarle sobre tus repositorios y ayudarle a trabajar más rápido.",
|
||||
"ar": "استخدم الوكلاء المصغرين لتخصيص سلوك OpenHands، وتعليمه حول مستودعاتك، ومساعدته على العمل بشكل أسرع.",
|
||||
"fr": "Utilisez des microagents pour personnaliser le comportement d'OpenHands, lui apprendre vos dépôts et l'aider à travailler plus rapidement.",
|
||||
"tr": "OpenHands'in davranışını özelleştirmek, depolarınızı ona öğretmek ve daha hızlı çalışmasına yardımcı olmak için mikro ajanları kullanın.",
|
||||
"de": "Verwenden Sie Microagents, um das Verhalten von OpenHands anzupassen, ihm Ihre Repositories beizubringen und ihm zu helfen, schneller zu arbeiten.",
|
||||
"uk": "Використовуйте мікроагенти, щоб налаштувати поведінку OpenHands, навчити його про ваші репозиторії та допомогти йому працювати швидше."
|
||||
},
|
||||
"AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR": {
|
||||
"en": "By signing up, you agree to our",
|
||||
"ja": "サインアップすることで、あなたは当社の",
|
||||
@@ -10927,6 +10943,22 @@
|
||||
"de": "Mit der Anmeldung stimmen Sie unseren",
|
||||
"uk": "Реєструючись, ви погоджуєтеся з нашими"
|
||||
},
|
||||
"AUTH$NO_PROVIDERS_CONFIGURED": {
|
||||
"en": "At least one identity provider must be configured (e.g., GitHub)",
|
||||
"ja": "少なくとも1つのIDプロバイダーを設定する必要があります(例:GitHub)",
|
||||
"zh-CN": "必须配置至少一个身份提供商(例如 GitHub)",
|
||||
"zh-TW": "必須配置至少一個身份提供商(例如 GitHub)",
|
||||
"ko-KR": "최소 하나의 ID 제공자를 구성해야 합니다 (예: GitHub)",
|
||||
"no": "Minst én identitetsleverandør må konfigureres (f.eks. GitHub)",
|
||||
"it": "Deve essere configurato almeno un provider di identità (ad es. GitHub)",
|
||||
"pt": "Pelo menos um provedor de identidade deve ser configurado (ex: GitHub)",
|
||||
"es": "Debe configurarse al menos un proveedor de identidad (ej: GitHub)",
|
||||
"ar": "يجب تكوين مزود هوية واحد على الأقل (مثل GitHub)",
|
||||
"fr": "Au moins un fournisseur d'identité doit être configuré (ex: GitHub)",
|
||||
"tr": "En az bir kimlik sağlayıcı yapılandırılmalıdır (örn. GitHub)",
|
||||
"de": "Mindestens ein Identitätsanbieter muss konfiguriert werden (z.B. GitHub)",
|
||||
"uk": "Принаймні один постачальник ідентифікації має бути налаштований (наприклад, GitHub)"
|
||||
},
|
||||
"COMMON$TERMS_OF_SERVICE": {
|
||||
"en": "Terms of Service",
|
||||
"ja": "利用規約",
|
||||
@@ -10974,5 +11006,325 @@
|
||||
"tr": "Gizlilik Politikası",
|
||||
"de": "Datenschutzrichtlinie",
|
||||
"uk": "Політика конфіденційності"
|
||||
},
|
||||
"COMMON$PERSONAL": {
|
||||
"en": "Personal",
|
||||
"ja": "個人用",
|
||||
"zh-CN": "个人",
|
||||
"zh-TW": "個人",
|
||||
"ko-KR": "개인",
|
||||
"no": "Personlig",
|
||||
"it": "Personale",
|
||||
"pt": "Pessoal",
|
||||
"es": "Personal",
|
||||
"ar": "شخصي",
|
||||
"fr": "Personnel",
|
||||
"tr": "Kişisel",
|
||||
"de": "Persönlich",
|
||||
"uk": "Особистий"
|
||||
},
|
||||
"COMMON$REPOSITORIES": {
|
||||
"en": "Repositories",
|
||||
"ja": "リポジトリ",
|
||||
"zh-CN": "仓库",
|
||||
"zh-TW": "儲存庫",
|
||||
"ko-KR": "저장소",
|
||||
"no": "Arkiver",
|
||||
"it": "Repository",
|
||||
"pt": "Repositórios",
|
||||
"es": "Repositorios",
|
||||
"ar": "المستودعات",
|
||||
"fr": "Dépôts",
|
||||
"tr": "Depolar",
|
||||
"de": "Repositories",
|
||||
"uk": "Репозиторії"
|
||||
},
|
||||
"COMMON$ORGANIZATIONS": {
|
||||
"en": "Organizations",
|
||||
"ja": "組織",
|
||||
"zh-CN": "组织",
|
||||
"zh-TW": "組織",
|
||||
"ko-KR": "조직",
|
||||
"no": "Organisasjoner",
|
||||
"it": "Organizzazioni",
|
||||
"pt": "Organizações",
|
||||
"es": "Organizaciones",
|
||||
"ar": "المؤسسات",
|
||||
"fr": "Organisations",
|
||||
"tr": "Organizasyonlar",
|
||||
"de": "Organisationen",
|
||||
"uk": "Організації"
|
||||
},
|
||||
"COMMON$ADD_MICROAGENT": {
|
||||
"en": "Add Microagent",
|
||||
"ja": "マイクロエージェントを追加",
|
||||
"zh-CN": "添加微代理",
|
||||
"zh-TW": "新增微代理",
|
||||
"ko-KR": "마이크로에이전트 추가",
|
||||
"no": "Legg til mikroagent",
|
||||
"it": "Aggiungi Microagent",
|
||||
"pt": "Adicionar Microagente",
|
||||
"es": "Agregar microagente",
|
||||
"ar": "إضافة وكيل صغير",
|
||||
"fr": "Ajouter un microagent",
|
||||
"tr": "Mikro ajan ekle",
|
||||
"de": "Microagent hinzufügen",
|
||||
"uk": "Додати мікроагента"
|
||||
},
|
||||
"COMMON$CREATED_ON": {
|
||||
"en": "Created on",
|
||||
"ja": "作成日",
|
||||
"zh-CN": "创建于",
|
||||
"zh-TW": "建立於",
|
||||
"ko-KR": "생성됨",
|
||||
"no": "Opprettet",
|
||||
"it": "Creato",
|
||||
"pt": "Criado",
|
||||
"es": "Creado",
|
||||
"ar": "تم الإنشاء",
|
||||
"fr": "Créé",
|
||||
"tr": "Oluşturuldu",
|
||||
"de": "Erstellt",
|
||||
"uk": "Створено"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO": {
|
||||
"en": "Learn this repo",
|
||||
"ja": "このリポジトリを学習する",
|
||||
"zh-CN": "学习此仓库",
|
||||
"zh-TW": "學習此儲存庫",
|
||||
"ko-KR": "이 저장소 학습",
|
||||
"no": "Lær dette repoet",
|
||||
"it": "Impara questo repository",
|
||||
"pt": "Aprender este repositório",
|
||||
"es": "Aprender este repositorio",
|
||||
"ar": "تعلم هذا المستودع",
|
||||
"fr": "Apprendre ce dépôt",
|
||||
"tr": "Bu depoyu öğren",
|
||||
"de": "Dieses Repository lernen",
|
||||
"uk": "Вивчити цей репозиторій"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT": {
|
||||
"en": "Ready to add a microagent?",
|
||||
"ja": "マイクロエージェントを追加する準備はできましたか?",
|
||||
"zh-CN": "准备好添加微代理了吗?",
|
||||
"zh-TW": "準備好新增微代理了嗎?",
|
||||
"ko-KR": "마이크로에이전트를 추가할 준비가 되셨나요?",
|
||||
"no": "Klar til å legge til en mikroagent?",
|
||||
"it": "Pronto ad aggiungere un microagent?",
|
||||
"pt": "Pronto para adicionar um microagente?",
|
||||
"es": "¿Listo para agregar un microagente?",
|
||||
"ar": "هل أنت مستعد لإضافة وكيل صغير؟",
|
||||
"fr": "Prêt à ajouter un microagent ?",
|
||||
"tr": "Bir mikro ajan eklemeye hazır mısınız?",
|
||||
"de": "Bereit, einen Microagent hinzuzufügen?",
|
||||
"uk": "Готові додати мікроагента?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES": {
|
||||
"en": "OpenHands can automatically learn about your repositories and store its findings as markdown in a microagent. The microagent will help OpenHands run faster and more accurately in any future conversations.",
|
||||
"ja": "OpenHandsはあなたのリポジトリについて自動的に学習し、その結果をマイクロエージェント内のMarkdownとして保存できます。マイクロエージェントは今後の会話でOpenHandsがより速く、より正確に動作するのに役立ちます。",
|
||||
"zh-CN": "OpenHands 可以自动学习您的仓库,并将其发现以 markdown 形式存储在微代理中。微代理将帮助 OpenHands 在未来的对话中运行得更快、更准确。",
|
||||
"zh-TW": "OpenHands 可以自動學習您的儲存庫,並將其發現以 markdown 形式儲存在微代理中。微代理將幫助 OpenHands 在未來的對話中運行得更快、更準確。",
|
||||
"ko-KR": "OpenHands는 사용자의 저장소를 자동으로 학습하고, 그 결과를 마이크로에이전트의 마크다운으로 저장할 수 있습니다. 마이크로에이전트는 앞으로의 대화에서 OpenHands가 더 빠르고 정확하게 동작하도록 도와줍니다.",
|
||||
"no": "OpenHands kan automatisk lære om dine repositorier og lagre funnene som markdown i en mikroagent. Mikroagenten vil hjelpe OpenHands å kjøre raskere og mer nøyaktig i fremtidige samtaler.",
|
||||
"it": "OpenHands può apprendere automaticamente dai tuoi repository e memorizzare i risultati come markdown in un microagent. Il microagent aiuterà OpenHands a funzionare più velocemente e con maggiore precisione in conversazioni future.",
|
||||
"pt": "O OpenHands pode aprender automaticamente sobre seus repositórios e armazenar suas descobertas como markdown em um microagente. O microagente ajudará o OpenHands a funcionar mais rápido e com mais precisão em conversas futuras.",
|
||||
"es": "OpenHands puede aprender automáticamente sobre tus repositorios y almacenar sus hallazgos como markdown en un microagente. El microagente ayudará a que OpenHands funcione más rápido y con mayor precisión en futuras conversaciones.",
|
||||
"ar": "يمكن لـ OpenHands التعرف تلقائيًا على مستودعاتك وتخزين النتائج كـ markdown في وكيل صغير. سيساعد الوكيل الصغير OpenHands على العمل بشكل أسرع وأكثر دقة في أي محادثات مستقبلية.",
|
||||
"fr": "OpenHands peut automatiquement apprendre à connaître vos dépôts et stocker ses découvertes en markdown dans un microagent. Le microagent aidera OpenHands à fonctionner plus rapidement et plus précisément lors de futures conversations.",
|
||||
"tr": "OpenHands, depolarınızı otomatik olarak öğrenebilir ve bulgularını bir mikro ajan içinde markdown olarak saklayabilir. Mikro ajan, OpenHands'in gelecekteki konuşmalarda daha hızlı ve daha doğru çalışmasına yardımcı olur.",
|
||||
"de": "OpenHands kann automatisch Informationen über Ihre Repositories sammeln und die Ergebnisse als Markdown in einem Microagent speichern. Der Microagent hilft OpenHands, in zukünftigen Gesprächen schneller und genauer zu arbeiten.",
|
||||
"uk": "OpenHands може автоматично вивчати ваші репозиторії та зберігати свої висновки у вигляді markdown у мікроагенті. Мікроагент допоможе OpenHands працювати швидше та точніше у майбутніх розмовах."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT_TO": {
|
||||
"en": "Add a Microagent to",
|
||||
"ja": "にマイクロエージェントを追加",
|
||||
"zh-CN": "添加微代理到",
|
||||
"zh-TW": "新增微代理到",
|
||||
"ko-KR": "에 마이크로에이전트 추가",
|
||||
"no": "Legg til en mikroagent i",
|
||||
"it": "Aggiungi un microagent a",
|
||||
"pt": "Adicionar um microagente a",
|
||||
"es": "Agregar un microagente a",
|
||||
"ar": "إضافة وكيل صغير إلى",
|
||||
"fr": "Ajouter un microagent à",
|
||||
"tr": "Bir mikro ajan ekle",
|
||||
"de": "Microagent hinzufügen zu",
|
||||
"uk": "Додати мікроагента до"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_A_MICROAGENT": {
|
||||
"en": "Add a Microagent",
|
||||
"ja": "マイクロエージェントを追加",
|
||||
"zh-CN": "添加微代理",
|
||||
"zh-TW": "新增微代理",
|
||||
"ko-KR": "마이크로에이전트 추가",
|
||||
"no": "Legg til en mikroagent",
|
||||
"it": "Aggiungi un microagent",
|
||||
"pt": "Adicionar um microagente",
|
||||
"es": "Agregar un microagente",
|
||||
"ar": "إضافة وكيل صغير",
|
||||
"fr": "Ajouter un microagent",
|
||||
"tr": "Bir mikro ajan ekle",
|
||||
"de": "Microagent hinzufügen",
|
||||
"uk": "Додати мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_MICROAGENT_MODAL_DESCRIPTION": {
|
||||
"en": "OpenHands will create a new microagent based on your instructions.",
|
||||
"ja": "OpenHandsはあなたの指示に基づいて新しいマイクロエージェントを作成します。",
|
||||
"zh-CN": "OpenHands 将根据您的指示创建一个新的微代理。",
|
||||
"zh-TW": "OpenHands 將根據您的指示建立新的微代理。",
|
||||
"ko-KR": "OpenHands가 귀하의 지시에 따라 새로운 마이크로에이전트를 생성합니다.",
|
||||
"no": "OpenHands vil opprette en ny mikroagent basert på dine instruksjoner.",
|
||||
"it": "OpenHands creerà un nuovo microagent in base alle tue istruzioni.",
|
||||
"pt": "O OpenHands criará um novo microagente com base nas suas instruções.",
|
||||
"es": "OpenHands creará un nuevo microagente según tus instrucciones.",
|
||||
"ar": "سيقوم OpenHands بإنشاء وكيل صغير جديد بناءً على تعليماتك.",
|
||||
"fr": "OpenHands créera un nouveau microagent selon vos instructions.",
|
||||
"tr": "OpenHands, talimatlarınıza göre yeni bir mikro ajan oluşturacaktır.",
|
||||
"de": "OpenHands erstellt einen neuen Microagenten basierend auf Ihren Anweisungen.",
|
||||
"uk": "OpenHands створить нового мікроагента відповідно до ваших інструкцій."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$WHAT_TO_DO": {
|
||||
"en": "What would you like the Microagent to do?",
|
||||
"ja": "マイクロエージェントに何をさせたいですか?",
|
||||
"zh-CN": "您希望微代理做什么?",
|
||||
"zh-TW": "您希望微代理做什麼?",
|
||||
"ko-KR": "마이크로에이전트가 무엇을 하길 원하시나요?",
|
||||
"no": "Hva vil du at mikroagenten skal gjøre?",
|
||||
"it": "Cosa vuoi che faccia il microagent?",
|
||||
"pt": "O que você gostaria que o microagente fizesse?",
|
||||
"es": "¿Qué te gustaría que hiciera el microagente?",
|
||||
"ar": "ماذا تريد أن يفعل الوكيل الصغير؟",
|
||||
"fr": "Que souhaitez-vous que le microagent fasse ?",
|
||||
"tr": "Mikro ajanın ne yapmasını istersiniz?",
|
||||
"de": "Was soll der Microagent tun?",
|
||||
"uk": "Що ви хочете, щоб зробив мікроагент?"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$DESCRIBE_WHAT_TO_DO": {
|
||||
"en": "Describe what you would like the Microagent to do.",
|
||||
"ja": "マイクロエージェントにしてほしいことを説明してください。",
|
||||
"zh-CN": "请描述您希望微代理执行的操作。",
|
||||
"zh-TW": "請描述您希望微代理執行的操作。",
|
||||
"ko-KR": "마이크로에이전트가 수행하길 원하는 작업을 설명하세요.",
|
||||
"no": "Beskriv hva du vil at mikroagenten skal gjøre.",
|
||||
"it": "Descrivi cosa vorresti che facesse il microagent.",
|
||||
"pt": "Descreva o que você gostaria que o microagente fizesse.",
|
||||
"es": "Describe lo que te gustaría que hiciera el microagente.",
|
||||
"ar": "صف ما ترغب أن يفعله الوكيل الصغير.",
|
||||
"fr": "Décrivez ce que vous souhaitez que le microagent fasse.",
|
||||
"tr": "Mikro ajanın ne yapmasını istediğinizi açıklayın.",
|
||||
"de": "Beschreiben Sie, was der Microagent tun soll.",
|
||||
"uk": "Опишіть, що ви хочете, щоб зробив мікроагент."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ADD_TRIGGERS": {
|
||||
"en": "Define triggers for the Microagent",
|
||||
"ja": "マイクロエージェントのトリガーを定義する",
|
||||
"zh-CN": "为微代理定义触发器",
|
||||
"zh-TW": "為微代理定義觸發條件",
|
||||
"ko-KR": "마이크로에이전트의 트리거 정의",
|
||||
"no": "Definer utløsere for mikroagenten",
|
||||
"it": "Definisci i trigger per il microagent",
|
||||
"pt": "Defina gatilhos para o microagente",
|
||||
"es": "Define desencadenantes para el microagente",
|
||||
"ar": "حدد المشغلات للوكيل الصغير",
|
||||
"fr": "Définir des déclencheurs pour le microagent",
|
||||
"tr": "Mikro ajan için tetikleyiciler tanımlayın",
|
||||
"de": "Definieren Sie Auslöser für den Microagenten",
|
||||
"uk": "Визначте тригери для мікроагента"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$HELP_TEXT_DESCRIBING_VALID_TRIGGERS": {
|
||||
"en": "Help text describing valid triggers.",
|
||||
"ja": "有効なトリガーについて説明するヘルプテキスト。",
|
||||
"zh-CN": "描述有效触发器的帮助文本。",
|
||||
"zh-TW": "描述有效觸發條件的說明文字。",
|
||||
"ko-KR": "유효한 트리거를 설명하는 도움말 텍스트입니다.",
|
||||
"no": "Hjelpetekst som beskriver gyldige utløsere.",
|
||||
"it": "Testo di aiuto che descrive i trigger validi.",
|
||||
"pt": "Texto de ajuda descrevendo gatilhos válidos.",
|
||||
"es": "Texto de ayuda que describe desencadenantes válidos.",
|
||||
"ar": "نص المساعدة الذي يصف المشغلات الصالحة.",
|
||||
"fr": "Texte d'aide décrivant les déclencheurs valides.",
|
||||
"tr": "Geçerli tetikleyicileri açıklayan yardım metni.",
|
||||
"de": "Hilfetext, der gültige Auslöser beschreibt.",
|
||||
"uk": "Текст довідки, що описує дійсні тригери."
|
||||
},
|
||||
"COMMON$FOR_EXAMPLE": {
|
||||
"en": "For example",
|
||||
"ja": "例えば",
|
||||
"zh-CN": "例如",
|
||||
"zh-TW": "例如",
|
||||
"ko-KR": "예를 들어",
|
||||
"no": "For eksempel",
|
||||
"it": "Ad esempio",
|
||||
"pt": "Por exemplo",
|
||||
"es": "Por ejemplo",
|
||||
"ar": "على سبيل المثال",
|
||||
"fr": "Par exemple",
|
||||
"tr": "Örneğin",
|
||||
"de": "Zum Beispiel",
|
||||
"uk": "Наприклад:"
|
||||
},
|
||||
"COMMON$TEST_DB_MIGRATION": {
|
||||
"en": "Test DB Migration",
|
||||
"ja": "DBマイグレーションのテスト",
|
||||
"zh-CN": "测试数据库迁移",
|
||||
"zh-TW": "測試資料庫遷移",
|
||||
"ko-KR": "DB 마이그레이션 테스트",
|
||||
"no": "Test DB-migrering",
|
||||
"it": "Test migrazione DB",
|
||||
"pt": "Testar migração do banco de dados",
|
||||
"es": "Probar migración de base de datos",
|
||||
"ar": "اختبار ترحيل قاعدة البيانات",
|
||||
"fr": "Tester la migration de la base de données",
|
||||
"tr": "Veritabanı geçişini test et",
|
||||
"de": "Datenbankmigration testen",
|
||||
"uk": "Тестування міграції БД"
|
||||
},
|
||||
"COMMON$RUN_TEST": {
|
||||
"en": "Run Test",
|
||||
"ja": "テストを実行",
|
||||
"zh-CN": "运行测试",
|
||||
"zh-TW": "執行測試",
|
||||
"ko-KR": "테스트 실행",
|
||||
"no": "Kjør test",
|
||||
"it": "Esegui test",
|
||||
"pt": "Executar teste",
|
||||
"es": "Ejecutar prueba",
|
||||
"ar": "تشغيل الاختبار",
|
||||
"fr": "Exécuter le test",
|
||||
"tr": "Testi çalıştır",
|
||||
"de": "Test ausführen",
|
||||
"uk": "Запустити тест"
|
||||
},
|
||||
"COMMON$RUN_APP": {
|
||||
"en": "Run App",
|
||||
"ja": "アプリを実行",
|
||||
"zh-CN": "运行应用",
|
||||
"zh-TW": "執行應用程式",
|
||||
"ko-KR": "앱 실행",
|
||||
"no": "Kjør app",
|
||||
"it": "Esegui app",
|
||||
"pt": "Executar aplicativo",
|
||||
"es": "Ejecutar aplicación",
|
||||
"ar": "تشغيل التطبيق",
|
||||
"fr": "Exécuter l'application",
|
||||
"tr": "Uygulamayı çalıştır",
|
||||
"de": "App ausführen",
|
||||
"uk": "Запустити додаток"
|
||||
},
|
||||
"COMMON$LEARN_FILE_STRUCTURE": {
|
||||
"en": "Learn File Structure",
|
||||
"ja": "ファイル構造を学ぶ",
|
||||
"zh-CN": "学习文件结构",
|
||||
"zh-TW": "學習檔案結構",
|
||||
"ko-KR": "파일 구조 학습",
|
||||
"no": "Lær filstruktur",
|
||||
"it": "Impara la struttura dei file",
|
||||
"pt": "Aprender estrutura de arquivos",
|
||||
"es": "Aprender la estructura de archivos",
|
||||
"ar": "تعلم بنية الملفات",
|
||||
"fr": "Apprendre la structure des fichiers",
|
||||
"tr": "Dosya yapısını öğren",
|
||||
"de": "Dateistruktur lernen",
|
||||
"uk": "Вивчити структуру файлів"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z" fill="white"/>
|
||||
<path d="M5.25511 5.78615C5.24752 5.92237 5.3599 6.03271 5.49634 6.03271H6.32082C6.45889 6.03271 6.56868 5.92013 6.58723 5.78331C6.67618 5.12718 7.1265 4.64893 7.92922 4.64893C8.61477 4.64893 9.24318 4.9917 9.24318 5.81689C9.24318 6.45166 8.86867 6.74365 8.27834 7.18799C7.60549 7.67676 7.07229 8.24805 7.11037 9.1748L7.11334 9.39161C7.11521 9.52833 7.22658 9.63818 7.36332 9.63818H8.17434C8.31241 9.63818 8.42434 9.52625 8.42434 9.38818V9.28271C8.42434 8.56543 8.69729 8.35596 9.43361 7.79736C10.043 7.33398 10.6778 6.81982 10.6778 5.74072C10.6778 4.22998 9.40188 3.5 8.00539 3.5C6.73831 3.5 5.34964 4.09061 5.25511 5.78615ZM6.81203 11.5488C6.81203 12.082 7.23732 12.4756 7.82131 12.4756C8.43068 12.4756 8.84963 12.082 8.84963 11.5488C8.84963 10.9966 8.43068 10.6094 7.82131 10.6094C7.23732 10.6094 6.81203 10.9966 6.81203 11.5488Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="21" viewBox="0 0 24 21" fill="none">
|
||||
<path d="M3 1.5C2.17157 1.5 1.5 2.17157 1.5 3V15C1.5 15.8284 2.17157 16.5 3 16.5H11.25C11.6642 16.5 12 16.8358 12 17.25C12 17.6642 11.6642 18 11.25 18H3C1.34315 18 0 16.6569 0 15V3C0 1.34315 1.34315 0 3 0H19.5C21.1569 0 22.5 1.34315 22.5 3V9C22.5 9.41421 22.1642 9.75 21.75 9.75C21.3358 9.75 21 9.41421 21 9V3C21 2.17157 20.3284 1.5 19.5 1.5H3Z" fill="currentColor"/>
|
||||
<path d="M4.71967 4.71967C5.01256 4.42678 5.48744 4.42678 5.78033 4.71967L7.76517 6.7045C8.2045 7.14384 8.2045 7.85616 7.76517 8.2955L5.78033 10.2803C5.48744 10.5732 5.01256 10.5732 4.71967 10.2803C4.42678 9.98744 4.42678 9.51256 4.71967 9.21967L6.43934 7.5L4.71967 5.78033C4.42678 5.48744 4.42678 5.01256 4.71967 4.71967Z" fill="currentColor"/>
|
||||
<path d="M8.25 10.5C8.25 10.0858 8.58579 9.75 9 9.75H12C12.4142 9.75 12.75 10.0858 12.75 10.5C12.75 10.9142 12.4142 11.25 12 11.25H9C8.58579 11.25 8.25 10.9142 8.25 10.5Z" fill="currentColor"/>
|
||||
<path d="M24 15.75C24 18.6495 21.6495 21 18.75 21C15.8505 21 13.5 18.6495 13.5 15.75C13.5 12.8505 15.8505 10.5 18.75 10.5C21.6495 10.5 24 12.8505 24 15.75ZM18.75 12.75C18.3358 12.75 18 13.0858 18 13.5V15H16.5C16.0858 15 15.75 15.3358 15.75 15.75C15.75 16.1642 16.0858 16.5 16.5 16.5H18V18C18 18.4142 18.3358 18.75 18.75 18.75C19.1642 18.75 19.5 18.4142 19.5 18V16.5H21C21.4142 16.5 21.75 16.1642 21.75 15.75C21.75 15.3358 21.4142 15 21 15H19.5V13.5C19.5 13.0858 19.1642 12.75 18.75 12.75Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 12.8683L5.53801 19.5225L4.46191 18.4775L10.9545 11.7917L4.86647 5.52251L5.94257 4.47751L12 10.7152L18.0574 4.47751L19.1334 5.52251L13.0454 11.7917L19.538 18.4775L18.4619 19.5225L12 12.8683Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
@@ -27,5 +27,6 @@ export default [
|
||||
route("terminal", "routes/terminal-tab.tsx"),
|
||||
route("vscode", "routes/vscode-tab.tsx"),
|
||||
]),
|
||||
route("microagent-management", "routes/microagent-management.tsx"),
|
||||
]),
|
||||
] satisfies RouteConfig;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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";
|
||||
|
||||
export const clientLoader = async ({ request }: Route.ClientLoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
const { pathname } = url;
|
||||
|
||||
let config = queryClient.getQueryData<GetConfigResponse>(["config"]);
|
||||
if (!config) {
|
||||
config = await OpenHands.getConfig();
|
||||
queryClient.setQueryData<GetConfigResponse>(["config"], config);
|
||||
}
|
||||
|
||||
const shouldHideMicroagentManagement =
|
||||
config?.FEATURE_FLAGS.HIDE_MICROAGENT_MANAGEMENT;
|
||||
|
||||
if (shouldHideMicroagentManagement && pathname === "/microagent-management") {
|
||||
return redirect("/");
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default MicroagentManagement;
|
||||
@@ -214,6 +214,7 @@ export default function MainApp() {
|
||||
<AuthModal
|
||||
githubAuthUrl={effectiveGitHubAuthUrl}
|
||||
appMode={config.data?.APP_MODE}
|
||||
providersConfigured={config.data?.PROVIDERS_CONFIGURED}
|
||||
/>
|
||||
)}
|
||||
{renderReAuthModal && <ReauthModal />}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export const microagentManagementSlice = createSlice({
|
||||
name: "microagentManagement",
|
||||
initialState: {
|
||||
selectedMicroagent: null,
|
||||
addMicroagentModalVisible: false,
|
||||
selectedRepository: null,
|
||||
},
|
||||
reducers: {
|
||||
setSelectedMicroagent: (state, action) => {
|
||||
state.selectedMicroagent = action.payload;
|
||||
},
|
||||
setAddMicroagentModalVisible: (state, action) => {
|
||||
state.addMicroagentModalVisible = action.payload;
|
||||
},
|
||||
setSelectedRepository: (state, action) => {
|
||||
state.selectedRepository = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setSelectedMicroagent,
|
||||
setAddMicroagentModalVisible,
|
||||
setSelectedRepository,
|
||||
} = microagentManagementSlice.actions;
|
||||
|
||||
export default microagentManagementSlice.reducer;
|
||||
@@ -9,6 +9,7 @@ import { jupyterReducer } from "./state/jupyter-slice";
|
||||
import securityAnalyzerReducer from "./state/security-analyzer-slice";
|
||||
import statusReducer from "./state/status-slice";
|
||||
import metricsReducer from "./state/metrics-slice";
|
||||
import microagentManagementReducer from "./state/microagent-management-slice";
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
fileState: fileStateReducer,
|
||||
@@ -21,6 +22,7 @@ export const rootReducer = combineReducers({
|
||||
securityAnalyzer: securityAnalyzerReducer,
|
||||
status: statusReducer,
|
||||
metrics: metricsReducer,
|
||||
microagentManagement: microagentManagementReducer,
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
|
||||
@@ -8,6 +8,7 @@ export const VERIFIED_PROVIDERS = [
|
||||
export const VERIFIED_MODELS = [
|
||||
"o3-mini-2025-01-31",
|
||||
"o3-2025-04-16",
|
||||
"o3",
|
||||
"o4-mini-2025-04-16",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
@@ -61,6 +62,7 @@ export const VERIFIED_OPENHANDS_MODELS = [
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-opus-4-20250514",
|
||||
"gemini-2.5-pro",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"devstral-small-2507",
|
||||
"devstral-medium-2507",
|
||||
|
||||
+123
-41
@@ -8,6 +8,8 @@
|
||||
"clsx": "^2.1.1",
|
||||
"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",
|
||||
@@ -79,12 +81,34 @@
|
||||
|
||||
"@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.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg=="],
|
||||
"@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=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
|
||||
|
||||
"@chromatic-com/storybook": ["@chromatic-com/storybook@4.0.1", "", { "dependencies": { "@neoconfetti/react": "^1.0.0", "chromatic": "^12.0.0", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" }, "peerDependencies": { "storybook": "^0.0.0-0 || ^9.0.0 || ^9.1.0-0" } }, "sha512-GQXe5lyZl3yLewLJQyFXEpOp2h+mfN2bPrzYaOFNCJjO4Js9deKbRHTOSaiP2FRwZqDLdQwy2+SEGeXPZ94yYw=="],
|
||||
|
||||
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="],
|
||||
|
||||
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="],
|
||||
|
||||
"@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="],
|
||||
|
||||
"@emotion/memoize": ["@emotion/memoize@0.9.0", "", {}, "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="],
|
||||
|
||||
"@emotion/react": ["@emotion/react@11.14.0", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.14.0", "@emotion/serialize": "^1.3.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA=="],
|
||||
|
||||
"@emotion/serialize": ["@emotion/serialize@1.3.3", "", { "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA=="],
|
||||
|
||||
"@emotion/sheet": ["@emotion/sheet@1.4.0", "", {}, "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="],
|
||||
|
||||
"@emotion/unitless": ["@emotion/unitless@0.10.0", "", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="],
|
||||
|
||||
"@emotion/use-insertion-effect-with-fallbacks": ["@emotion/use-insertion-effect-with-fallbacks@1.2.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg=="],
|
||||
|
||||
"@emotion/utils": ["@emotion/utils@1.4.2", "", {}, "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="],
|
||||
|
||||
"@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/android-arm": ["@esbuild/android-arm@0.25.6", "", { "os": "android", "cpu": "arm" }, "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg=="],
|
||||
@@ -183,45 +207,45 @@
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.2", "", { "os": "android", "cpu": "arm" }, "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.2", "", { "os": "android", "cpu": "arm64" }, "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.44.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.44.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.44.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.44.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.44.2", "", { "os": "linux", "cpu": "arm" }, "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.44.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.44.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.44.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g=="],
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="],
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.44.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw=="],
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.44.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.44.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.44.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.44.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.44.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.44.2", "", { "os": "win32", "cpu": "x64" }, "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA=="],
|
||||
"@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=="],
|
||||
|
||||
@@ -231,27 +255,27 @@
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@9.0.16", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^9.0.16" } }, "sha512-pi9ipxhs9bA2yCHDGp2+yWy6E2LywDFTqWcFh3aw/LRxnlRTf52QiVJkWpJbNFEXgk4QrKVrAruf9LLiXpTcOA=="],
|
||||
"@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-docs": ["@storybook/addon-docs@9.0.16", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "9.0.16", "@storybook/icons": "^1.2.12", "@storybook/react-dom-shim": "9.0.16", "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.16" } }, "sha512-/ZXaxMC/JqL0cnVuyPHXdJhNvgCrKvxcnM3ACdgBLsEIGcIqegPF+Ahkb2f9sjU36sR7ihT81cL/7cUvQwzd4Q=="],
|
||||
"@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-onboarding": ["@storybook/addon-onboarding@9.0.16", "", { "peerDependencies": { "storybook": "^9.0.16" } }, "sha512-69BPJ9fGNGpDAcGvNJ58V5uQOmpHkQMLcgp/ON3NepoCHiSReUzojB6wV8Ag13PUZmvWXVnE14SWKBZp93xTFQ=="],
|
||||
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@9.0.17", "", { "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-WoZZ8d58gP6uBu6OJ2K1GjBSM4+Kcr0I9lo0z3convzYqxrhfUm9pNEwVm57KCbVVyBbIKmevddCsSFoPC5u6Q=="],
|
||||
|
||||
"@storybook/addon-vitest": ["@storybook/addon-vitest@9.0.16", "", { "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.16", "vitest": "^3.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/runner", "vitest"] }, "sha512-amIJLeAcREF/imEmAhZmsPc3kvtEDVdk7O6uvh8/ql8UYNN5Tnc+ud6CsfIZO82ru+PupoYKrt6SC8EwpQ8YMQ=="],
|
||||
"@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/builder-vite": ["@storybook/builder-vite@9.0.16", "", { "dependencies": { "@storybook/csf-plugin": "9.0.16", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^9.0.16", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-zXockUexeRy3ABG7DFLEvJqJe4mGL0JkI7FMrpwiKaHCQNaD87vR0xkRVeh0a3B8GKUNxoYtpYKvdzc9DobQHQ=="],
|
||||
"@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/csf-plugin": ["@storybook/csf-plugin@9.0.16", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.16" } }, "sha512-MSmfPwI0j1mMAc+R3DVkVBQf2KLzaVn2SLdEwweesx63Nh9j3zu9CqKEa0zOuDX1lR2M0DZU0lV6K4sc2EYI4A=="],
|
||||
"@storybook/csf-plugin": ["@storybook/csf-plugin@9.0.17", "", { "dependencies": { "unplugin": "^1.3.1" }, "peerDependencies": { "storybook": "^9.0.17" } }, "sha512-6Q4eo1ObrLlsnB6bIt6T8+45XAb4to2pQGNrI7QPkLQRLrZinrJcNbLY7AGkyIoCOEsEbq08n09/nClQUbu8HA=="],
|
||||
|
||||
"@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.16", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "9.0.16" }, "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.16", "typescript": ">= 4.9.x" }, "optionalPeers": ["typescript"] }, "sha512-1jk9fBe8vEoZrba9cK19ZDdZgYMXUNl3Egjj5RsTMYMc1L2mtIu9o56VyK/1V4Q52N9IyawHvmIIuxc5pCZHkQ=="],
|
||||
"@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-dom-shim": ["@storybook/react-dom-shim@9.0.16", "", { "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.16" } }, "sha512-5aIK+31R41mRUvDB4vmBv8hwh3IVHIk/Zbs6kkWF2a+swOsB2+a06aLX21lma4/0T/AuFVXHWat0+inQ4nrXRg=="],
|
||||
"@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-vite": ["@storybook/react-vite@9.0.16", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "0.6.1", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "9.0.16", "@storybook/react": "9.0.16", "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.16", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-a+UsoymyvPH4bJJVI+asj02N8U2wlkGyzhUqF6LUM9gXzixRMxoRHkchCKLdqLhE+//STrwC0YFF3GG6Y5oMEg=="],
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -313,7 +337,9 @@
|
||||
|
||||
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.12", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g=="],
|
||||
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
|
||||
"@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
|
||||
|
||||
@@ -321,6 +347,8 @@
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
|
||||
|
||||
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -343,11 +371,11 @@
|
||||
|
||||
"@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.18", "", { "dependencies": { "@volar/source-map": "2.4.18" } }, "sha512-G3yYV85ekH4TV0EDS6DsS/dUJWrz675H9UgsxFz5pQbmas51a0Q2fF6Lb2q4RKgytuLZ4E0MBdT5PlVsJXNalw=="],
|
||||
"@volar/language-core": ["@volar/language-core@2.4.19", "", { "dependencies": { "@volar/source-map": "2.4.19" } }, "sha512-i0aLpNA8DYZ2uG05t5K47nUWe+zvvrN9tfz16zS5pCJV9td8F0u+rVAOVSQ1ypufDLUD+ej9BH2/lmug4+lawQ=="],
|
||||
|
||||
"@volar/source-map": ["@volar/source-map@2.4.18", "", {}, "sha512-zaj2V/zo/CHQ/xA75h60jBPgrz+Ou9s6aPl7dX0rT46/uill9aB/ZaDk92ROpJsa/9e2xftCeNAU9ZwVyB/egQ=="],
|
||||
"@volar/source-map": ["@volar/source-map@2.4.19", "", {}, "sha512-ttWmO/Ld7r3ebIPPAYvAuSLrlJ96ZALPka44mD4sWA8bw2n9u7TGnMcaTUkiF0GLG8bq/K09beWmEAB1mqMy/A=="],
|
||||
|
||||
"@volar/typescript": ["@volar/typescript@2.4.18", "", { "dependencies": { "@volar/language-core": "2.4.18", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-xcbsMG8m/yhvO1VIKnTtc+llZxw3YtWkZiV7/F1qNpTORdPExkZRcBxJ5d19MXLpkeiQ+DG5JURHh1SV0bcWRA=="],
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
@@ -385,6 +413,8 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],
|
||||
@@ -397,6 +427,8 @@
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="],
|
||||
|
||||
"chai": ["chai@5.2.1", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A=="],
|
||||
@@ -423,6 +455,8 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
|
||||
@@ -445,9 +479,11 @@
|
||||
|
||||
"dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.181", "", {}, "sha512-+ISMj8OIQ+0qEeDj14Rt8WwcTOiqHyAB+5bnK1K7xNNLjBJ4hRCQfUkw8RWtcLbfBzDwc15ZnKH0c7SNOfwiyA=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.185", "", {}, "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
@@ -455,6 +491,8 @@
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
|
||||
|
||||
"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=="],
|
||||
@@ -463,6 +501,8 @@
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
@@ -479,6 +519,8 @@
|
||||
|
||||
"filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="],
|
||||
|
||||
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
|
||||
|
||||
"find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
|
||||
|
||||
"focus-trap": ["focus-trap@7.6.5", "", { "dependencies": { "tabbable": "^6.2.0" } }, "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg=="],
|
||||
@@ -505,12 +547,18 @@
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
|
||||
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="],
|
||||
|
||||
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
@@ -539,6 +587,8 @@
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
@@ -571,6 +621,8 @@
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="],
|
||||
|
||||
"locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="],
|
||||
@@ -591,6 +643,8 @@
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||
|
||||
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
|
||||
|
||||
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
@@ -625,6 +679,10 @@
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
@@ -635,19 +693,21 @@
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.2.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ=="],
|
||||
|
||||
"playwright": ["playwright@1.54.0", "", { "dependencies": { "playwright-core": "1.54.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-y9yzHmXRwEUOpghM7XGcA38GjWuTOUMaTIcm/5rHcYVjh5MSp9qQMRRMc/+p1cx+csoPnX4wkxAF61v5VKirxg=="],
|
||||
"playwright": ["playwright@1.54.1", "", { "dependencies": { "playwright-core": "1.54.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g=="],
|
||||
|
||||
"playwright-core": ["playwright-core@1.54.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-uiWpWaJh3R3etpJ0QrpligEMl62Dk1iSAB6NUXylvmQz+e3eipXHDHvOvydDAssb5Oqo0E818qdn0L9GcJSTyA=="],
|
||||
"playwright-core": ["playwright-core@1.54.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -677,6 +737,10 @@
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
"react-select": ["react-select@5.10.2", "", { "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.8.1", "@floating-ui/dom": "^1.0.1", "@types/react-transition-group": "^4.4.0", "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", "use-isomorphic-layout-effect": "^1.2.0" }, "peerDependencies": { "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" } }, "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ=="],
|
||||
|
||||
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
|
||||
|
||||
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||
|
||||
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
|
||||
@@ -685,7 +749,9 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"rollup": ["rollup@4.44.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.2", "@rollup/rollup-android-arm64": "4.44.2", "@rollup/rollup-darwin-arm64": "4.44.2", "@rollup/rollup-darwin-x64": "4.44.2", "@rollup/rollup-freebsd-arm64": "4.44.2", "@rollup/rollup-freebsd-x64": "4.44.2", "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", "@rollup/rollup-linux-arm-musleabihf": "4.44.2", "@rollup/rollup-linux-arm64-gnu": "4.44.2", "@rollup/rollup-linux-arm64-musl": "4.44.2", "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-musl": "4.44.2", "@rollup/rollup-linux-s390x-gnu": "4.44.2", "@rollup/rollup-linux-x64-gnu": "4.44.2", "@rollup/rollup-linux-x64-musl": "4.44.2", "@rollup/rollup-win32-arm64-msvc": "4.44.2", "@rollup/rollup-win32-ia32-msvc": "4.44.2", "@rollup/rollup-win32-x64-msvc": "4.44.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg=="],
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
@@ -703,6 +769,8 @@
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"sonner": ["sonner@2.0.6", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
@@ -713,7 +781,7 @@
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"storybook": ["storybook@9.0.16", "", { "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-DzjzeggdzlXKKBK1L9iqNKqqNpyfeaL1hxxeAOmqgeMezwy5d5mCJmjNcZEmx+prsRmvj1OWm4ZZAg6iP/wABg=="],
|
||||
"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=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
@@ -733,6 +801,8 @@
|
||||
|
||||
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
|
||||
|
||||
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
@@ -789,6 +859,8 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"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-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=="],
|
||||
@@ -813,6 +885,8 @@
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
|
||||
|
||||
"@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
@@ -821,6 +895,10 @@
|
||||
|
||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
|
||||
"@emotion/babel-plugin/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"@microsoft/api-extractor/minimatch": ["minimatch@3.0.8", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q=="],
|
||||
|
||||
"@microsoft/api-extractor/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="],
|
||||
@@ -839,12 +917,14 @@
|
||||
|
||||
"@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/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="],
|
||||
"@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=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -893,6 +973,8 @@
|
||||
|
||||
"@rushstack/node-core-library/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Select, type SelectProps } from "./Select";
|
||||
import type { IOption } from "../../shared/types";
|
||||
import { useState } from "react";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Select",
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const options = [
|
||||
{ label: "Red", value: "red" },
|
||||
{ label: "Green", value: "green" },
|
||||
{ label: "Blue", value: "blue" },
|
||||
{ label: "Yellow", value: "yellow" },
|
||||
{ label: "Purple", value: "purple" },
|
||||
{ label: "Orange", value: "orange" },
|
||||
{ label: "Black", value: "black" },
|
||||
{ label: "White", value: "white" },
|
||||
{ label: "Gray", value: "gray" },
|
||||
{ label: "Pink", value: "pink" },
|
||||
];
|
||||
|
||||
const SelectComponent = (props: Partial<SelectProps<unknown>>) => {
|
||||
const [value, setValue] = useState<IOption<string> | null>(
|
||||
(props.value as IOption<string>) ?? null
|
||||
);
|
||||
return (
|
||||
<div className="min-w-104">
|
||||
<Select
|
||||
{...props}
|
||||
label="Select label"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
placeholder="Select an option"
|
||||
options={options}
|
||||
className="h-64 max-w-128 p-4"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Main: Story = {
|
||||
args: {},
|
||||
render: ({}) => <SelectComponent />,
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
args: {},
|
||||
render: ({}) => <SelectComponent error="This field is required" />,
|
||||
};
|
||||
export const Hint: Story = {
|
||||
args: {},
|
||||
render: ({}) => <SelectComponent hint="This is hint" />,
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {},
|
||||
render: ({}) => <SelectComponent disabled value={options[1]} />,
|
||||
};
|
||||
|
||||
export const ReadOnly: Story = {
|
||||
args: {},
|
||||
render: ({}) => <SelectComponent readOnly value={options[0]} />,
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import type { HTMLProps, IOption } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import ReactSelect, { createFilter } from "react-select";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { DropdownIndicator } from "./components/DropdownIndicator";
|
||||
import { Placeholder } from "./components/Placeholder";
|
||||
import { SingleValue } from "./components/SingleValue";
|
||||
import { Option } from "./components/Option";
|
||||
|
||||
export type SelectProps<T> = Omit<HTMLProps<"input">, "value" | "onChange"> & {
|
||||
error?: string;
|
||||
hint?: string;
|
||||
label: string;
|
||||
value?: IOption<T> | null;
|
||||
options: IOption<T>[];
|
||||
noOptionsText?: string;
|
||||
onChange(value: IOption<T> | null): void;
|
||||
};
|
||||
|
||||
export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||
const {
|
||||
error,
|
||||
hint,
|
||||
options,
|
||||
value,
|
||||
id: propId,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
disabled,
|
||||
label,
|
||||
onChange,
|
||||
readOnly,
|
||||
noOptionsText,
|
||||
} = props;
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const generatedId = useId();
|
||||
const id = propId ?? generatedId;
|
||||
|
||||
const filterOption = useMemo(
|
||||
() =>
|
||||
createFilter({
|
||||
ignoreCase: true,
|
||||
ignoreAccents: true,
|
||||
trim: true,
|
||||
matchFrom: "any",
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
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>
|
||||
<ReactSelect
|
||||
inputValue={inputValue}
|
||||
value={value}
|
||||
tabSelectsValue={false}
|
||||
unstyled
|
||||
isSearchable
|
||||
backspaceRemovesValue
|
||||
autoFocus={autoFocus}
|
||||
noOptionsMessage={() => (
|
||||
<Typography.Text className="py-3 block">
|
||||
{noOptionsText ?? "No options"}
|
||||
</Typography.Text>
|
||||
)}
|
||||
isDisabled={disabled ?? readOnly}
|
||||
filterOption={filterOption}
|
||||
customProps={{ error, readOnly, hint }}
|
||||
onInputChange={(value, action) => {
|
||||
if (action.action === "input-change") {
|
||||
setInputValue(value);
|
||||
const val = props.value?.label;
|
||||
if (val != null && val !== value) {
|
||||
props.onChange(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={(option) => {
|
||||
onChange(option as IOption<T>);
|
||||
setInputValue("");
|
||||
}}
|
||||
placeholder={placeholder ?? ""}
|
||||
options={options}
|
||||
menuPortalTarget={document.body}
|
||||
classNames={{
|
||||
indicatorSeparator: (state) =>
|
||||
cn(
|
||||
"bg-light-neutral-500",
|
||||
state.selectProps.customProps.error && "bg-red-400",
|
||||
state.selectProps.customProps.readOnly && "bg-light-neutral-985"
|
||||
),
|
||||
input: () => cn("text-white"),
|
||||
valueContainer: () => cn("py-4.25 px-4"),
|
||||
control: (state) =>
|
||||
cn(
|
||||
"border-light-neutral-500 border-1 bg-light-neutral-950",
|
||||
state.menuIsOpen ? "rounded-t-2xl" : "rounded-2xl",
|
||||
"hover:bg-light-neutral-900",
|
||||
"focus-within:bg-light-neutral-900",
|
||||
state.selectProps.customProps.error && "border-red-400",
|
||||
state.selectProps.customProps.readOnly &&
|
||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto"
|
||||
),
|
||||
menu: () =>
|
||||
cn(
|
||||
"border-light-neutral-500 border-1 border-t-0 rounded-b-2xl bg-light-neutral-950"
|
||||
),
|
||||
}}
|
||||
components={{
|
||||
DropdownIndicator,
|
||||
Placeholder,
|
||||
// @ts-ignore
|
||||
Option: Option,
|
||||
SingleValue,
|
||||
}}
|
||||
/>
|
||||
<Typography.Text
|
||||
fontSize="xs"
|
||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||
>
|
||||
{error ?? hint}
|
||||
</Typography.Text>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { type DropdownIndicatorProps } from "react-select";
|
||||
import { Icon } from "../../icon/Icon";
|
||||
|
||||
export const DropdownIndicator = ({
|
||||
selectProps,
|
||||
className,
|
||||
}: DropdownIndicatorProps) => (
|
||||
<div className={cn("flex flex-row items-center", "py-3 px-3", className)}>
|
||||
<Icon
|
||||
className={cn(
|
||||
"h-5 w-5 text-light-neutral-300",
|
||||
selectProps.customProps.error && "text-red-400"
|
||||
)}
|
||||
icon="ChevronDown"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,56 @@
|
||||
import { type OptionProps } from "react-select";
|
||||
import type { IOption } from "../../../shared/types";
|
||||
import { Typography } from "../../typography/Typography";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
|
||||
export const Option = ({
|
||||
className,
|
||||
children,
|
||||
isSelected,
|
||||
innerProps,
|
||||
data,
|
||||
selectProps: { options, inputValue, filterOption },
|
||||
}: OptionProps & { value: unknown }) => {
|
||||
// In the current design, last option (which may be really last option or last visible option if the search term is present)
|
||||
// has to be rounded. Therefore, that calculation is applied here by using filterOption function from react-select.
|
||||
// Unfortunately, this function is meant to be applied only internally which means API of this function is not really
|
||||
// user-friendly (look at the __isNew__ property).
|
||||
// Ideally, either design will be changed or another implementation of this functionality will be explored.
|
||||
|
||||
const visibleOptions = filterOption
|
||||
? (options ?? []).filter((o) =>
|
||||
filterOption(
|
||||
{
|
||||
// @ts-ignore
|
||||
...o,
|
||||
data: {
|
||||
__isNew__: false,
|
||||
},
|
||||
},
|
||||
inputValue
|
||||
)
|
||||
)
|
||||
: options;
|
||||
const lastOption = visibleOptions[
|
||||
visibleOptions!.length - 1
|
||||
] as IOption<unknown>;
|
||||
|
||||
const option = data as IOption<unknown>;
|
||||
const isLast = option.value === lastOption?.value;
|
||||
return (
|
||||
<Typography.Text
|
||||
fontSize="m"
|
||||
fontWeight={isSelected ? 600 : 400}
|
||||
className={cn(
|
||||
className,
|
||||
"block px-6 py-3 cursor-pointer",
|
||||
"text-light-neutral-200",
|
||||
"hover:bg-blue/50 hover:font-semibold",
|
||||
isLast && "rounded-b-2xl"
|
||||
)}
|
||||
{...innerProps}
|
||||
>
|
||||
{children}
|
||||
</Typography.Text>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { type PlaceholderProps } from "react-select";
|
||||
import { Typography } from "../../typography/Typography";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
|
||||
export const Placeholder = ({
|
||||
innerProps,
|
||||
children,
|
||||
selectProps: {
|
||||
customProps: { error },
|
||||
},
|
||||
}: PlaceholderProps) => {
|
||||
return (
|
||||
<Typography.Text
|
||||
{...innerProps}
|
||||
fontSize="m"
|
||||
fontWeight={400}
|
||||
style={{
|
||||
gridArea: "1 / 1 / 2 / 3",
|
||||
}}
|
||||
className={cn(
|
||||
innerProps.className,
|
||||
"text-light-neutral-300",
|
||||
error && "text-red-400"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Typography.Text>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Typography } from "../../typography/Typography";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import type { SingleValueProps } from "react-select";
|
||||
|
||||
export const SingleValue = ({
|
||||
innerProps,
|
||||
selectProps: {
|
||||
customProps: { error },
|
||||
},
|
||||
children,
|
||||
}: SingleValueProps) => {
|
||||
return (
|
||||
<Typography.Text
|
||||
{...innerProps}
|
||||
fontSize="m"
|
||||
fontWeight={400}
|
||||
style={{
|
||||
gridArea: "1 / 1 / 2 / 3",
|
||||
}}
|
||||
className={cn("text-white", error && "text-red-400")}
|
||||
>
|
||||
{children}
|
||||
</Typography.Text>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {} from "react-select/base";
|
||||
// This import is necessary for module augmentation.
|
||||
// It allows us to extend the 'Props' interface in the 'react-select/base' module
|
||||
// and add our custom property 'myCustomProp' to it.
|
||||
|
||||
declare module "react-select/base" {
|
||||
export interface Props<
|
||||
Option,
|
||||
IsMulti extends boolean,
|
||||
Group extends GroupBase<Option>
|
||||
> {
|
||||
customProps: {
|
||||
error?: string;
|
||||
hint?: string;
|
||||
readOnly: boolean | undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Tabs } from "./Tabs";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Tabs",
|
||||
component: Tabs,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Tabs>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Main: Story = {
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
render: ({}) => (
|
||||
<Tabs>
|
||||
<Tabs.Item text="Overview" icon="HouseFill">
|
||||
Summary of data
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Analytics" icon="BarChartFill">
|
||||
Traffic and metrics
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Settings" icon="GearFill">
|
||||
Customize profile
|
||||
</Tabs.Item>
|
||||
</Tabs>
|
||||
),
|
||||
};
|
||||
export const Scrollable: Story = {
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
render: ({}) => (
|
||||
<div className="max-w-md">
|
||||
<Tabs>
|
||||
<Tabs.Item text="Overview" icon="HouseFill">
|
||||
Summary of data
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Analytics" icon="BarChartFill">
|
||||
Traffic and metrics
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Settings" icon="GearFill">
|
||||
Customize profile
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Billing" icon="CreditCardFill">
|
||||
Manage invoices
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Integrations" icon="PlugFill">
|
||||
Third-party services
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Notifications" icon="BellFill">
|
||||
Set alert preferences
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Reports" icon="FileEarmarkBarGraphFill">
|
||||
Generate PDF reports
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Feedback" icon="ChatDotsFill">
|
||||
Leave your thoughts
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Access Control" icon="ShieldLockFill">
|
||||
Manage roles and permissions
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Activity Log" icon="ClockHistory">
|
||||
Track recent actions
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Support" icon="LifePreserver">
|
||||
Get help and resources
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="API Keys" icon="KeyFill">
|
||||
Generate or revoke keys
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Localization" icon="Translate">
|
||||
Language and region
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Deployments" icon="CloudUploadFill">
|
||||
View deployment history
|
||||
</Tabs.Item>
|
||||
<Tabs.Item text="Audit Trail" icon="FileLockFill">
|
||||
Security and compliance logs
|
||||
</Tabs.Item>
|
||||
</Tabs>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
useRef,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import type { HTMLProps } from "../../shared/types";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import React from "react";
|
||||
import {
|
||||
TabItem,
|
||||
type TabItemProps,
|
||||
type TabItemPropsPublic,
|
||||
} from "./components/TabItem";
|
||||
import { useElementOverflow } from "./hooks/use-element-overflow";
|
||||
import { useElementScroll } from "./hooks/use-element-scroll";
|
||||
import { TabScroller } from "./components/TabScroller";
|
||||
|
||||
export type TabsProps = HTMLProps<"div">;
|
||||
|
||||
type TabsType = React.FC<PropsWithChildren<TabsProps>> & {
|
||||
Item: React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
||||
};
|
||||
|
||||
const Tabs: TabsType = ({ children, ...props }) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const tabListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isOverflowing = useElementOverflow({
|
||||
contentRef: tabListRef,
|
||||
containerRef,
|
||||
});
|
||||
const { canScrollLeft, canScrollRight, scrollLeft, scrollRight } =
|
||||
useElementScroll({
|
||||
containerRef,
|
||||
scrollRef: tabListRef,
|
||||
});
|
||||
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
const tabs =
|
||||
React.Children.map(children, (child, index: number) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = childrenArray.length - 1 === index;
|
||||
return React.cloneElement(
|
||||
child as ReactElement,
|
||||
{
|
||||
index,
|
||||
isFirst,
|
||||
isLast,
|
||||
isActive: index === activeIndex,
|
||||
onSelect: () => setActiveIndex(index),
|
||||
} as TabItemProps
|
||||
);
|
||||
}) ?? [];
|
||||
|
||||
return (
|
||||
<div className={cn("w-full")}>
|
||||
<div className={cn("flex flex-row items-stretch")} ref={containerRef}>
|
||||
{canScrollLeft && isOverflowing && (
|
||||
<TabScroller onScroll={scrollLeft} position="left" />
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn("flex", "overflow-x-auto scrollbar-none")}
|
||||
ref={tabListRef}
|
||||
role="tablist"
|
||||
aria-label="Tabs"
|
||||
>
|
||||
{tabs}
|
||||
</div>
|
||||
|
||||
{canScrollRight && isOverflowing && (
|
||||
<TabScroller onScroll={scrollRight} position="right" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"border-1 border-t-0 rounded-b-2xl border-light-neutral-500",
|
||||
"bg-grey-970 p-4 text-light-neutral-300"
|
||||
)}
|
||||
>
|
||||
{tabs.map((child, index) => {
|
||||
if (index !== activeIndex) {
|
||||
return null;
|
||||
}
|
||||
const tabContent = (child.props as PropsWithChildren).children;
|
||||
return (
|
||||
<div key={index} role="tabpanel" aria-labelledby={`tab-${index}`}>
|
||||
{tabContent}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.Item = TabItem as React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
||||
|
||||
export { Tabs };
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { HTMLProps } from "../../../shared/types";
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import React from "react";
|
||||
import { Icon, type IconProps } from "../../icon/Icon";
|
||||
import { Typography } from "../../typography/Typography";
|
||||
|
||||
export type TabItemProps = HTMLProps<"div"> & {
|
||||
icon?: IconProps["icon"];
|
||||
text: string;
|
||||
children: React.ReactNode;
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
isFirst: boolean;
|
||||
isLast: boolean;
|
||||
onSelect: () => void;
|
||||
};
|
||||
export type TabItemPropsPublic = Omit<
|
||||
TabItemProps,
|
||||
"index" | "isActive" | "isFirst" | "isLast" | "onSelect"
|
||||
>;
|
||||
|
||||
export const TabItem = ({
|
||||
text,
|
||||
index,
|
||||
isActive,
|
||||
isFirst,
|
||||
isLast,
|
||||
onSelect,
|
||||
icon,
|
||||
}: TabItemProps) => {
|
||||
return (
|
||||
<button
|
||||
role="tab"
|
||||
id={`tab-${index}`}
|
||||
aria-selected={isActive}
|
||||
aria-controls={`panel-${index}`}
|
||||
className={cn(
|
||||
"flex items-center gap-x-3 cursor-pointer",
|
||||
"px-6 py-3",
|
||||
"text-light-neutral-15 whitespace-nowrap",
|
||||
"border-light-neutral-500 border-b-1 border-t-1 border-r-1",
|
||||
"bg-light-neutral-970 focus:outline-0",
|
||||
"enabled:hover:bg-light-neutral-500",
|
||||
"enabled:focus:bg-grey-970",
|
||||
"enabled:active:bg-grey-970 enabled:active:text-primary-500",
|
||||
isActive && "enabled:text-primary-500",
|
||||
isFirst && "border-l-1 rounded-tl-2xl",
|
||||
isLast && "rounded-tr-2xl"
|
||||
)}
|
||||
onClick={onSelect}
|
||||
>
|
||||
{icon && <Icon icon={icon} className={cn("w-4 h-4 shrink-0")} />}
|
||||
<Typography.Text fontSize={"s"} fontWeight={400}>
|
||||
{text}
|
||||
</Typography.Text>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { cn } from "../../../shared/utils/cn";
|
||||
import { Icon, type IconProps } from "../../icon/Icon";
|
||||
|
||||
type TabScrollerProps = {
|
||||
position: "left" | "right";
|
||||
onScroll(): void;
|
||||
};
|
||||
|
||||
const tabScrollMetadata: Record<
|
||||
TabScrollerProps["position"],
|
||||
{ className: string; icon: IconProps["icon"] }
|
||||
> = {
|
||||
left: {
|
||||
className: cn("rounded-tl-2xl"),
|
||||
icon: "ChevronDoubleLeft",
|
||||
},
|
||||
right: {
|
||||
className: cn("rounded-tr-2xl"),
|
||||
icon: "ChevronDoubleRight",
|
||||
},
|
||||
};
|
||||
|
||||
export const TabScroller = ({ position, onScroll }: TabScrollerProps) => {
|
||||
const { className, icon } = tabScrollMetadata[position];
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onScroll}
|
||||
className={cn(
|
||||
"border-1 border-light-neutral-500",
|
||||
"flex flex-row items-center px-4",
|
||||
"bg-light-neutral-970",
|
||||
"enabled:hover:bg-light-neutral-500",
|
||||
"enabled:focus:bg-light-neutral-970",
|
||||
"enabled:active:bg-grey-970",
|
||||
className
|
||||
)}
|
||||
aria-label={`Scroll tabs ${position}`}
|
||||
>
|
||||
<Icon icon={icon} className={cn("w-4.5 h-4.5 text-primary-500")} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
|
||||
type UseElementOverflowParams = {
|
||||
contentRef: React.RefObject<HTMLDivElement | null>;
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
export function useElementOverflow({
|
||||
contentRef,
|
||||
containerRef,
|
||||
}: UseElementOverflowParams): boolean {
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverflow = () => {
|
||||
const container = containerRef.current;
|
||||
const content = contentRef.current;
|
||||
if (container && content) {
|
||||
setIsOverflowing(content.scrollWidth > container.clientWidth);
|
||||
}
|
||||
};
|
||||
|
||||
checkOverflow();
|
||||
const resizeObserver = new ResizeObserver(checkOverflow);
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
if (contentRef.current) {
|
||||
resizeObserver.observe(contentRef.current);
|
||||
}
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, [containerRef, contentRef]);
|
||||
|
||||
return isOverflowing;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
|
||||
const SCROLL_OFFSET = 200;
|
||||
|
||||
type UseElementScrollParams = {
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
export function useElementScroll({
|
||||
containerRef,
|
||||
scrollRef,
|
||||
}: UseElementScrollParams) {
|
||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||
const [canScrollRight, setCanScrollRight] = useState(false);
|
||||
|
||||
const updateScrollState = useCallback(() => {
|
||||
const container = containerRef?.current;
|
||||
const scroll = scrollRef?.current;
|
||||
if (!container || !scroll) {
|
||||
return;
|
||||
}
|
||||
const maxScrollLeft = scroll.scrollWidth - container.clientWidth;
|
||||
|
||||
setCanScrollLeft(scroll.scrollLeft > 0);
|
||||
setCanScrollRight(scroll.scrollLeft < maxScrollLeft);
|
||||
}, [containerRef, scrollRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const scroll = scrollRef?.current;
|
||||
if (!scroll) {
|
||||
return;
|
||||
}
|
||||
updateScrollState();
|
||||
|
||||
const preventScroll = (e: Event) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
scroll.addEventListener("wheel", preventScroll, { passive: false });
|
||||
|
||||
scroll.addEventListener("scroll", updateScrollState);
|
||||
window.addEventListener("resize", updateScrollState);
|
||||
|
||||
return () => {
|
||||
scroll.removeEventListener("wheel", preventScroll);
|
||||
scroll.removeEventListener("scroll", updateScrollState);
|
||||
window.removeEventListener("resize", updateScrollState);
|
||||
};
|
||||
}, [updateScrollState]);
|
||||
|
||||
const scrollLeft = useCallback(() => {
|
||||
scrollRef?.current?.scrollBy?.({
|
||||
left: -SCROLL_OFFSET,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, []);
|
||||
|
||||
const scrollRight = useCallback(() => {
|
||||
scrollRef?.current?.scrollBy?.({ left: SCROLL_OFFSET, behavior: "smooth" });
|
||||
}, []);
|
||||
|
||||
return {
|
||||
canScrollLeft,
|
||||
canScrollRight,
|
||||
scrollLeft,
|
||||
scrollRight,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Button } from "../button/Button";
|
||||
import { ToastManager } from "./ToastManager";
|
||||
import { toasterMessages } from "./Toast";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Toast",
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const ToastComponent = () => {
|
||||
const notify = () => {
|
||||
toasterMessages.error("Lorem Ipsum");
|
||||
toasterMessages.success("Lorem Ipsum");
|
||||
toasterMessages.info("Lorem Ipsum");
|
||||
toasterMessages.warning("Lorem Ipsum");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToastManager>
|
||||
<Button onClick={notify}>Notify</Button>
|
||||
</ToastManager>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Main: Story = {
|
||||
args: {},
|
||||
render: ToastComponent,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
import { Icon, type IconProps } from "../icon/Icon";
|
||||
import { cn } from "../../shared/utils/cn";
|
||||
import { Typography } from "../typography/Typography";
|
||||
import { toastStyles } from "./utils";
|
||||
|
||||
type IBaseToastProps = {
|
||||
id: string | number;
|
||||
text: string;
|
||||
icon: IconProps["icon"];
|
||||
iconClassName: string;
|
||||
};
|
||||
const BaseToast = (props: IBaseToastProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-1 border-light-neutral-500 rounded-l-[100px] rounded-r-4xl",
|
||||
"bg-light-neutral-900 px-3 py-3",
|
||||
"max-w-sm min-w-32",
|
||||
"flex flex-row items-center justify-between gap-x-4"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={props.icon}
|
||||
className={cn("w-6 h-6 flex-shrink-0", props.iconClassName)}
|
||||
/>
|
||||
<Typography.Text fontSize="xs" className="text-white">
|
||||
{props.text}
|
||||
</Typography.Text>
|
||||
<button
|
||||
onClick={() => sonnerToast.dismiss(props.id)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Icon icon="X" className={cn("w-6 h-6 flex-shrink-0 text-white")} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const toasterMessages = {
|
||||
error: (text?: string) => {
|
||||
const styles = toastStyles["error"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
},
|
||||
success: (text?: string) => {
|
||||
const styles = toastStyles["success"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
},
|
||||
info: (text?: string) => {
|
||||
const styles = toastStyles["info"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
},
|
||||
warning: (text?: string) => {
|
||||
const styles = toastStyles["warning"];
|
||||
sonnerToast.custom((id) => (
|
||||
<BaseToast
|
||||
id={id}
|
||||
icon={styles.icon}
|
||||
iconClassName={cn(styles.iconColor)}
|
||||
text={text!}
|
||||
/>
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type PropsWithChildren } from "react";
|
||||
import { Toaster, type ToasterProps } from "sonner";
|
||||
|
||||
export const ToastManager = (props: PropsWithChildren<ToasterProps>) => {
|
||||
return (
|
||||
<>
|
||||
<Toaster {...props} />
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { IconProps } from "../icon/Icon";
|
||||
|
||||
type ToastType = "error" | "success" | "info" | "warning";
|
||||
|
||||
export const toastStyles: Record<
|
||||
ToastType,
|
||||
{ icon: IconProps["icon"]; iconColor: string }
|
||||
> = {
|
||||
error: {
|
||||
icon: "ExclamationOctagonFill",
|
||||
iconColor: "text-red-400",
|
||||
},
|
||||
success: {
|
||||
icon: "CheckCircleFill",
|
||||
iconColor: "text-green-500",
|
||||
},
|
||||
info: {
|
||||
icon: "PatchQuestionFill",
|
||||
iconColor: "text-aqua-500",
|
||||
},
|
||||
warning: {
|
||||
icon: "ExclamationTriangleFill",
|
||||
iconColor: "text-primary-500",
|
||||
},
|
||||
};
|
||||
@@ -8,8 +8,12 @@ export { Input } from "./components/input/Input";
|
||||
export { InteractiveChip } from "./components/interactive-chip/InteractiveChip";
|
||||
export { RadioGroup } from "./components/radio-group/RadioGroup";
|
||||
export { RadioOption } from "./components/radio-group/RadioOption";
|
||||
export { Select } from "./components/select/Select";
|
||||
export { Scrollable } from "./components/scrollable/Scrollable";
|
||||
export { ToastManager } from "./components/toast/ToastManager";
|
||||
export { toasterMessages } from "./components/toast/Toast";
|
||||
export { Toggle } from "./components/toggle/Toggle";
|
||||
export { Tabs } from "./components/tabs/Tabs";
|
||||
export { Tooltip } from "./components/tooltip/Tooltip";
|
||||
export { Typography } from "./components/typography/Typography";
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"email": "stephan@all-hands.dev"
|
||||
}
|
||||
],
|
||||
"version": "1.0.0-beta.4",
|
||||
"version": "1.0.0-beta.5",
|
||||
"description": "OpenHands UI Components",
|
||||
"keywords": [
|
||||
"openhands",
|
||||
@@ -78,6 +78,8 @@
|
||||
"clsx": "^2.1.1",
|
||||
"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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% if repository_info %}
|
||||
<REPOSITORY_INFO>
|
||||
At the user's request, repository {{ repository_info.repo_name }} has been cloned to the current working directory {{ repository_info.repo_directory }}.
|
||||
At the user's request, repository {{ repository_info.repo_name }} has been cloned to {{ repository_info.repo_directory }} in the current working directory.
|
||||
</REPOSITORY_INFO>
|
||||
{% endif %}
|
||||
{% if repository_instructions -%}
|
||||
@@ -10,6 +10,9 @@ At the user's request, repository {{ repository_info.repo_name }} has been clone
|
||||
{% endif %}
|
||||
{% if runtime_info -%}
|
||||
<RUNTIME_INFORMATION>
|
||||
{% if runtime_info.working_dir %}
|
||||
The current working directory is {{ runtime_info.working_dir }}
|
||||
{% endif %}
|
||||
{% if runtime_info.available_hosts %}
|
||||
The user has access to the following hosts for accessing a web application,
|
||||
each of which has a corresponding port:
|
||||
|
||||
@@ -14,6 +14,12 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
* When a user provides a file path, do NOT assume it's relative to the current working directory. First explore the file system to locate the file before working on it.
|
||||
* If asked to edit a file, edit the file directly, rather than creating a new file with a different filename.
|
||||
* For global search-and-replace operations, consider using `sed` instead of opening file editors multiple times.
|
||||
* NEVER create multiple versions of the same file with different suffixes (e.g., file_test.py, file_fix.py, file_simple.py). Instead:
|
||||
- Always modify the original file directly when making changes
|
||||
- If you need to create a temporary file for testing, delete it once you've confirmed your solution works
|
||||
- If you decide a file you created is no longer useful, delete it instead of creating a new version
|
||||
* Do NOT include documentation files explaining your changes in version control unless the user explicitly requests it
|
||||
* When reproducing bugs or implementing fixes, use a single file rather than creating multiple files with different versions
|
||||
</FILE_SYSTEM_GUIDELINES>
|
||||
|
||||
<CODE_QUALITY>
|
||||
@@ -21,6 +27,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
* When implementing solutions, focus on making the minimal changes needed to solve the problem.
|
||||
* Before implementing any changes, first thoroughly understand the codebase through exploration.
|
||||
* If you are adding a lot of code to a function or file, consider splitting the function or file into smaller pieces when appropriate.
|
||||
* Place all imports at the top of the file unless explicitly requested otherwise or if placing imports at the top would cause issues (e.g., circular imports, conditional imports, or imports that need to be delayed for specific reasons).
|
||||
</CODE_QUALITY>
|
||||
|
||||
<VERSION_CONTROL>
|
||||
@@ -46,7 +53,10 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
* For new features: Consider test-driven development when appropriate
|
||||
* If the repository lacks testing infrastructure and implementing tests would require extensive setup, consult with the user before investing time in building testing infrastructure
|
||||
* If the environment is not set up to run tests, consult with the user first before investing time to install all dependencies
|
||||
4. IMPLEMENTATION: Make focused, minimal changes to address the problem
|
||||
4. IMPLEMENTATION:
|
||||
* Make focused, minimal changes to address the problem
|
||||
* Always modify existing files directly rather than creating new versions with different suffixes
|
||||
* If you create temporary files for testing, delete them after confirming your solution works
|
||||
5. VERIFICATION: If the environment is set up to run tests, test your implementation thoroughly, including edge cases. If the environment is not set up to run tests, consult with the user first before investing time to run tests.
|
||||
</PROBLEM_SOLVING_WORKFLOW>
|
||||
|
||||
@@ -72,3 +82,13 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
4. Document your reasoning process
|
||||
* When you run into any major issue while executing a plan from the user, please don't try to directly work around it. Instead, propose a new plan and confirm with the user before proceeding.
|
||||
</TROUBLESHOOTING>
|
||||
|
||||
<DOCUMENTATION>
|
||||
* When explaining changes or solutions to the user:
|
||||
- Include explanations in your conversation responses rather than creating separate documentation files
|
||||
- If you need to create documentation files for reference, do NOT include them in version control unless explicitly requested
|
||||
- Never create multiple versions of documentation files with different suffixes
|
||||
* If the user asks for documentation:
|
||||
- Confirm whether they want it as a separate file or just in the conversation
|
||||
- Ask if they want documentation files to be included in version control
|
||||
</DOCUMENTATION>
|
||||
|
||||
@@ -21,6 +21,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
* When implementing solutions, focus on making the minimal changes needed to solve the problem.
|
||||
* Before implementing any changes, first thoroughly understand the codebase through exploration.
|
||||
* If you are adding a lot of code to a function or file, consider splitting the function or file into smaller pieces when appropriate.
|
||||
* Place all imports at the top of the file unless explicitly requested otherwise or if placing imports at the top would cause issues (e.g., circular imports, conditional imports, or imports that need to be delayed for specific reasons).
|
||||
</CODE_QUALITY>
|
||||
|
||||
<VERSION_CONTROL>
|
||||
|
||||
@@ -21,6 +21,7 @@ Your primary role is to assist users by executing commands, modifying code, and
|
||||
* When implementing solutions, focus on making the minimal changes needed to solve the problem.
|
||||
* Before implementing any changes, first thoroughly understand the codebase through exploration.
|
||||
* If you are adding a lot of code to a function or file, consider splitting the function or file into smaller pieces when appropriate.
|
||||
* Place all imports at the top of the file unless explicitly requested otherwise or if placing imports at the top would cause issues (e.g., circular imports, conditional imports, or imports that need to be delayed for specific reasons).
|
||||
</CODE_QUALITY>
|
||||
|
||||
<VERSION_CONTROL>
|
||||
|
||||
@@ -8,8 +8,6 @@ from openhands.events.action import (
|
||||
Action,
|
||||
AgentFinishAction,
|
||||
AgentRejectAction,
|
||||
BrowseInteractiveAction,
|
||||
BrowseURLAction,
|
||||
CmdRunAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
@@ -17,7 +15,6 @@ from openhands.events.action import (
|
||||
)
|
||||
from openhands.events.observation import (
|
||||
AgentStateChangedObservation,
|
||||
BrowserOutputObservation,
|
||||
CmdOutputMetadata,
|
||||
CmdOutputObservation,
|
||||
FileReadObservation,
|
||||
@@ -54,17 +51,19 @@ class DummyAgent(Agent):
|
||||
},
|
||||
{
|
||||
'action': CmdRunAction(command='echo "foo"'),
|
||||
'observations': [CmdOutputObservation('foo', command='echo "foo"')],
|
||||
'observations': [
|
||||
CmdOutputObservation(
|
||||
'foo',
|
||||
command='echo "foo"',
|
||||
metadata=CmdOutputMetadata(exit_code=0),
|
||||
)
|
||||
],
|
||||
},
|
||||
{
|
||||
'action': FileWriteAction(
|
||||
content='echo "Hello, World!"', path='hello.sh'
|
||||
),
|
||||
'observations': [
|
||||
FileWriteObservation(
|
||||
content='echo "Hello, World!"', path='hello.sh'
|
||||
)
|
||||
],
|
||||
'observations': [FileWriteObservation(content='', path='hello.sh')],
|
||||
},
|
||||
{
|
||||
'action': FileReadAction(path='hello.sh'),
|
||||
@@ -76,36 +75,12 @@ class DummyAgent(Agent):
|
||||
'action': CmdRunAction(command='bash hello.sh'),
|
||||
'observations': [
|
||||
CmdOutputObservation(
|
||||
'bash: hello.sh: No such file or directory',
|
||||
command='bash workspace/hello.sh',
|
||||
metadata=CmdOutputMetadata(exit_code=127),
|
||||
'Hello, World!',
|
||||
command='bash hello.sh',
|
||||
metadata=CmdOutputMetadata(exit_code=0),
|
||||
)
|
||||
],
|
||||
},
|
||||
{
|
||||
'action': BrowseURLAction(url='https://google.com'),
|
||||
'observations': [
|
||||
BrowserOutputObservation(
|
||||
'<html><body>Simulated Google page</body></html>',
|
||||
url='https://google.com',
|
||||
screenshot='',
|
||||
trigger_by_action='',
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
'action': BrowseInteractiveAction(
|
||||
browser_actions='goto("https://google.com")'
|
||||
),
|
||||
'observations': [
|
||||
BrowserOutputObservation(
|
||||
'<html><body>Simulated Google page after interaction</body></html>',
|
||||
url='https://google.com',
|
||||
screenshot='',
|
||||
trigger_by_action='',
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
'action': AgentRejectAction(),
|
||||
'observations': [AgentStateChangedObservation('', AgentState.REJECTED)],
|
||||
@@ -147,6 +122,47 @@ class DummyAgent(Agent):
|
||||
obs.pop('timestamp', None)
|
||||
obs.pop('cause', None)
|
||||
obs.pop('source', None)
|
||||
# Remove dynamic metadata fields that vary between runs
|
||||
if 'extras' in obs and 'metadata' in obs['extras']:
|
||||
metadata = obs['extras']['metadata']
|
||||
if isinstance(metadata, dict):
|
||||
metadata.pop('pid', None)
|
||||
metadata.pop('username', None)
|
||||
metadata.pop('hostname', None)
|
||||
metadata.pop('working_dir', None)
|
||||
metadata.pop('py_interpreter_path', None)
|
||||
metadata.pop('suffix', None)
|
||||
# Normalize file paths for comparison - extract just the filename
|
||||
if 'extras' in obs and 'path' in obs['extras']:
|
||||
path = obs['extras']['path']
|
||||
if isinstance(path, str):
|
||||
# Extract just the filename from the path
|
||||
import os
|
||||
|
||||
obs['extras']['path'] = os.path.basename(path)
|
||||
# Normalize message field to handle path differences
|
||||
if 'message' in obs:
|
||||
import os
|
||||
|
||||
message = obs['message']
|
||||
if isinstance(message, str):
|
||||
# Replace full paths with just filenames in messages
|
||||
if 'I wrote to the file ' in message:
|
||||
parts = message.split('I wrote to the file ')
|
||||
if len(parts) == 2:
|
||||
filename = os.path.basename(
|
||||
parts[1].rstrip('.')
|
||||
)
|
||||
obs['message'] = (
|
||||
f'I wrote to the file {filename}.'
|
||||
)
|
||||
elif 'I read the file ' in message:
|
||||
parts = message.split('I read the file ')
|
||||
if len(parts) == 2:
|
||||
filename = os.path.basename(
|
||||
parts[1].rstrip('.')
|
||||
)
|
||||
obs['message'] = f'I read the file {filename}.'
|
||||
|
||||
if hist_obs != expected_obs:
|
||||
print(
|
||||
|
||||
@@ -10,6 +10,9 @@ At the user's request, repository {{ repository_info.repo_name }} has been clone
|
||||
{% endif %}
|
||||
{% if runtime_info and (runtime_info.additional_agent_instructions or runtime_info.date) -%}
|
||||
<RUNTIME_INFORMATION>
|
||||
{% if runtime_info.working_dir %}
|
||||
The current working directory is {{ runtime_info.working_dir }}
|
||||
{% endif %}
|
||||
{% if runtime_info.additional_agent_instructions %}
|
||||
{{ runtime_info.additional_agent_instructions }}
|
||||
{% endif %}
|
||||
|
||||
+17
-2
@@ -231,12 +231,26 @@ async def run_session(
|
||||
return
|
||||
|
||||
confirmation_status = await read_confirmation_input(config)
|
||||
if confirmation_status == 'yes' or confirmation_status == 'always':
|
||||
if confirmation_status in ('yes', 'always'):
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_CONFIRMED),
|
||||
EventSource.USER,
|
||||
)
|
||||
else:
|
||||
elif confirmation_status == 'edit':
|
||||
# Tell the agent the proposed action was rejected
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_REJECTED),
|
||||
EventSource.USER,
|
||||
)
|
||||
# Notify the user
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<skyblue>Okay, please tell me what I should do instead.</skyblue>'
|
||||
)
|
||||
)
|
||||
# Solicit replacement isntructions
|
||||
await prompt_for_next_task(AgentState.AWAITING_USER_INPUT)
|
||||
else: # 'no' or fallback
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_REJECTED),
|
||||
EventSource.USER,
|
||||
@@ -277,6 +291,7 @@ async def run_session(
|
||||
selected_repository=config.sandbox.selected_repo,
|
||||
repo_directory=repo_directory,
|
||||
conversation_instructions=conversation_instructions,
|
||||
working_dir=os.getcwd(),
|
||||
)
|
||||
|
||||
# Add MCP tools to the agent
|
||||
|
||||
+11
-25
@@ -589,34 +589,20 @@ async def read_prompt_input(
|
||||
|
||||
async def read_confirmation_input(config: OpenHandsConfig) -> str:
|
||||
try:
|
||||
prompt_session = create_prompt_session(config)
|
||||
choices = [
|
||||
'Yes, proceed',
|
||||
'No, skip this action',
|
||||
"Always proceed (don't ask again)",
|
||||
'Let me provide different instructions',
|
||||
]
|
||||
|
||||
while True:
|
||||
with patch_stdout():
|
||||
print_formatted_text('')
|
||||
confirmation: str = await prompt_session.prompt_async(
|
||||
HTML('<gold>Proceed with action? (y)es/(n)o/(a)lways > </gold>'),
|
||||
)
|
||||
# keep the outer coroutine responsive by using asyncio.to_thread which puts the blocking call app.run() of cli_confirm() in a separate thread
|
||||
index = await asyncio.to_thread(
|
||||
cli_confirm, config, 'Choose an option:', choices
|
||||
)
|
||||
|
||||
confirmation = (
|
||||
'' if confirmation is None else confirmation.strip().lower()
|
||||
)
|
||||
return {0: 'yes', 1: 'no', 2: 'always', 3: 'edit'}.get(index, 'no')
|
||||
|
||||
if confirmation in ['y', 'yes']:
|
||||
return 'yes'
|
||||
elif confirmation in ['n', 'no']:
|
||||
return 'no'
|
||||
elif confirmation in ['a', 'always']:
|
||||
return 'always'
|
||||
else:
|
||||
# Display error message for invalid input
|
||||
print_formatted_text('')
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansired>Invalid input. Please enter (y)es, (n)o, or (a)lways.</ansired>'
|
||||
)
|
||||
)
|
||||
# Continue the loop to re-prompt
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return 'no'
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user