mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
34 Commits
fix/utils-
...
0.32.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a820e45ec4 | ||
|
|
588b9f34be | ||
|
|
fb02fefaca | ||
|
|
856d5ff976 | ||
|
|
eb4aeb3922 | ||
|
|
4b47e5215b | ||
|
|
0087082643 | ||
|
|
e698a393b2 | ||
|
|
d48e2a4cf1 | ||
|
|
749a903de5 | ||
|
|
0a6321246a | ||
|
|
ddda30d9b7 | ||
|
|
d1851cc3ee | ||
|
|
883bf5847b | ||
|
|
1c8ab4d835 | ||
|
|
255e209886 | ||
|
|
c8904e4672 | ||
|
|
dcfc2da428 | ||
|
|
84e28234e5 | ||
|
|
60e8b5841c | ||
|
|
dd03d9adce | ||
|
|
9fa211bc27 | ||
|
|
22cf5144cc | ||
|
|
99e493b3a4 | ||
|
|
e4d92d6f56 | ||
|
|
a9d08852db | ||
|
|
408ad1ff2b | ||
|
|
69d4a8df7c | ||
|
|
02f92164de | ||
|
|
1549927fa3 | ||
|
|
e951da7a25 | ||
|
|
f830d5814c | ||
|
|
0519e9e3c2 | ||
|
|
9b8a628395 |
5
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
5
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
@@ -5,11 +5,12 @@ labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thank you for taking the time to fill out this bug report. Please provide as much information as possible to help us understand and address the issue effectively.
|
||||
value: Thank you for taking the time to fill out this bug report. Please provide as much information as possible
|
||||
to help us understand and address the issue effectively.
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for the same bug?
|
||||
label: Is there an existing issue for the same bug? (If one exists, thumbs up or comment on the issue instead).
|
||||
description: Please check if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have checked the existing issues.
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for OpenHands features
|
||||
name: Feature Request or Enhancement
|
||||
about: Suggest an idea for an OpenHands feature or enhancement
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
@@ -9,10 +9,6 @@ assignees: ''
|
||||
|
||||
**What problem or use case are you trying to solve?**
|
||||
|
||||
**Describe the UX of the solution you'd like**
|
||||
|
||||
**Do you have thoughts on the technical implementation?**
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
**Describe the UX or technical implementation you have in mind**
|
||||
|
||||
**Additional context**
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/technical_proposal.md
vendored
18
.github/ISSUE_TEMPLATE/technical_proposal.md
vendored
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: Technical Proposal
|
||||
about: Propose a new architecture or technology
|
||||
title: ''
|
||||
labels: 'proposal'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Summary**
|
||||
|
||||
**Motivation**
|
||||
|
||||
**Technical Design**
|
||||
|
||||
**Alternatives to Consider**
|
||||
|
||||
**Additional context**
|
||||
83
.github/workflows/ghcr-build.yml
vendored
83
.github/workflows/ghcr-build.yml
vendored
@@ -25,10 +25,31 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BASE_IMAGE_FOR_HASH_EQUIVALENCE_TEST: nikolaik/python-nodejs:python3.12-nodejs22
|
||||
RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
jobs:
|
||||
define-matrix:
|
||||
runs-on: blacksmith
|
||||
outputs:
|
||||
base_image: ${{ steps.define-base-images.outputs.base_image }}
|
||||
steps:
|
||||
- name: Define base images
|
||||
shell: bash
|
||||
id: define-base-images
|
||||
run: |
|
||||
# Only build nikolaik on PRs, otherwise build both nikolaik and ubuntu.
|
||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
||||
json=$(jq -n -c '[
|
||||
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" }
|
||||
]')
|
||||
else
|
||||
json=$(jq -n -c '[
|
||||
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" },
|
||||
{ image: "ubuntu:24.04", tag: "ubuntu" }
|
||||
]')
|
||||
fi
|
||||
echo "base_image=$json" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Builds the OpenHands Docker images
|
||||
ghcr_build_app:
|
||||
name: Build App Image
|
||||
@@ -37,6 +58,7 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
# Since this job uses outputs it cannot use matrix
|
||||
hash_from_app_image: ${{ steps.get_hash_in_app_image.outputs.hash_from_app_image }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -84,11 +106,10 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: define-matrix
|
||||
strategy:
|
||||
matrix:
|
||||
base_image:
|
||||
- image: 'nikolaik/python-nodejs:python3.12-nodejs22'
|
||||
tag: nikolaik
|
||||
base_image: ${{ fromJson(needs.define-matrix.outputs.base_image) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -118,6 +139,7 @@ jobs:
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
# This is the one that saves the cache, the others set 'lookup-only: true'
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Install poetry via pipx
|
||||
@@ -129,14 +151,33 @@ jobs:
|
||||
- name: Lowercase Repository Owner
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- name: Short SHA
|
||||
run: |
|
||||
echo SHORT_SHA=$(git rev-parse --short "$RELEVANT_SHA") >> $GITHUB_ENV
|
||||
- name: Determine docker build params
|
||||
if: github.event.pull_request.head.repo.fork != true
|
||||
shell: bash
|
||||
run: |
|
||||
./containers/build.sh -i runtime -o ${{ env.REPO_OWNER }} -t ${{ matrix.base_image.tag }} --dry
|
||||
|
||||
DOCKER_BUILD_JSON=$(jq -c . < docker-build-dry.json)
|
||||
echo "DOCKER_TAGS=$(echo "$DOCKER_BUILD_JSON" | jq -r '.tags | join(",")')" >> $GITHUB_ENV
|
||||
echo "DOCKER_PLATFORM=$(echo "$DOCKER_BUILD_JSON" | jq -r '.platform')" >> $GITHUB_ENV
|
||||
echo "DOCKER_BUILD_ARGS=$(echo "$DOCKER_BUILD_JSON" | jq -r '.build_args | join(",")')" >> $GITHUB_ENV
|
||||
- name: Build and push runtime image ${{ matrix.base_image.image }}
|
||||
if: github.event.pull_request.head.repo.fork != true
|
||||
run: |
|
||||
./containers/build.sh -i runtime -o ${{ env.REPO_OWNER }} --push -t ${{ matrix.base_image.tag }}
|
||||
uses: useblacksmith/build-push-action@v1
|
||||
with:
|
||||
push: true
|
||||
tags: ${{ env.DOCKER_TAGS }}
|
||||
platforms: ${{ env.DOCKER_PLATFORM }}
|
||||
build-args: ${{ env.DOCKER_BUILD_ARGS }}
|
||||
context: containers/runtime
|
||||
provenance: false
|
||||
# Forked repos can't push to GHCR, so we need to upload the image as an artifact
|
||||
- name: Build runtime image ${{ matrix.base_image.image }} for fork
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: docker/build-push-action@v6
|
||||
uses: useblacksmith/build-push-action@v1
|
||||
with:
|
||||
tags: ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image.tag }}
|
||||
outputs: type=docker,dest=/tmp/runtime-${{ matrix.base_image.tag }}.tar
|
||||
@@ -156,6 +197,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
base_image: ['nikolaik']
|
||||
env:
|
||||
BASE_IMAGE_FOR_HASH_EQUIVALENCE_TEST: nikolaik/python-nodejs:python3.12-nodejs22
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -167,6 +210,7 @@ jobs:
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
lookup-only: true
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
@@ -203,12 +247,12 @@ jobs:
|
||||
# Run unit tests with the Docker runtime Docker images as root
|
||||
test_runtime_root:
|
||||
name: RT Unit Tests (Root)
|
||||
needs: [ghcr_build_runtime]
|
||||
needs: [ghcr_build_runtime, define-matrix]
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
base_image: ['nikolaik']
|
||||
base_image: ${{ fromJson(needs.define-matrix.outputs.base_image) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
@@ -219,12 +263,12 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image }}
|
||||
name: runtime-${{ matrix.base_image.tag }}
|
||||
path: /tmp
|
||||
- name: Load runtime image for fork
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
run: |
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image.tag }}.tar
|
||||
- name: Cache Poetry dependencies
|
||||
uses: useblacksmith/cache@v5
|
||||
with:
|
||||
@@ -232,6 +276,7 @@ jobs:
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
lookup-only: true
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
@@ -253,7 +298,10 @@ jobs:
|
||||
# Install to be able to retry on failures for flaky tests
|
||||
poetry run pip install pytest-rerunfailures
|
||||
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image.tag }}
|
||||
|
||||
# Setting RUN_AS_OPENHANDS to false means use root.
|
||||
# That should mean SANDBOX_USER_ID is ignored but some tests do not check for RUN_AS_OPENHANDS.
|
||||
|
||||
TEST_RUNTIME=docker \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
@@ -270,10 +318,10 @@ jobs:
|
||||
test_runtime_oh:
|
||||
name: RT Unit Tests (openhands)
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
needs: [ghcr_build_runtime]
|
||||
needs: [ghcr_build_runtime, define-matrix]
|
||||
strategy:
|
||||
matrix:
|
||||
base_image: ['nikolaik']
|
||||
base_image: ${{ fromJson(needs.define-matrix.outputs.base_image) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
@@ -284,12 +332,12 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image }}
|
||||
name: runtime-${{ matrix.base_image.tag }}
|
||||
path: /tmp
|
||||
- name: Load runtime image for fork
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
run: |
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image.tag }}.tar
|
||||
- name: Cache Poetry dependencies
|
||||
uses: useblacksmith/cache@v5
|
||||
with:
|
||||
@@ -297,6 +345,7 @@ jobs:
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
lookup-only: true
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
@@ -318,7 +367,7 @@ jobs:
|
||||
# Install to be able to retry on failures for flaky tests
|
||||
poetry run pip install pytest-rerunfailures
|
||||
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image.tag }}
|
||||
|
||||
TEST_RUNTIME=docker \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
|
||||
@@ -118,7 +118,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker container image by
|
||||
setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.31-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.32-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -185,7 +185,7 @@ test:
|
||||
|
||||
build-frontend:
|
||||
@echo "$(YELLOW)Building frontend...$(RESET)"
|
||||
@cd frontend && npm run build
|
||||
@cd frontend && npm run prepare && npm run build
|
||||
|
||||
# Start backend
|
||||
start-backend:
|
||||
|
||||
@@ -43,17 +43,17 @@ See the [Running OpenHands](https://docs.all-hands.dev/modules/usage/installatio
|
||||
system requirements and more information.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
|
||||
@@ -7,15 +7,17 @@ org_name=""
|
||||
push=0
|
||||
load=0
|
||||
tag_suffix=""
|
||||
dry_run=0
|
||||
|
||||
# Function to display usage information
|
||||
usage() {
|
||||
echo "Usage: $0 -i <image_name> [-o <org_name>] [--push] [--load] [-t <tag_suffix>]"
|
||||
echo "Usage: $0 -i <image_name> [-o <org_name>] [--push] [--load] [-t <tag_suffix>] [--dry]"
|
||||
echo " -i: Image name (required)"
|
||||
echo " -o: Organization name"
|
||||
echo " --push: Push the image"
|
||||
echo " --load: Load the image"
|
||||
echo " -t: Tag suffix"
|
||||
echo " --dry: Don't build, only create build-args.json"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -27,6 +29,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--push) push=1; shift ;;
|
||||
--load) load=1; shift ;;
|
||||
-t) tag_suffix="$2"; shift 2 ;;
|
||||
--dry) dry_run=1; shift ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
@@ -113,10 +116,13 @@ echo "Repo: $DOCKER_REPOSITORY"
|
||||
echo "Base dir: $DOCKER_BASE_DIR"
|
||||
|
||||
args=""
|
||||
full_tags=()
|
||||
for tag in "${tags[@]}"; do
|
||||
args+=" -t $DOCKER_REPOSITORY:$tag"
|
||||
full_tags+=("$DOCKER_REPOSITORY:$tag")
|
||||
done
|
||||
|
||||
|
||||
if [[ $push -eq 1 ]]; then
|
||||
args+=" --push"
|
||||
args+=" --cache-to=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag,mode=max"
|
||||
@@ -136,6 +142,26 @@ else
|
||||
# For push or without load, build for multiple platforms
|
||||
platform="linux/amd64,linux/arm64"
|
||||
fi
|
||||
if [[ $dry_run -eq 1 ]]; then
|
||||
echo "Dry Run is enabled. Writing build config to docker-build-dry.json"
|
||||
jq -n \
|
||||
--argjson tags "$(printf '%s\n' "${full_tags[@]}" | jq -R . | jq -s .)" \
|
||||
--arg platform "$platform" \
|
||||
--arg openhands_build_version "$OPENHANDS_BUILD_VERSION" \
|
||||
--arg dockerfile "$dir/Dockerfile" \
|
||||
'{
|
||||
tags: $tags,
|
||||
platform: $platform,
|
||||
build_args: [
|
||||
"OPENHANDS_BUILD_VERSION=" + $openhands_build_version
|
||||
],
|
||||
dockerfile: $dockerfile
|
||||
}' > docker-build-dry.json
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
|
||||
echo "Building for platform(s): $platform"
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"}
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.31-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.32-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of openhands-state for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -32,6 +32,8 @@ For instructions or processes that need to be followed in a specific order, use
|
||||
|
||||
Example:
|
||||
1. Step one: Do this.
|
||||
- First this sub step.
|
||||
- Then this sub step.
|
||||
2. Step two: Complete this action.
|
||||
3. Step three: Verify the result.
|
||||
|
||||
@@ -47,6 +49,14 @@ docker run -it \
|
||||
...
|
||||
```
|
||||
|
||||
### Use of Note and Warning
|
||||
|
||||
When adding a note or warning, use the built-in note and warning syntax.
|
||||
|
||||
Example:
|
||||
:::note
|
||||
This section is for advanced users only.
|
||||
:::
|
||||
|
||||
### Referring to UI Elements
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -61,7 +61,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -56,6 +56,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
La façon la plus simple d'exécuter OpenHands est avec Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter OpenHands en mode [headless scriptable](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), en tant que [CLI interactive](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), ou en utilisant l'[Action GitHub OpenHands](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
@@ -13,7 +13,7 @@ C'est le Runtime par défaut qui est utilisé lorsque vous démarrez OpenHands.
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -34,7 +34,7 @@ Docker で OpenHands を CLI モードで実行するには:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -44,7 +44,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ DockerでOpenHandsをヘッドレスモードで実行するには:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -42,7 +42,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ nikolaik の `SANDBOX_RUNTIME_CONTAINER_IMAGE` は、ランタイムサーバー
|
||||
|
||||
```bash
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
@@ -82,5 +82,5 @@ docker network create openhands-network
|
||||
# 分離されたネットワークで OpenHands を実行
|
||||
docker run # ... \
|
||||
--network openhands-network \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
@@ -35,7 +35,7 @@ Para executar o OpenHands no modo CLI com Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -45,7 +45,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Para executar o OpenHands no modo Headless com Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -43,7 +43,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.main -t "escreva um script bash que imprima oi"
|
||||
```
|
||||
|
||||
|
||||
@@ -58,17 +58,17 @@
|
||||
A maneira mais fácil de executar o OpenHands é no Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
Você encontrará o OpenHands em execução em http://localhost:3000!
|
||||
|
||||
@@ -13,7 +13,7 @@ Este é o Runtime padrão que é usado quando você inicia o OpenHands. Você po
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -50,7 +50,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -59,7 +59,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -57,6 +57,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
在 Docker 中运行 OpenHands 是最简单的方式。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
你也可以在可脚本化的[无头模式](https://docs.all-hands.dev/modules/usage/how-to/headless-mode)下运行 OpenHands,作为[交互式 CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),或使用 [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action)。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -4,9 +4,7 @@ OpenHands Cloud is the cloud hosted version of OpenHands by All Hands AI.
|
||||
|
||||
## Accessing OpenHands Cloud
|
||||
|
||||
Currently, users are being admitted to access OpenHands Cloud in waves. To sign up,
|
||||
[join the waitlist](https://www.all-hands.dev/join-waitlist). Once you are approved, you will get an email with
|
||||
instructions on how to access it.
|
||||
OpenHands Cloud can be accessed at https://app.all-hands.dev/.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Configuration Options
|
||||
|
||||
This guide details all configuration options available for OpenHands, helping you customize its behavior and integrate it with other services.
|
||||
|
||||
:::note
|
||||
If you are running in [GUI Mode](https://docs.all-hands.dev/modules/usage/how-to/gui-mode), the settings available in the Settings UI will always
|
||||
take precedence.
|
||||
This page outlines all available configuration options for OpenHands, allowing you to customize its behavior and
|
||||
integrate it with other services. In GUI Mode, any settings applied through the Settings UI will take precedence.
|
||||
:::
|
||||
|
||||
## Core Configuration
|
||||
|
||||
@@ -3,109 +3,97 @@
|
||||
So you've [run OpenHands](./installation) and have
|
||||
[set up your LLM](./installation#setup). Now what?
|
||||
|
||||
OpenHands can help you tackle a wide variety of engineering tasks. But the technology
|
||||
is still new, and we're a long way off from having agents that can take on large, complicated
|
||||
engineering tasks without any guidance. So it's important to get a feel for what the agent
|
||||
does well, and where it might need some help.
|
||||
OpenHands can assist with a range of engineering tasks. However, the technology is still new, and we’re far from having
|
||||
agents that can handle complex tasks independently. It’s important to understand what the agent does well and where it
|
||||
needs support.
|
||||
|
||||
## Hello World
|
||||
|
||||
The first thing you might want to try is a simple "hello world" example.
|
||||
This can be more complicated than it sounds!
|
||||
Start with a simple "hello world" example. It might be trickier than it seems!
|
||||
|
||||
Try prompting the agent with:
|
||||
> Please write a bash script hello.sh that prints "hello world!"
|
||||
Prompt the agent with:
|
||||
> Write a bash script hello.sh that prints "hello world!"
|
||||
|
||||
You should see that the agent not only writes the script, it sets the correct
|
||||
permissions and runs the script to check the output.
|
||||
The agent will write the script, set the correct permissions, and run it to check the output.
|
||||
|
||||
You can continue prompting the agent to refine your code. This is a great way to
|
||||
work with agents. Start simple, and iterate.
|
||||
|
||||
> Please modify hello.sh so that it accepts a name as the first argument, but defaults to "world"
|
||||
> Modify hello.sh so that it accepts a name as the first argument, but defaults to "world"
|
||||
|
||||
You can also work in any language you need, though the agent might need to spend some
|
||||
time setting up its environment!
|
||||
You can also use any language you need. The agent may need time to set up the environment.
|
||||
|
||||
> Please convert hello.sh to a Ruby script, and run it
|
||||
|
||||
## Building From Scratch
|
||||
|
||||
Agents do exceptionally well at "greenfield" tasks (tasks where they don't need
|
||||
any context about an existing codebase) and they can just start from scratch.
|
||||
|
||||
It's best to start with a simple task, and then iterate on it. It's also best to be
|
||||
as specific as possible about what you want, what the tech stack should be, etc.
|
||||
Agents excel at "greenfield" tasks, where they don’t need context about existing code and
|
||||
they can start from scratch.
|
||||
Begin with a simple task and iterate from there. Be specific about what you want and the tech stack.
|
||||
|
||||
For example, we might build a TODO app:
|
||||
|
||||
> Please build a basic TODO list app in React. It should be frontend-only, and all state
|
||||
> should be kept in localStorage.
|
||||
> Build a frontend-only TODO app in React. All state should be stored in localStorage.
|
||||
|
||||
We can keep iterating on the app once the skeleton is there:
|
||||
Once the basic structure is in place, continue refining:
|
||||
|
||||
> Please allow adding an optional due date to every task.
|
||||
> Allow adding an optional due date to each task.
|
||||
|
||||
Just like with normal development, it's good to commit and push your code frequently.
|
||||
Just like normal development, commit and push your code often.
|
||||
This way you can always revert back to an old state if the agent goes off track.
|
||||
You can ask the agent to commit and push for you:
|
||||
|
||||
> Please commit the changes and push them to a new branch called "feature/due-dates"
|
||||
|
||||
> Commit the changes and push them to a new branch called "feature/due-dates"
|
||||
|
||||
## Adding New Code
|
||||
|
||||
OpenHands can also do a great job adding new code to an existing code base.
|
||||
OpenHands is great at adding new code to an existing codebase.
|
||||
|
||||
For example, you can ask OpenHands to add a new GitHub action to your project
|
||||
which lints your code. OpenHands may take a peek at your codebase to see what language
|
||||
it should use and then drop a new file into `./github/workflows/lint.yml`.
|
||||
For instance, you can ask OpenHands to add a GitHub action that lints your code. It might check your codebase to
|
||||
determine the language, then create a new file in `./github/workflows/lint.yml`.
|
||||
|
||||
> Please add a GitHub action that lints the code in this repository.
|
||||
> Add a GitHub action that lints the code in this repository.
|
||||
|
||||
Some tasks might require a bit more context. While OpenHands can use `ls` and `grep`
|
||||
to search through your codebase, providing context up front allows it to move faster,
|
||||
and more accurately. And it'll cost you fewer tokens!
|
||||
Some tasks need more context. While OpenHands can use commands like ls and grep to search, providing context upfront
|
||||
speeds things up and reduces token usage.
|
||||
|
||||
> Please modify ./backend/api/routes.js to add a new route that returns a list of all tasks.
|
||||
> Modify ./backend/api/routes.js to add a new route that returns a list of all tasks.
|
||||
|
||||
> Please add a new React component that displays a list of Widgets to the ./frontend/components
|
||||
> directory. It should use the existing Widget component.
|
||||
> Add a new React component to the ./frontend/components directory to display a list of Widgets.
|
||||
> It should use the existing Widget component.
|
||||
|
||||
## Refactoring
|
||||
|
||||
OpenHands does great at refactoring existing code, especially in small chunks.
|
||||
You probably don't want to try rearchitecting your whole codebase, but breaking up
|
||||
long files and functions, renaming variables, etc. tend to work very well.
|
||||
OpenHands does great at refactoring code in small chunks. Rather than rearchitecting the entire codebase,
|
||||
it's more effective to break up long files and functions or rename variables.
|
||||
|
||||
> Please rename all the single-letter variables in ./app.go.
|
||||
> Rename all the single-letter variables in ./app.go.
|
||||
|
||||
> Please break the function `build_and_deploy_widgets` into two functions, `build_widgets` and `deploy_widgets` in widget.php.
|
||||
> Split the `build_and_deploy_widgets` function into two functions, `build_widgets` and `deploy_widgets` in widget.php.
|
||||
|
||||
> Please break ./api/routes.js into separate files for each route.
|
||||
> Break ./api/routes.js into separate files for each route.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
OpenHands can also help you track down and fix bugs in your code. But as any
|
||||
developer knows, bug fixing can be extremely tricky, and often OpenHands will need more context.
|
||||
It helps if you've diagnosed the bug, but want OpenHands to figure out the logic.
|
||||
OpenHands can help track down and fix bugs, but bug fixing can be tricky and often requires more context.
|
||||
It’s helpful if you’ve already diagnosed the issue and just need OpenHands to handle the logic.
|
||||
|
||||
> Currently the email field in the `/subscribe` endpoint is rejecting .io domains. Please fix this.
|
||||
> The email field in the `/subscribe` endpoint is rejecting .io domains. Fix this.
|
||||
|
||||
> The `search_widgets` function in ./app.py is doing a case-sensitive search. Please make it case-insensitive.
|
||||
> The `search_widgets` function in ./app.py is doing a case-sensitive search. Make it case-insensitive.
|
||||
|
||||
It often helps to do test-driven development when bug fixing with an agent.
|
||||
You can ask the agent to write a new test, and then iterate until it fixes the bug:
|
||||
For bug fixing, test-driven development can be really useful. You can ask the agent to write a new test and iterate
|
||||
until the bug is fixed:
|
||||
|
||||
> The `hello` function crashes on the empty string. Please write a test that reproduces this bug, then fix the code so it passes.
|
||||
> The `hello` function crashes on the empty string. Write a test that reproduces this bug, then fix the code so it passes.
|
||||
|
||||
## More
|
||||
|
||||
OpenHands is capable of helping out on just about any coding task but it takes some practice
|
||||
to get the most out of it. Remember to:
|
||||
OpenHands can assist with nearly any coding task, but it takes some practice to get the best results.
|
||||
Keep these tips in mind:
|
||||
* Keep your tasks small.
|
||||
* Be as specific as possible.
|
||||
* Provide as much context as possible.
|
||||
* Be specific.
|
||||
* Provide plenty of context.
|
||||
* Commit and push frequently.
|
||||
|
||||
See [Prompting Best Practices](./prompting/prompting-best-practices) for more tips on how to get the most out of OpenHands.
|
||||
|
||||
@@ -35,7 +35,7 @@ To run OpenHands in CLI mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -45,52 +45,8 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
This command will start an interactive session in Docker where you can input tasks and receive responses from OpenHands.
|
||||
|
||||
## Examples of CLI Commands and Expected Outputs
|
||||
|
||||
Here are some examples of CLI commands and their expected outputs:
|
||||
|
||||
### Example 1: Simple Task
|
||||
|
||||
```bash
|
||||
>> Write a Python script that prints "Hello, World!"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 Sure! Here is a Python script that prints "Hello, World!":
|
||||
|
||||
❯ print("Hello, World!")
|
||||
```
|
||||
|
||||
### Example 2: Bash Command
|
||||
|
||||
```bash
|
||||
>> Create a directory named "test_dir"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 Creating a directory named "test_dir":
|
||||
|
||||
❯ mkdir test_dir
|
||||
```
|
||||
|
||||
### Example 3: Error Handling
|
||||
|
||||
```bash
|
||||
>> Delete a non-existent file
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 An error occurred. Please try again.
|
||||
```
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Custom Sandbox
|
||||
|
||||
:::note
|
||||
This guide is for users that would like to use their own custom Docker image for the runtime, e.g. with certain tools or programming languages pre-installed
|
||||
:::
|
||||
|
||||
The sandbox is where the agent performs its tasks. Instead of running commands directly on your computer
|
||||
(which could be risky), the agent runs them inside a Docker container.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Using the OpenHands GitHub Action
|
||||
|
||||
This guide explains how to use the OpenHands GitHub Action, both within the OpenHands repository and in your own projects.
|
||||
This guide explains how to use the OpenHands GitHub Action in your own projects.
|
||||
|
||||
## Using the Action in the OpenHands Repository
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ OpenHands provides a Graphical User Interface (GUI) mode for interacting with th
|
||||
|
||||
### Initial Setup
|
||||
|
||||
1. Upon first launch, you'll see a settings page.
|
||||
1. Upon first launch, you'll see a settings popup.
|
||||
2. Select an `LLM Provider` and `LLM Model` from the dropdown menus. If the required model does not exist in the list,
|
||||
toggle `Advanced` options and enter it with the correct prefix in the `Custom Model` text box.
|
||||
select `see advanced settings`. Then toggle `Advanced` options and enter it with the correct prefix in the
|
||||
`Custom Model` text box.
|
||||
3. Enter the corresponding `API Key` for your chosen provider.
|
||||
4. Click `Save Changes` to apply the settings.
|
||||
|
||||
@@ -32,7 +33,7 @@ OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if it
|
||||
- `repo` (Full control of private repositories)
|
||||
- **Fine-Grained Tokens**
|
||||
- All Repositories (You can select specific repositories, but this will impact what returns in repo search)
|
||||
- Minimal Permissions ( Select **Meta Data = Read-only** read for search, **Pull Requests = Read and Write**, **Content = Read and Write** for branch creation)
|
||||
- Minimal Permissions ( Select `Meta Data = Read-only` read for search, `Pull Requests = Read and Write` and `Content = Read and Write` for branch creation)
|
||||
2. **Enter Token in OpenHands**:
|
||||
- Click the Settings button (gear icon).
|
||||
- Navigate to the `GitHub Settings` section.
|
||||
|
||||
@@ -32,7 +32,7 @@ To run OpenHands in Headless mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -43,7 +43,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -58,17 +58,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.31-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.32-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.31
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.32
|
||||
```
|
||||
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
@@ -116,8 +116,6 @@ We use SemVer so `0.9` will automatically point to the latest `0.9.x` release, a
|
||||
- For the most up-to-date development version, replace $VERSION in `openhands:$VERSION` and `runtime:$VERSION`, with `main`.
|
||||
This version is unstable and is recommended for testing or development purposes only.
|
||||
|
||||
You can choose the tag that best suits your needs based on stability requirements and desired features.
|
||||
|
||||
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
|
||||
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
|
||||
|
||||
@@ -1,60 +1,28 @@
|
||||
|
||||
# OpenHands Feature Overview
|
||||
|
||||

|
||||

|
||||
|
||||
## 1. Workspace
|
||||
The Workspace feature provides a comprehensive development environment with the following key capabilities:
|
||||
- File Explorer: Browse, view, and manage project files and directories
|
||||
- Project Management: Import, create, and navigate between different projects
|
||||
- Integrated Development Tools: Seamless integration with various development workflows
|
||||
- File Operations:
|
||||
* View file contents
|
||||
* Create new files and folders
|
||||
### Chat Panel
|
||||
- Displays the conversation between the user and OpenHands.
|
||||
- OpenHands explains its actions in this panel.
|
||||
|
||||
### Workspace
|
||||
- Browse project files and directories.
|
||||
- Use the `Open in VS Code` option to:
|
||||
* Modify files
|
||||
* Upload and download files
|
||||
* Basic file manipulation
|
||||
|
||||
## 2. Jupyter Notebook
|
||||
The Jupyter Notebook feature offers an interactive coding and data analysis environment:
|
||||
- Interactive Code Cells: Execute Python code in a cell-based interface
|
||||
- Input and Output Tracking: Maintain a history of code inputs and their corresponding outputs
|
||||
- Persistent Session: Preserve code execution context between cells
|
||||
- Supports various Python operations and data analysis tasks
|
||||
- Real-time code execution and result visualization
|
||||
### Jupyter
|
||||
- Shows all Python commands that were executed by OpenHands.
|
||||
- Particularly handy when using OpenHands to perform data visualization tasks.
|
||||
|
||||
## 3. Browser (Beta)
|
||||
The Browser feature provides web interaction capabilities:
|
||||
- Web Page Navigation: Open and browse websites within the application
|
||||
- Screenshot Capture: Automatically generate screenshots of web pages
|
||||
- Interaction Tools:
|
||||
* Click elements
|
||||
* Fill out forms
|
||||
* Scroll pages
|
||||
* Navigate through web content
|
||||
- Supports 15 different browser interaction functions
|
||||
### App
|
||||
- Shows the web server when OpenHands runs an application.
|
||||
- Users can interact with the running application.
|
||||
|
||||
## 4. Terminal
|
||||
The Terminal feature offers a command-line interface within the application:
|
||||
- Execute Shell Commands: Run bash and system commands
|
||||
- Command History: Track and recall previous commands
|
||||
- Environment Interaction: Interact directly with the system's command line
|
||||
- Support for various programming and system administration tasks
|
||||
### Browser
|
||||
- Used by OpenHands to browse websites.
|
||||
- The browser is non-interactive.
|
||||
|
||||
## 5. Chat / AI Conversation
|
||||
The Chat interface provides an AI-powered conversational experience:
|
||||
- Interactive AI Assistant: Engage in natural language conversations
|
||||
- Context-Aware Responses: AI understands and responds to development-related queries
|
||||
- Action Suggestions: Provides actionable recommendations for tasks
|
||||
- Conversation Management: Create, delete, and manage different conversation threads
|
||||
|
||||
## 6. App (Beta)
|
||||
The main application interface combines all these features:
|
||||
- Integrated Workspace: Seamless integration of workspace, browser, terminal, and AI chat
|
||||
- Configurable Layout: Customize the arrangement of different feature panels
|
||||
- State Management: Maintain context and state across different features
|
||||
- Security and Privacy Controls: Manage application settings and permissions
|
||||
|
||||
### Additional Notes
|
||||
- The application is currently in beta, with ongoing improvements and feature additions
|
||||
- Supports various development workflows and AI-assisted coding
|
||||
- Designed to enhance developer productivity through integrated tools and AI assistance
|
||||
### Terminal
|
||||
- A space for OpenHands and users to run terminal commands.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 🤖 LLM Backends
|
||||
|
||||
:::note
|
||||
This section is for users who want to connect OpenHands to different LLMs.
|
||||
:::
|
||||
|
||||
OpenHands can connect to any LLM supported by LiteLLM. However, it requires a powerful model to work.
|
||||
|
||||
## Model Recommendations
|
||||
@@ -9,10 +13,12 @@ recommendations for model selection. Our latest benchmarking results can be foun
|
||||
|
||||
Based on these findings and community feedback, the following models have been verified to work reasonably well with OpenHands:
|
||||
|
||||
- anthropic/claude-3-7-sonnet-20250219 (recommended)
|
||||
- deepseek/deepseek-chat
|
||||
- OpenHands LM
|
||||
- gpt-4o
|
||||
- [anthropic/claude-3-7-sonnet-20250219](https://www.anthropic.com/api) (recommended)
|
||||
- [gemini/gemini-2.5-pro](https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025/)
|
||||
- [deepseek/deepseek-chat](https://api-docs.deepseek.com/)
|
||||
- [openai/o3-mini](https://openai.com/index/openai-o3-mini/)
|
||||
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) -- available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
|
||||
|
||||
|
||||
:::warning
|
||||
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
# Prompting Best Practices
|
||||
|
||||
When working with OpenHands AI software developer, it's crucial to provide clear and effective prompts. This guide outlines best practices for creating prompts that will yield the most accurate and useful responses.
|
||||
When working with OpenHands AI software developer, providing clear and effective prompts is key to getting accurate
|
||||
and useful responses. This guide outlines best practices for crafting effective prompts.
|
||||
|
||||
## Characteristics of Good Prompts
|
||||
|
||||
Good prompts are:
|
||||
|
||||
- **Concrete**: They explain exactly what functionality should be added or what error needs to be fixed.
|
||||
- **Location-specific**: If known, they explain the locations in the code base that should be modified.
|
||||
- **Appropriately scoped**: They should be the size of a single feature, typically not exceeding 100 lines of code.
|
||||
- **Concrete**: Clearly describe what functionality should be added or what error needs fixing.
|
||||
- **Location-specific**: Specify the locations in the codebase that should be modified, if known.
|
||||
- **Appropriately scoped**: Focus on a single feature, typically not exceeding 100 lines of code.
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Prompt Examples
|
||||
|
||||
- "Add a function `calculate_average` in `utils/math_operations.py` that takes a list of numbers as input and returns their average."
|
||||
- "Fix the TypeError in `frontend/src/components/UserProfile.tsx` occurring on line 42. The error suggests we're trying to access a property of undefined."
|
||||
- "Implement input validation for the email field in the registration form. Update `frontend/src/components/RegistrationForm.tsx` to check if the email is in a valid format before submission."
|
||||
- Add a function `calculate_average` in `utils/math_operations.py` that takes a list of numbers as input and returns their average.
|
||||
- Fix the TypeError in `frontend/src/components/UserProfile.tsx` occurring on line 42. The error suggests we're trying to access a property of undefined.
|
||||
- Implement input validation for the email field in the registration form. Update `frontend/src/components/RegistrationForm.tsx` to check if the email is in a valid format before submission.
|
||||
|
||||
### Bad Prompt Examples
|
||||
|
||||
- "Make the code better." (Too vague, not concrete)
|
||||
- "Rewrite the entire backend to use a different framework." (Not appropriately scoped)
|
||||
- "There's a bug somewhere in the user authentication. Can you find and fix it?" (Lacks specificity and location information)
|
||||
- Make the code better. (Too vague, not concrete)
|
||||
- Rewrite the entire backend to use a different framework. (Not appropriately scoped)
|
||||
- There's a bug somewhere in the user authentication. Can you find and fix it? (Lacks specificity and location information)
|
||||
|
||||
## Tips for Effective Prompting
|
||||
|
||||
- Be as specific as possible about the desired outcome or the problem to be solved.
|
||||
- Provide context, including relevant file paths and line numbers if available.
|
||||
- Break down large tasks into smaller, manageable prompts.
|
||||
- Include any relevant error messages or logs.
|
||||
- Specify the programming language or framework if it's not obvious from the context.
|
||||
- Break large tasks into smaller, manageable prompts.
|
||||
- Include relevant error messages or logs.
|
||||
- Specify the programming language or framework, if not obvious.
|
||||
|
||||
Remember, the more precise and informative your prompt is, the better the AI can assist you in developing or modifying the OpenHands software.
|
||||
The more precise and informative your prompt, the better OpenHands can assist you.
|
||||
|
||||
See [Getting Started with OpenHands](../getting-started) for more examples of helpful prompts.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Runtime Configuration
|
||||
|
||||
:::note
|
||||
This section is for users that would like to use a runtime other than Docker for OpenHands.
|
||||
:::
|
||||
|
||||
A Runtime is an environment where the OpenHands agent can edit files and run
|
||||
commands.
|
||||
|
||||
|
||||
@@ -20,25 +20,3 @@ Try these in order:
|
||||
* If using Docker Desktop, ensure `Settings > Advanced > Allow the default Docker socket to be used` is enabled.
|
||||
* Depending on your configuration you may need `Settings > Resources > Network > Enable host networking` enabled in Docker Desktop.
|
||||
* Reinstall Docker Desktop.
|
||||
---
|
||||
|
||||
# Development Workflow Specific
|
||||
### Error building runtime docker image
|
||||
|
||||
**Description**
|
||||
|
||||
Attempts to start a new session fail, and errors with terms like the following appear in the logs:
|
||||
```
|
||||
debian-security bookworm-security
|
||||
InRelease At least one invalid signature was encountered.
|
||||
```
|
||||
|
||||
This seems to happen when the hash of an existing external library changes and your local docker instance has
|
||||
cached a previous version. To work around this, please try the following:
|
||||
|
||||
* Stop any containers where the name has the prefix `openhands-runtime-` :
|
||||
`docker ps --filter name=openhands-runtime- --filter status=running -aq | xargs docker stop`
|
||||
* Remove any containers where the name has the prefix `openhands-runtime-` :
|
||||
`docker rmi $(docker images --filter name=openhands-runtime- -q --no-trunc)`
|
||||
* Stop and Remove any containers / images where the name has the prefix `openhands-runtime-`
|
||||
* Prune containers / images : `docker container prune -f && docker image prune -f`
|
||||
|
||||
26
docs/package-lock.json
generated
26
docs/package-lock.json
generated
@@ -15,8 +15,8 @@
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-use": "^17.6.0"
|
||||
},
|
||||
@@ -15503,9 +15503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -15638,15 +15638,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.25.0"
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-overlay": {
|
||||
@@ -16417,9 +16417,9 @@
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-use": "^17.6.0"
|
||||
},
|
||||
|
||||
@@ -18,6 +18,23 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'Key Features',
|
||||
id: 'usage/key-features',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'OpenHands Cloud',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Openhands Cloud',
|
||||
id: 'usage/cloud/openhands-cloud',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Cloud GitHub Resolver',
|
||||
id: 'usage/cloud/cloud-github-resolver',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Prompting',
|
||||
@@ -27,6 +44,17 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'Best Practices',
|
||||
id: 'usage/prompting/prompting-best-practices',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Customization',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Repository Customization',
|
||||
id: 'usage/customization/repository',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Microagents',
|
||||
@@ -55,17 +83,6 @@ const sidebars: SidebarsConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Customization',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Repository Customization',
|
||||
id: 'usage/customization/repository',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Usage Methods',
|
||||
@@ -90,23 +107,6 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'Github Action',
|
||||
id: 'usage/how-to/github-action',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Cloud',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Openhands Cloud',
|
||||
id: 'usage/cloud/openhands-cloud',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Cloud GitHub Resolver',
|
||||
id: 'usage/cloud/cloud-github-resolver',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
BIN
docs/static/img/oh-features.png
vendored
Normal file
BIN
docs/static/img/oh-features.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
424
docs/yarn.lock
424
docs/yarn.lock
@@ -84,7 +84,7 @@
|
||||
"@algolia/requester-fetch" "5.20.0"
|
||||
"@algolia/requester-node-http" "5.20.0"
|
||||
|
||||
"@algolia/client-search@>= 4.9.1 < 6", "@algolia/client-search@5.20.0":
|
||||
"@algolia/client-search@5.20.0":
|
||||
version "5.20.0"
|
||||
resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.20.0.tgz"
|
||||
integrity sha512-KL1zWTzrlN4MSiaK1ea560iCA/UewMbS4ZsLQRPoDTWyrbDKVbztkPwwv764LAqgXk0fvkNZvJ3IelcK7DqhjQ==
|
||||
@@ -172,7 +172,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz"
|
||||
integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.21.3", "@babel/core@^7.25.9", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0":
|
||||
"@babel/core@^7.21.3", "@babel/core@^7.25.9":
|
||||
version "7.26.0"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz"
|
||||
integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
|
||||
@@ -1468,7 +1468,7 @@
|
||||
webpack "^5.95.0"
|
||||
webpackbar "^6.0.1"
|
||||
|
||||
"@docusaurus/core@^3.7.0", "@docusaurus/core@3.7.0":
|
||||
"@docusaurus/core@3.7.0", "@docusaurus/core@^3.7.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/core/-/core-3.7.0.tgz"
|
||||
integrity sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==
|
||||
@@ -1564,7 +1564,7 @@
|
||||
vfile "^6.0.1"
|
||||
webpack "^5.88.1"
|
||||
|
||||
"@docusaurus/module-type-aliases@^3.5.1", "@docusaurus/module-type-aliases@3.7.0":
|
||||
"@docusaurus/module-type-aliases@3.7.0", "@docusaurus/module-type-aliases@^3.5.1":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz"
|
||||
integrity sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==
|
||||
@@ -1601,7 +1601,7 @@
|
||||
utility-types "^3.10.0"
|
||||
webpack "^5.88.1"
|
||||
|
||||
"@docusaurus/plugin-content-docs@*", "@docusaurus/plugin-content-docs@3.7.0":
|
||||
"@docusaurus/plugin-content-docs@3.7.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0.tgz"
|
||||
integrity sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==
|
||||
@@ -1624,7 +1624,7 @@
|
||||
utility-types "^3.10.0"
|
||||
webpack "^5.88.1"
|
||||
|
||||
"@docusaurus/plugin-content-pages@^3.7.0", "@docusaurus/plugin-content-pages@3.7.0":
|
||||
"@docusaurus/plugin-content-pages@3.7.0", "@docusaurus/plugin-content-pages@^3.7.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0.tgz"
|
||||
integrity sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==
|
||||
@@ -1828,7 +1828,7 @@
|
||||
resolved "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.7.0.tgz"
|
||||
integrity sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ==
|
||||
|
||||
"@docusaurus/types@^3.5.1", "@docusaurus/types@3.7.0":
|
||||
"@docusaurus/types@3.7.0", "@docusaurus/types@^3.5.1":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz"
|
||||
integrity sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==
|
||||
@@ -2011,7 +2011,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@@ -2145,7 +2145,7 @@
|
||||
"@svgr/babel-plugin-transform-react-native-svg" "8.1.0"
|
||||
"@svgr/babel-plugin-transform-svg-component" "8.0.0"
|
||||
|
||||
"@svgr/core@*", "@svgr/core@8.1.0":
|
||||
"@svgr/core@8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz"
|
||||
integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==
|
||||
@@ -2495,7 +2495,7 @@
|
||||
"@types/history" "^4.7.11"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16":
|
||||
"@types/react@*":
|
||||
version "18.2.79"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz"
|
||||
integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==
|
||||
@@ -2580,7 +2580,7 @@
|
||||
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@webassemblyjs/ast@^1.12.1", "@webassemblyjs/ast@1.12.1":
|
||||
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
|
||||
version "1.12.1"
|
||||
resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz"
|
||||
integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==
|
||||
@@ -2681,7 +2681,7 @@
|
||||
"@webassemblyjs/wasm-gen" "1.12.1"
|
||||
"@webassemblyjs/wasm-parser" "1.12.1"
|
||||
|
||||
"@webassemblyjs/wasm-parser@^1.12.1", "@webassemblyjs/wasm-parser@1.12.1":
|
||||
"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1":
|
||||
version "1.12.1"
|
||||
resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz"
|
||||
integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==
|
||||
@@ -2736,7 +2736,7 @@ acorn-walk@^8.0.0:
|
||||
dependencies:
|
||||
acorn "^8.11.0"
|
||||
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2:
|
||||
acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
@@ -2761,12 +2761,7 @@ ajv-formats@^2.1.1:
|
||||
dependencies:
|
||||
ajv "^8.0.0"
|
||||
|
||||
ajv-keywords@^3.4.1:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
|
||||
ajv-keywords@^3.5.2:
|
||||
ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
@@ -2778,7 +2773,7 @@ ajv-keywords@^5.1.0:
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.3"
|
||||
|
||||
ajv@^6.12.2, ajv@^6.12.5, ajv@^6.9.1:
|
||||
ajv@^6.12.2, ajv@^6.12.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
@@ -2788,7 +2783,7 @@ ajv@^6.12.2, ajv@^6.12.5, ajv@^6.9.1:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.0, ajv@^8.8.2, ajv@^8.9.0:
|
||||
ajv@^8.0.0, ajv@^8.9.0:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz"
|
||||
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
|
||||
@@ -2805,7 +2800,7 @@ algoliasearch-helper@^3.22.6:
|
||||
dependencies:
|
||||
"@algolia/events" "^4.0.1"
|
||||
|
||||
algoliasearch@^5.14.2, algoliasearch@^5.17.1, "algoliasearch@>= 3.1 < 6", "algoliasearch@>= 4.9.1 < 6":
|
||||
algoliasearch@^5.14.2, algoliasearch@^5.17.1:
|
||||
version "5.20.0"
|
||||
resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.20.0.tgz"
|
||||
integrity sha512-groO71Fvi5SWpxjI9Ia+chy0QBwT61mg6yxJV27f5YFf+Mw+STT75K6SHySpP8Co5LsCrtsbCH5dJZSRtkSKaQ==
|
||||
@@ -3060,7 +3055,7 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2:
|
||||
version "4.24.2"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz"
|
||||
integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==
|
||||
@@ -3337,6 +3332,11 @@ comma-separated-tokens@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
|
||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||
|
||||
commander@7, commander@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz"
|
||||
@@ -3352,21 +3352,11 @@ commander@^5.1.0:
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
commander@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
commander@7:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
common-path-prefix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz"
|
||||
@@ -3749,18 +3739,11 @@ cytoscape-cose-bilkent@^4.1.0:
|
||||
dependencies:
|
||||
cose-base "^1.0.0"
|
||||
|
||||
cytoscape@^3.2.0, cytoscape@^3.28.1:
|
||||
cytoscape@^3.28.1:
|
||||
version "3.30.1"
|
||||
resolved "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.30.1.tgz"
|
||||
integrity sha512-TRJc3HbBPkHd50u9YfJh2FxD1lDLZ+JXnJoyBn5LkncoeuT7fapO/Hq/Ed8TdFclaKshzInge2i30bg7VKeoPQ==
|
||||
|
||||
d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
"d3-array@1 - 2":
|
||||
version "2.12.1"
|
||||
resolved "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz"
|
||||
@@ -3768,6 +3751,13 @@ d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3",
|
||||
dependencies:
|
||||
internmap "^1.0.0"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
d3-axis@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz"
|
||||
@@ -3877,16 +3867,16 @@ d3-hierarchy@3:
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-path@1:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-polygon@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz"
|
||||
@@ -3934,13 +3924,6 @@ d3-scale@4:
|
||||
resolved "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz"
|
||||
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||
|
||||
d3-shape@^1.2.0:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz"
|
||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
d3-shape@3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz"
|
||||
@@ -3948,6 +3931,13 @@ d3-shape@3:
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
d3-shape@^1.2.0:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz"
|
||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
"d3-time-format@2 - 4", d3-time-format@4:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz"
|
||||
@@ -4043,27 +4033,20 @@ debounce@^1.2.1:
|
||||
resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz"
|
||||
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
|
||||
|
||||
debug@^2.6.0:
|
||||
debug@2.6.9, debug@^2.6.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@4:
|
||||
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decode-named-character-reference@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz"
|
||||
@@ -4144,16 +4127,16 @@ delaunator@5:
|
||||
dependencies:
|
||||
robust-predicates "^3.0.2"
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"
|
||||
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
||||
|
||||
depd@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"
|
||||
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
||||
|
||||
dequal@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz"
|
||||
@@ -4697,7 +4680,7 @@ figures@^3.2.0:
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
file-loader@*, file-loader@^6.2.0:
|
||||
file-loader@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz"
|
||||
integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
|
||||
@@ -4844,6 +4827,11 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
@@ -4997,16 +4985,16 @@ got@^12.1.0:
|
||||
p-cancelable "^3.0.0"
|
||||
responselike "^3.0.0"
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
graceful-fs@4.2.10:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
gray-matter@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz"
|
||||
@@ -5293,16 +5281,6 @@ http-deceiver@^1.2.7:
|
||||
resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz"
|
||||
integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
|
||||
|
||||
http-errors@~1.6.2:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"
|
||||
integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.0"
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
http-errors@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
|
||||
@@ -5314,6 +5292,16 @@ http-errors@2.0.0:
|
||||
statuses "2.0.1"
|
||||
toidentifier "1.0.1"
|
||||
|
||||
http-errors@~1.6.2:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"
|
||||
integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.0"
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
http-parser-js@>=0.5.1:
|
||||
version "0.5.9"
|
||||
resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz"
|
||||
@@ -5429,7 +5417,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@2, inherits@2.0.4:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@@ -5439,16 +5427,16 @@ inherits@2.0.3:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
ini@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz"
|
||||
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
inline-style-parser@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz"
|
||||
@@ -5466,16 +5454,16 @@ inline-style-prefixer@^7.0.1:
|
||||
dependencies:
|
||||
css-in-js-utils "^3.1.0"
|
||||
|
||||
internmap@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz"
|
||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
internmap@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz"
|
||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
||||
|
||||
interpret@^1.0.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz"
|
||||
@@ -5488,16 +5476,16 @@ invariant@^2.2.4:
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
ipaddr.js@^2.0.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz"
|
||||
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
|
||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||
|
||||
ipaddr.js@^2.0.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz"
|
||||
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
|
||||
|
||||
is-alphabetical@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz"
|
||||
@@ -5668,16 +5656,16 @@ is-yarn-global@^0.4.0:
|
||||
resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz"
|
||||
integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
|
||||
@@ -6894,6 +6882,11 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
"mime-db@>= 1.43.0 < 2":
|
||||
version "1.53.0"
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz"
|
||||
@@ -6904,40 +6897,14 @@ mime-db@~1.33.0:
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz"
|
||||
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.27:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime-types@^2.1.31:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime-types@~2.1.17, mime-types@2.1.18:
|
||||
mime-types@2.1.18, mime-types@~2.1.17:
|
||||
version "2.1.18"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz"
|
||||
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
|
||||
dependencies:
|
||||
mime-db "~1.33.0"
|
||||
|
||||
mime-types@~2.1.24:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime-types@~2.1.34:
|
||||
mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
@@ -6977,7 +6944,7 @@ minimalistic-assert@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@3.1.2:
|
||||
minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
@@ -7041,16 +7008,16 @@ nanoid@^3.3.7:
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
|
||||
negotiator@~0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz"
|
||||
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
negotiator@~0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz"
|
||||
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
|
||||
|
||||
neo-async@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
|
||||
@@ -7387,13 +7354,6 @@ path-parse@^1.0.7:
|
||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-to-regexp@0.1.12:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz"
|
||||
@@ -7404,6 +7364,13 @@ path-to-regexp@3.3.0:
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz"
|
||||
integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
|
||||
@@ -7989,7 +7956,7 @@ postcss-zindex@^6.0.2:
|
||||
resolved "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz"
|
||||
integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==
|
||||
|
||||
"postcss@^7.0.0 || ^8.0.1", postcss@^8, postcss@^8.0.3, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.2, postcss@^8.4, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.31, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.6:
|
||||
postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.33, postcss@^8.4.38:
|
||||
version "8.4.38"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz"
|
||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||
@@ -8107,21 +8074,16 @@ randombytes@^2.1.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
range-parser@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
range-parser@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz"
|
||||
integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
|
||||
|
||||
range-parser@^1.2.1, range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
raw-body@2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz"
|
||||
@@ -8172,12 +8134,12 @@ react-dev-utils@^12.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
react-dom@*, "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18.0.0 || ^19.0.0", react-dom@^19.0.0, "react-dom@>= 16.8.0 < 19.0.0":
|
||||
version "19.0.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz"
|
||||
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
|
||||
react-dom@^19.1.0:
|
||||
version "19.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623"
|
||||
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
|
||||
dependencies:
|
||||
scheduler "^0.25.0"
|
||||
scheduler "^0.26.0"
|
||||
|
||||
react-error-overlay@^6.0.11:
|
||||
version "6.0.11"
|
||||
@@ -8222,7 +8184,7 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.3"
|
||||
|
||||
react-loadable@*, "react-loadable@npm:@docusaurus/react-loadable@6.0.0":
|
||||
"react-loadable@npm:@docusaurus/react-loadable@6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz"
|
||||
integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==
|
||||
@@ -8249,7 +8211,7 @@ react-router-dom@^5.3.4:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@^5.3.4, react-router@>=5, react-router@5.3.4:
|
||||
react-router@5.3.4, react-router@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz"
|
||||
integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
|
||||
@@ -8289,10 +8251,10 @@ react-use@^17.6.0:
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react@*, "react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18.0.0 || ^19.0.0", react@^19.0.0, "react@>= 16.8.0 < 19.0.0", react@>=15, react@>=16, react@>=16.0.0:
|
||||
version "19.0.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.0.0.tgz"
|
||||
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
|
||||
react@^19.1.0:
|
||||
version "19.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
|
||||
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
|
||||
|
||||
readable-stream@^2.0.1:
|
||||
version "2.3.8"
|
||||
@@ -8624,7 +8586,7 @@ sade@^1.7.3:
|
||||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1:
|
||||
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
@@ -8644,30 +8606,21 @@ sax@^1.2.4:
|
||||
resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz"
|
||||
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
|
||||
|
||||
scheduler@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz"
|
||||
integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
|
||||
scheduler@^0.26.0:
|
||||
version "0.26.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
|
||||
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
|
||||
|
||||
schema-utils@^3.0.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz"
|
||||
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
|
||||
schema-utils@2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz"
|
||||
integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.8"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
"@types/json-schema" "^7.0.4"
|
||||
ajv "^6.12.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
schema-utils@^3.1.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz"
|
||||
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.8"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
schema-utils@^3.2.0:
|
||||
schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz"
|
||||
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
|
||||
@@ -8686,25 +8639,11 @@ schema-utils@^4.0.0, schema-utils@^4.0.1:
|
||||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.1.0"
|
||||
|
||||
schema-utils@2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz"
|
||||
integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.4"
|
||||
ajv "^6.12.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
screenfull@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz"
|
||||
integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
|
||||
|
||||
"search-insights@>= 1 < 3":
|
||||
version "2.17.3"
|
||||
resolved "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz"
|
||||
integrity sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==
|
||||
|
||||
section-matter@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz"
|
||||
@@ -8991,12 +8930,12 @@ source-map-support@~0.5.20:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
|
||||
integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==
|
||||
|
||||
source-map@^0.6.1:
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
@@ -9006,16 +8945,6 @@ source-map@^0.7.0:
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz"
|
||||
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
|
||||
|
||||
source-map@~0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
|
||||
integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==
|
||||
|
||||
space-separated-tokens@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
|
||||
@@ -9083,45 +9012,22 @@ stacktrace-js@^2.0.2:
|
||||
stack-generator "^2.0.5"
|
||||
stacktrace-gps "^3.0.4"
|
||||
|
||||
"statuses@>= 1.4.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
|
||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||
|
||||
statuses@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
|
||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||
|
||||
"statuses@>= 1.4.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
|
||||
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
|
||||
|
||||
std-env@^3.7.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz"
|
||||
integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
string-width@^4.1.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.2.0:
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -9139,6 +9045,20 @@ string-width@^5.0.1, string-width@^5.1.2:
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringify-entities@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz"
|
||||
@@ -9352,7 +9272,7 @@ ts-easing@^0.2.0:
|
||||
resolved "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz"
|
||||
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
||||
|
||||
tslib@*, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.6.0:
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.6.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
@@ -9387,7 +9307,7 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
"typescript@>= 2.7", typescript@>=4.9.5, typescript@~5.8.2:
|
||||
typescript@~5.8.2:
|
||||
version "5.8.2"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz"
|
||||
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
|
||||
@@ -9510,7 +9430,7 @@ universalify@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
|
||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||
|
||||
unpipe@~1.0.0, unpipe@1.0.0:
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
|
||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||
@@ -9747,7 +9667,7 @@ webpack-sources@^3.2.3:
|
||||
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
|
||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
|
||||
"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.88.1, webpack@^5.95.0, "webpack@>= 4", "webpack@>=4.41.1 || 5.x", webpack@>=5, "webpack@3 || 4 || 5":
|
||||
webpack@^5.88.1, webpack@^5.95.0:
|
||||
version "5.96.1"
|
||||
resolved "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz"
|
||||
integrity sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==
|
||||
@@ -9790,7 +9710,7 @@ webpackbar@^6.0.1:
|
||||
std-env "^3.7.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
websocket-driver@^0.7.4, websocket-driver@>=0.5.1:
|
||||
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz"
|
||||
integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
|
||||
|
||||
52
evaluation/benchmarks/swe_bench/binary_patch_utils.py
Normal file
52
evaluation/benchmarks/swe_bench/binary_patch_utils.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Utilities for handling binary files and patch generation in SWE-bench evaluation.
|
||||
"""
|
||||
|
||||
|
||||
def remove_binary_diffs(patch_text):
|
||||
"""
|
||||
Remove binary file diffs from a git patch.
|
||||
|
||||
Args:
|
||||
patch_text (str): The git patch text
|
||||
|
||||
Returns:
|
||||
str: The cleaned patch text with binary diffs removed
|
||||
"""
|
||||
lines = patch_text.splitlines()
|
||||
cleaned_lines = []
|
||||
block = []
|
||||
is_binary_block = False
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('diff --git '):
|
||||
if block and not is_binary_block:
|
||||
cleaned_lines.extend(block)
|
||||
block = [line]
|
||||
is_binary_block = False
|
||||
elif 'Binary files' in line:
|
||||
is_binary_block = True
|
||||
block.append(line)
|
||||
else:
|
||||
block.append(line)
|
||||
|
||||
if block and not is_binary_block:
|
||||
cleaned_lines.extend(block)
|
||||
return '\n'.join(cleaned_lines)
|
||||
|
||||
|
||||
def remove_binary_files_from_git():
|
||||
"""
|
||||
Generate a bash command to remove binary files from git staging.
|
||||
|
||||
Returns:
|
||||
str: A bash command that removes binary files from git staging
|
||||
"""
|
||||
return """
|
||||
for file in $(git status --porcelain | grep -E "^(M| M|\\?\\?|A| A)" | cut -c4-); do
|
||||
if [ -f "$file" ] && (file "$file" | grep -q "executable" || git check-attr binary "$file" | grep -q "binary: set"); then
|
||||
git rm -f "$file" 2>/dev/null || rm -f "$file"
|
||||
echo "Removed: $file"
|
||||
fi
|
||||
done
|
||||
""".strip()
|
||||
@@ -28,7 +28,7 @@ def get_resource_mapping(dataset_name: str) -> dict[str, float]:
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
_global_resource_mapping[dataset_name] = json.load(f)
|
||||
logger.info(f'Loaded resource mapping for {dataset_name}')
|
||||
logger.debug(f'Loaded resource mapping for {dataset_name}')
|
||||
return _global_resource_mapping[dataset_name]
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ import toml
|
||||
from datasets import load_dataset
|
||||
|
||||
import openhands.agenthub
|
||||
from evaluation.benchmarks.swe_bench.binary_patch_utils import (
|
||||
remove_binary_diffs,
|
||||
remove_binary_files_from_git,
|
||||
)
|
||||
from evaluation.benchmarks.swe_bench.resource.mapping import (
|
||||
get_instance_resource_factor,
|
||||
)
|
||||
@@ -38,8 +42,12 @@ from openhands.core.config import (
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.critic import AgentFinishedCritic
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation, ErrorObservation
|
||||
from openhands.events.action import CmdRunAction, FileReadAction, MessageAction
|
||||
from openhands.events.observation import (
|
||||
CmdOutputObservation,
|
||||
ErrorObservation,
|
||||
FileReadObservation,
|
||||
)
|
||||
from openhands.events.serialization.event import event_from_dict, event_to_dict
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
@@ -121,7 +129,7 @@ Be thorough in your exploration, testing, and reasoning. It's fine if your think
|
||||
)
|
||||
|
||||
if 'image_assets' in instance:
|
||||
assets = instance['image_assets']
|
||||
assets = json.loads(instance['image_assets'])
|
||||
assert (
|
||||
'problem_statement' in assets
|
||||
), 'problem_statement is required in image_assets'
|
||||
@@ -146,8 +154,8 @@ def get_instance_docker_image(
|
||||
# swebench/sweb.eval.x86_64.django_1776_django-11333:v1
|
||||
docker_image_prefix = 'docker.io/swebench/'
|
||||
repo, name = instance_id.split('__')
|
||||
image_name = f'swebench/sweb.eval.x86_64.{repo}_1776_{name}:latest'
|
||||
logger.info(f'Using official SWE-Bench image: {image_name}')
|
||||
image_name = f'swebench/sweb.eval.x86_64.{repo}_1776_{name}:latest'.lower()
|
||||
logger.debug(f'Using official SWE-Bench image: {image_name}')
|
||||
return image_name
|
||||
else:
|
||||
# OpenHands version of the image
|
||||
@@ -164,10 +172,7 @@ def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_swebench_official_image = bool(
|
||||
('verified' in metadata.dataset.lower() or 'lite' in metadata.dataset.lower())
|
||||
and 'swe-gym' not in metadata.dataset.lower()
|
||||
)
|
||||
use_swebench_official_image = 'swe-gym' not in metadata.dataset.lower()
|
||||
base_container_image = get_instance_docker_image(
|
||||
instance['instance_id'],
|
||||
swebench_official_image=use_swebench_official_image,
|
||||
@@ -229,16 +234,17 @@ def initialize_runtime(
|
||||
workspace_dir_name = _get_swebench_workspace_dir_name(instance)
|
||||
obs: CmdOutputObservation
|
||||
|
||||
# Set instance id
|
||||
# Set instance id and git configuration
|
||||
action = CmdRunAction(
|
||||
command=f"""echo 'export SWE_INSTANCE_ID={instance['instance_id']}' >> ~/.bashrc && echo 'export PIP_CACHE_DIR=~/.cache/pip' >> ~/.bashrc && echo "alias git='git --no-pager'" >> ~/.bashrc"""
|
||||
command=f"""echo 'export SWE_INSTANCE_ID={instance['instance_id']}' >> ~/.bashrc && echo 'export PIP_CACHE_DIR=~/.cache/pip' >> ~/.bashrc && echo "alias git='git --no-pager'" >> ~/.bashrc && git config --global core.pager "" && git config --global diff.binary false"""
|
||||
)
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0, f'Failed to export SWE_INSTANCE_ID: {str(obs)}'
|
||||
obs.exit_code == 0,
|
||||
f'Failed to export SWE_INSTANCE_ID and configure git: {str(obs)}',
|
||||
)
|
||||
|
||||
action = CmdRunAction(command="""export USER=$(whoami); echo USER=${USER} """)
|
||||
@@ -334,15 +340,18 @@ def initialize_runtime(
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to remove git remotes: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='which python')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0 and 'testbed' in obs.content,
|
||||
f'Expected to find python interpreter from testbed, but got: {str(obs)}',
|
||||
)
|
||||
if 'multimodal' not in metadata.dataset.lower():
|
||||
# Only for non-multimodal datasets, we need to activate the testbed environment for Python
|
||||
# SWE-Bench multimodal datasets are not using the testbed environment
|
||||
action = CmdRunAction(command='which python')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0 and 'testbed' in obs.content,
|
||||
f'Expected to find python interpreter from testbed, but got: {str(obs)}',
|
||||
)
|
||||
|
||||
logger.info('-' * 30)
|
||||
logger.info('END Runtime Initialization Fn')
|
||||
@@ -452,11 +461,22 @@ def complete_runtime(
|
||||
f'Failed to git add -A: {str(obs)}',
|
||||
)
|
||||
|
||||
# Remove binary files from git staging
|
||||
action = CmdRunAction(command=remove_binary_files_from_git())
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
isinstance(obs, CmdOutputObservation) and obs.exit_code == 0,
|
||||
f'Failed to remove binary files: {str(obs)}',
|
||||
)
|
||||
|
||||
n_retries = 0
|
||||
git_patch = None
|
||||
while n_retries < 5:
|
||||
action = CmdRunAction(
|
||||
command=f'git diff --no-color --cached {instance["base_commit"]}'
|
||||
command=f'git diff --no-color --cached {instance["base_commit"]} > patch.diff'
|
||||
)
|
||||
action.set_hard_timeout(max(300 + 100 * n_retries, 600))
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
@@ -465,8 +485,28 @@ def complete_runtime(
|
||||
n_retries += 1
|
||||
if isinstance(obs, CmdOutputObservation):
|
||||
if obs.exit_code == 0:
|
||||
git_patch = obs.content.strip()
|
||||
break
|
||||
# Read the patch file
|
||||
action = FileReadAction(path='patch.diff')
|
||||
action.set_hard_timeout(max(300 + 100 * n_retries, 600))
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
if isinstance(obs, FileReadObservation):
|
||||
git_patch = obs.content
|
||||
break
|
||||
elif isinstance(obs, ErrorObservation):
|
||||
# Fall back to cat "patch.diff" to get the patch
|
||||
assert 'File could not be decoded as utf-8' in obs.content
|
||||
action = CmdRunAction(command='cat patch.diff')
|
||||
action.set_hard_timeout(max(300 + 100 * n_retries, 600))
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
assert isinstance(obs, CmdOutputObservation) and obs.exit_code == 0
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
git_patch = obs.content
|
||||
break
|
||||
else:
|
||||
assert_and_raise(False, f'Unexpected observation type: {str(obs)}')
|
||||
else:
|
||||
logger.info('Failed to get git diff, retrying...')
|
||||
sleep_if_should_continue(10)
|
||||
@@ -478,6 +518,9 @@ def complete_runtime(
|
||||
|
||||
assert_and_raise(git_patch is not None, 'Failed to get git diff (None)')
|
||||
|
||||
# Remove binary diffs from the patch
|
||||
git_patch = remove_binary_diffs(git_patch)
|
||||
|
||||
logger.info('-' * 30)
|
||||
logger.info('END Runtime Completion Fn')
|
||||
logger.info('-' * 30)
|
||||
@@ -761,9 +804,19 @@ if __name__ == '__main__':
|
||||
with open(cur_output_file, 'r') as f:
|
||||
for line in f:
|
||||
instance = json.loads(line)
|
||||
history = [event_from_dict(event) for event in instance['history']]
|
||||
critic_result = critic.evaluate(history)
|
||||
if not critic_result.success:
|
||||
try:
|
||||
history = [
|
||||
event_from_dict(event) for event in instance['history']
|
||||
]
|
||||
critic_result = critic.evaluate(
|
||||
history, instance['test_result'].get('git_patch', '')
|
||||
)
|
||||
if not critic_result.success:
|
||||
instances_failed.append(instance['instance_id'])
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f'Error loading history for instance {instance["instance_id"]}: {e}'
|
||||
)
|
||||
instances_failed.append(instance['instance_id'])
|
||||
logger.info(
|
||||
f'{len(instances_failed)} instances failed the current attempt {attempt}: {instances_failed}'
|
||||
@@ -792,7 +845,11 @@ if __name__ == '__main__':
|
||||
with open(cur_output_file, 'r') as f:
|
||||
for line in f:
|
||||
instance = json.loads(line)
|
||||
if instance['instance_id'] not in added_instance_ids:
|
||||
# Also make sure git_patch is not empty - otherwise we fall back to previous attempt (empty patch is worse than anything else)
|
||||
if (
|
||||
instance['instance_id'] not in added_instance_ids
|
||||
and instance['test_result']['git_patch'].strip()
|
||||
):
|
||||
fout.write(line)
|
||||
added_instance_ids.add(instance['instance_id'])
|
||||
logger.info(
|
||||
|
||||
@@ -18,6 +18,7 @@ if [[ -z "$item" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
WORKSPACE_NAME=$(echo "$item" | jq -r '(.repo | tostring) + "__" + (.version | tostring) | gsub("/"; "__")')
|
||||
|
||||
echo "WORKSPACE_NAME: $WORKSPACE_NAME"
|
||||
@@ -36,5 +37,7 @@ mkdir -p /workspace
|
||||
cp -r /testbed /workspace/$WORKSPACE_NAME
|
||||
|
||||
# Activate instance-specific environment
|
||||
. /opt/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate testbed
|
||||
if [ -d /opt/miniconda3 ]; then
|
||||
. /opt/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate testbed
|
||||
fi
|
||||
|
||||
@@ -19,13 +19,13 @@ class Test(BaseIntegrationTest):
|
||||
obs = runtime.run_action(action)
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to run command: {obs.content}')
|
||||
|
||||
# create README.md
|
||||
# create file
|
||||
action = CmdRunAction(command='echo \'print("hello world")\' > hello.py')
|
||||
obs = runtime.run_action(action)
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to run command: {obs.content}')
|
||||
|
||||
# git add README.md
|
||||
action = CmdRunAction(command='git add hello.py')
|
||||
# git add
|
||||
action = CmdRunAction(command='git add hello.py .vscode/')
|
||||
obs = runtime.run_action(action)
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to run command: {obs.content}')
|
||||
|
||||
@@ -40,6 +40,15 @@ 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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
cd frontend
|
||||
lint-staged
|
||||
vitest run
|
||||
npm run check-unlocalized-strings
|
||||
npx lint-staged
|
||||
npm test
|
||||
29
frontend/__tests__/api/file-service/file-service.api.test.ts
Normal file
29
frontend/__tests__/api/file-service/file-service.api.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { FileService } from "#/api/file-service/file-service.api";
|
||||
import {
|
||||
FILE_VARIANTS_1,
|
||||
FILE_VARIANTS_2,
|
||||
} from "#/mocks/file-service-handlers";
|
||||
|
||||
/**
|
||||
* File service API tests. The actual API calls are mocked using MSW.
|
||||
* You can find the mock handlers in `frontend/src/mocks/file-service-handlers.ts`.
|
||||
*/
|
||||
|
||||
describe("FileService", () => {
|
||||
it("should get a list of files", async () => {
|
||||
await expect(FileService.getFiles("test-conversation-id")).resolves.toEqual(
|
||||
FILE_VARIANTS_1,
|
||||
);
|
||||
|
||||
await expect(
|
||||
FileService.getFiles("test-conversation-id-2"),
|
||||
).resolves.toEqual(FILE_VARIANTS_2);
|
||||
});
|
||||
|
||||
it("should get content of a file", async () => {
|
||||
await expect(
|
||||
FileService.getFile("test-conversation-id", "file1.txt"),
|
||||
).resolves.toEqual("Content of file1.txt");
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ vi.mock("react-i18next", async () => {
|
||||
describe("ExpandableMessage", () => {
|
||||
it("should render with neutral border for non-action messages", () => {
|
||||
renderWithProviders(<ExpandableMessage message="Hello" type="thought" />);
|
||||
const element = screen.getByText("Hello");
|
||||
const element = screen.getAllByText("Hello")[0];
|
||||
const container = element.closest(
|
||||
"div.flex.gap-2.items-center.justify-start",
|
||||
);
|
||||
@@ -35,7 +35,7 @@ describe("ExpandableMessage", () => {
|
||||
renderWithProviders(
|
||||
<ExpandableMessage message="Error occurred" type="error" />,
|
||||
);
|
||||
const element = screen.getByText("Error occurred");
|
||||
const element = screen.getAllByText("Error occurred")[0];
|
||||
const container = element.closest(
|
||||
"div.flex.gap-2.items-center.justify-start",
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { it, describe, expect, vi, beforeAll, afterAll } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { WaitlistModal } from "#/components/features/waitlist/waitlist-modal";
|
||||
import { AuthModal } from "#/components/features/waitlist/auth-modal";
|
||||
import * as CaptureConsent from "#/utils/handle-capture-consent";
|
||||
|
||||
describe("WaitlistModal", () => {
|
||||
describe("AuthModal", () => {
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal("location", { href: "" });
|
||||
});
|
||||
@@ -14,7 +14,7 @@ describe("WaitlistModal", () => {
|
||||
});
|
||||
|
||||
it("should render a tos checkbox that is unchecked by default", () => {
|
||||
render(<WaitlistModal ghTokenIsSet={false} githubAuthUrl={null} />);
|
||||
render(<AuthModal githubAuthUrl={null} />);
|
||||
const checkbox = screen.getByRole("checkbox");
|
||||
|
||||
expect(checkbox).not.toBeChecked();
|
||||
@@ -22,7 +22,7 @@ describe("WaitlistModal", () => {
|
||||
|
||||
it("should only enable the GitHub button if the tos checkbox is checked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<WaitlistModal ghTokenIsSet={false} githubAuthUrl={null} />);
|
||||
render(<AuthModal githubAuthUrl={null} />);
|
||||
const checkbox = screen.getByRole("checkbox");
|
||||
const button = screen.getByRole("button", { name: "GITHUB$CONNECT_TO_GITHUB" });
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("WaitlistModal", () => {
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
render(<WaitlistModal ghTokenIsSet={false} githubAuthUrl="mock-url" />);
|
||||
render(<AuthModal githubAuthUrl="mock-url" />);
|
||||
|
||||
const checkbox = screen.getByRole("checkbox");
|
||||
await user.click(checkbox);
|
||||
@@ -1,15 +1,12 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { describe, it, expect, vi, Mock, afterEach } from "vitest";
|
||||
import toast from "#/utils/toast";
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { FileExplorer } from "#/components/features/file-explorer/file-explorer";
|
||||
import { FileService } from "#/api/file-service/file-service.api";
|
||||
|
||||
const toastSpy = vi.spyOn(toast, "error");
|
||||
const uploadFilesSpy = vi.spyOn(OpenHands, "uploadFiles");
|
||||
const getFilesSpy = vi.spyOn(OpenHands, "getFiles");
|
||||
const getFilesSpy = vi.spyOn(FileService, "getFiles");
|
||||
|
||||
vi.mock("../../services/fileService", async () => ({
|
||||
uploadFiles: vi.fn(),
|
||||
@@ -64,41 +61,4 @@ describe.skip("FileExplorer", () => {
|
||||
expect(folder1).toBeInTheDocument();
|
||||
expect(folder1).not.toBeVisible();
|
||||
});
|
||||
|
||||
it("should upload files", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderFileExplorerWithRunningAgentState();
|
||||
|
||||
const file = new File([""], "file-name");
|
||||
const uploadFileInput = await screen.findByTestId("file-input");
|
||||
await user.upload(uploadFileInput, file);
|
||||
|
||||
// TODO: Improve this test by passing expected argument to `uploadFiles`
|
||||
expect(uploadFilesSpy).toHaveBeenCalledOnce();
|
||||
expect(getFilesSpy).toHaveBeenCalled();
|
||||
|
||||
const file2 = new File([""], "file-name-2");
|
||||
const uploadDirInput = await screen.findByTestId("file-input");
|
||||
await user.upload(uploadDirInput, [file, file2]);
|
||||
|
||||
expect(uploadFilesSpy).toHaveBeenCalledTimes(2);
|
||||
expect(getFilesSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should display an error toast if file upload fails", async () => {
|
||||
(uploadFilesSpy as Mock).mockRejectedValue(new Error());
|
||||
const user = userEvent.setup();
|
||||
renderFileExplorerWithRunningAgentState();
|
||||
|
||||
const uploadFileInput = await screen.findByTestId("file-input");
|
||||
const file = new File([""], "test");
|
||||
|
||||
await user.upload(uploadFileInput, file);
|
||||
|
||||
expect(uploadFilesSpy).rejects.toThrow();
|
||||
expect(toastSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("upload-error"),
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,10 @@ import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { vi, describe, afterEach, it, expect } from "vitest";
|
||||
import TreeNode from "#/components/features/file-explorer/tree-node";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { FileService } from "#/api/file-service/file-service.api";
|
||||
|
||||
const getFileSpy = vi.spyOn(OpenHands, "getFile");
|
||||
const getFilesSpy = vi.spyOn(OpenHands, "getFiles");
|
||||
const getFileSpy = vi.spyOn(FileService, "getFile");
|
||||
const getFilesSpy = vi.spyOn(FileService, "getFiles");
|
||||
|
||||
vi.mock("../../services/fileService", async () => ({
|
||||
uploadFile: vi.fn(),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createRoutesStub } from "react-router";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import App from "#/routes/_oh.app/route";
|
||||
import App from "#/routes/conversation";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import * as CustomToast from "#/utils/custom-toast-handlers";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRoutesStub } from "react-router";
|
||||
import { screen, waitFor, within } from "@testing-library/react";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import MainApp from "#/routes/_oh/route";
|
||||
import MainApp from "#/routes/root-layout";
|
||||
import i18n from "#/i18n";
|
||||
import * as CaptureConsent from "#/utils/handle-capture-consent";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
@@ -4,9 +4,9 @@ import { renderWithProviders } from "test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { AxiosError } from "axios";
|
||||
import MainApp from "#/routes/_oh/route";
|
||||
import MainApp from "#/routes/root-layout";
|
||||
import SettingsScreen from "#/routes/settings";
|
||||
import Home from "#/routes/_oh._index/route";
|
||||
import Home from "#/routes/home";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
const createAxiosNotFoundErrorObject = () =>
|
||||
|
||||
@@ -2,10 +2,7 @@ import { render, screen } from "@testing-library/react";
|
||||
import { test, expect, describe, vi } from "vitest";
|
||||
import { InteractiveChatBox } from "#/components/features/chat/interactive-chat-box";
|
||||
import { ChatInput } from "#/components/features/chat/chat-input";
|
||||
import path from 'path';
|
||||
import { scanDirectoryForUnlocalizedStrings } from "#/utils/scan-unlocalized-strings-ast";
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
@@ -15,22 +12,17 @@ vi.mock("react-i18next", () => ({
|
||||
describe("Check for hardcoded English strings", () => {
|
||||
test("InteractiveChatBox should not have hardcoded English strings", () => {
|
||||
const { container } = render(
|
||||
<InteractiveChatBox
|
||||
onSubmit={() => {}}
|
||||
onStop={() => {}}
|
||||
/>
|
||||
<InteractiveChatBox onSubmit={() => {}} onStop={() => {}} />,
|
||||
);
|
||||
|
||||
// Get all text content
|
||||
const text = container.textContent;
|
||||
|
||||
// List of English strings that should be translated
|
||||
const hardcodedStrings = [
|
||||
"What do you want to build?",
|
||||
];
|
||||
const hardcodedStrings = ["What do you want to build?"];
|
||||
|
||||
// Check each string
|
||||
hardcodedStrings.forEach(str => {
|
||||
hardcodedStrings.forEach((str) => {
|
||||
expect(text).not.toContain(str);
|
||||
});
|
||||
});
|
||||
@@ -39,23 +31,4 @@ describe("Check for hardcoded English strings", () => {
|
||||
render(<ChatInput onSubmit={() => {}} />);
|
||||
screen.getByPlaceholderText("SUGGESTIONS$WHAT_TO_BUILD");
|
||||
});
|
||||
|
||||
test("No unlocalized strings should exist in frontend code", () => {
|
||||
const srcPath = path.resolve(__dirname, '../../src');
|
||||
|
||||
// Get unlocalized strings using the AST scanner
|
||||
// The scanner now properly handles CSS classes using AST information
|
||||
const results = scanDirectoryForUnlocalizedStrings(srcPath);
|
||||
|
||||
// If we found any unlocalized strings, format them for output
|
||||
if (results.size > 0) {
|
||||
const formattedResults = Array.from(results.entries())
|
||||
.map(([file, strings]) => `\n${file}:\n ${strings.join('\n ')}`)
|
||||
.join('\n');
|
||||
|
||||
throw new Error(
|
||||
`Found unlocalized strings in the following files:${formattedResults}`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
2441
frontend/package-lock.json
generated
2441
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.31.0",
|
||||
"version": "0.32.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "2.7.5",
|
||||
"@heroui/react": "2.7.6",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.4.0",
|
||||
"@react-router/serve": "^7.4.0",
|
||||
"@react-router/node": "^7.5.0",
|
||||
"@react-router/serve": "^7.5.0",
|
||||
"@react-types/shared": "^3.28.0",
|
||||
"@reduxjs/toolkit": "^2.6.1",
|
||||
"@stripe/react-stripe-js": "^3.5.1",
|
||||
"@stripe/stripe-js": "^6.1.0",
|
||||
"@tanstack/react-query": "^5.69.0",
|
||||
"@stripe/react-stripe-js": "^3.6.0",
|
||||
"@stripe/stripe-js": "^7.0.0",
|
||||
"@tanstack/react-query": "^5.72.1",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.8.4",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.6.2",
|
||||
"framer-motion": "^12.6.3",
|
||||
"i18next": "^24.2.3",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"isbot": "^5.1.25",
|
||||
"jose": "^6.0.10",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.233.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"posthog-js": "^1.235.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.5.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.4.0",
|
||||
"react-router": "^7.5.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.8",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"vite": "^6.2.3",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"vite": "^6.2.5",
|
||||
"web-vitals": "^3.5.2",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
@@ -65,12 +65,8 @@
|
||||
"lint": "eslint src --ext .ts,.tsx,.js && prettier --check src/**/*.{ts,tsx}",
|
||||
"lint:fix": "eslint src --ext .ts,.tsx,.js --fix && prettier --write src/**/*.{ts,tsx}",
|
||||
"prepare": "cd .. && husky frontend/.husky",
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run test && lint-staged"
|
||||
}
|
||||
"typecheck": "react-router typegen && tsc",
|
||||
"check-unlocalized-strings": "node scripts/check-unlocalized-strings.cjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{ts,tsx,js}": [
|
||||
@@ -84,22 +80,22 @@
|
||||
"@babel/types": "^7.27.0",
|
||||
"@mswjs/socket.io-binding": "^0.1.1",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@react-router/dev": "^7.4.0",
|
||||
"@react-router/dev": "^7.5.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.68.0",
|
||||
"@tanstack/eslint-plugin-query": "^5.72.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.1",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.1",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/ws": "^8.18.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitest/coverage-v8": "^3.0.9",
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -108,18 +104,18 @@
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"husky": "^9.1.6",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^26.0.0",
|
||||
"lint-staged": "^15.5.0",
|
||||
"msw": "^2.6.6",
|
||||
"postcss": "^8.5.2",
|
||||
"prettier": "^3.5.3",
|
||||
"stripe": "^17.7.0",
|
||||
"stripe": "^18.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.0.2"
|
||||
|
||||
707
frontend/scripts/check-unlocalized-strings.cjs
Executable file
707
frontend/scripts/check-unlocalized-strings.cjs
Executable file
@@ -0,0 +1,707 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Pre-commit hook script to check for unlocalized strings in the frontend code
|
||||
* This script is based on the test in __tests__/utils/check-hardcoded-strings.test.tsx
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const parser = require('@babel/parser');
|
||||
const traverse = require('@babel/traverse').default;
|
||||
|
||||
// Files/directories to ignore
|
||||
const IGNORE_PATHS = [
|
||||
// Build and dependency files
|
||||
"node_modules",
|
||||
"dist",
|
||||
".git",
|
||||
"test",
|
||||
"__tests__",
|
||||
".d.ts",
|
||||
"i18n",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"tsconfig.json",
|
||||
|
||||
// Internal code that doesn't need localization
|
||||
"mocks", // Mock data
|
||||
"assets", // SVG paths and CSS classes
|
||||
"types", // Type definitions and constants
|
||||
"state", // Redux state management
|
||||
"api", // API endpoints
|
||||
"services", // Internal services
|
||||
"hooks", // React hooks
|
||||
"context", // React context
|
||||
"store", // Redux store
|
||||
"routes.ts", // Route definitions
|
||||
"root.tsx", // Root component
|
||||
"entry.client.tsx", // Client entry point
|
||||
"utils/scan-unlocalized-strings.ts", // Original scanner
|
||||
"utils/scan-unlocalized-strings-ast.ts", // This file itself
|
||||
];
|
||||
|
||||
// Extensions to scan
|
||||
const SCAN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
||||
|
||||
// Attributes that typically don't contain user-facing text
|
||||
const NON_TEXT_ATTRIBUTES = [
|
||||
"className",
|
||||
"testId",
|
||||
"id",
|
||||
"name",
|
||||
"type",
|
||||
"href",
|
||||
"src",
|
||||
"alt",
|
||||
"placeholder",
|
||||
"rel",
|
||||
"target",
|
||||
"style",
|
||||
"onClick",
|
||||
"onChange",
|
||||
"onSubmit",
|
||||
"data-testid",
|
||||
"aria-label",
|
||||
"aria-labelledby",
|
||||
"aria-describedby",
|
||||
"aria-hidden",
|
||||
"role",
|
||||
];
|
||||
|
||||
function shouldIgnorePath(filePath) {
|
||||
return IGNORE_PATHS.some((ignore) => filePath.includes(ignore));
|
||||
}
|
||||
|
||||
// Check if a string looks like a translation key
|
||||
// Translation keys typically use dots, underscores, or are all caps
|
||||
// Also check for the pattern with $ which is used in our translation keys
|
||||
function isLikelyTranslationKey(str) {
|
||||
return (
|
||||
/^[A-Z0-9_$.]+$/.test(str) ||
|
||||
str.includes(".") ||
|
||||
/[A-Z0-9_]+\$[A-Z0-9_]+/.test(str)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if a string is a raw translation key that should be wrapped in t()
|
||||
function isRawTranslationKey(str) {
|
||||
// Check for our specific translation key pattern (e.g., "SETTINGS$GITHUB_SETTINGS")
|
||||
// Exclude specific keys that are already properly used with i18next.t() in the code
|
||||
const excludedKeys = [
|
||||
"STATUS$ERROR_LLM_OUT_OF_CREDITS",
|
||||
"ERROR$GENERIC",
|
||||
"GITHUB$AUTH_SCOPE",
|
||||
];
|
||||
|
||||
if (excludedKeys.includes(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[A-Z0-9_]+\$[A-Z0-9_]+$/.test(str);
|
||||
}
|
||||
|
||||
// Specific technical strings that should be excluded from localization
|
||||
const EXCLUDED_TECHNICAL_STRINGS = [
|
||||
"openid email profile", // OAuth scope string - not user-facing
|
||||
];
|
||||
|
||||
function isExcludedTechnicalString(str) {
|
||||
return EXCLUDED_TECHNICAL_STRINGS.includes(str);
|
||||
}
|
||||
|
||||
function isCommonDevelopmentString(str) {
|
||||
// Technical patterns that are definitely not UI strings
|
||||
const technicalPatterns = [
|
||||
// URLs and paths
|
||||
/^https?:\/\//, // URLs
|
||||
/^\/[a-zA-Z0-9_\-./]*$/, // File paths
|
||||
/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/, // File extensions, class names
|
||||
/^@[a-zA-Z0-9/-]+$/, // Import paths
|
||||
/^#\/[a-zA-Z0-9/-]+$/, // Alias imports
|
||||
/^[a-zA-Z0-9/-]+\/[a-zA-Z0-9/-]+$/, // Module paths
|
||||
/^data:image\/[a-zA-Z0-9;,]+$/, // Data URLs
|
||||
/^application\/[a-zA-Z0-9-]+$/, // MIME types
|
||||
/^!\[image]\(data:image\/png;base64,$/, // Markdown image with base64 data
|
||||
|
||||
// Numbers, IDs, and technical values
|
||||
/^\d+(\.\d+)?$/, // Numbers
|
||||
/^#[0-9a-fA-F]{3,8}$/, // Color codes
|
||||
/^[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+$/, // Key-value pairs
|
||||
/^mm:ss$/, // Time format
|
||||
/^[a-zA-Z0-9]+\/[a-zA-Z0-9-]+$/, // Provider/model format
|
||||
/^\?[a-zA-Z0-9_-]+$/, // URL parameters
|
||||
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i, // UUID
|
||||
/^[A-Za-z0-9+/=]+$/, // Base64
|
||||
|
||||
// HTML and CSS selectors
|
||||
/^[a-z]+(\[[^\]]+\])+$/, // CSS attribute selectors
|
||||
/^[a-z]+:[a-z-]+$/, // CSS pseudo-selectors
|
||||
/^[a-z]+\.[a-z0-9_-]+$/, // CSS class selectors
|
||||
/^[a-z]+#[a-z0-9_-]+$/, // CSS ID selectors
|
||||
/^[a-z]+\s*>\s*[a-z]+$/, // CSS child selectors
|
||||
/^[a-z]+\s+[a-z]+$/, // CSS descendant selectors
|
||||
|
||||
// CSS and styling patterns
|
||||
/^[a-z0-9-]+:[a-z0-9-]+$/, // CSS property:value
|
||||
/^[a-z0-9-]+:[a-z0-9-]+;[a-z0-9-]+:[a-z0-9-]+$/, // Multiple CSS properties
|
||||
];
|
||||
|
||||
// File extensions and media types
|
||||
const fileExtensionPattern =
|
||||
/^\.(png|jpg|jpeg|gif|svg|webp|bmp|ico|pdf|mp4|webm|ogg|mp3|wav|json|xml|csv|txt|md|html|css|js|jsx|ts|tsx)$/i;
|
||||
if (fileExtensionPattern.test(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// AI model and provider patterns
|
||||
const aiRelatedPattern =
|
||||
/^(AI|OpenAI|VertexAI|PaLM|Gemini|Anthropic|Anyscale|Databricks|Ollama|FriendliAI|Groq|DeepInfra|AI21|Replicate|OpenRouter|Azure|AWS|SageMaker|Bedrock|Mistral|Perplexity|Fireworks|Cloudflare|Workers|Voyage|claude-|gpt-|o1-|o3-)/i;
|
||||
if (aiRelatedPattern.test(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CSS units and values
|
||||
const cssUnitsPattern =
|
||||
/(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/;
|
||||
const cssValuesPattern =
|
||||
/(rgb|rgba|hsl|hsla|#[0-9a-fA-F]+|solid|absolute|relative|sticky|fixed|static|block|inline|flex|grid|none|auto|hidden|visible)/;
|
||||
|
||||
if (cssUnitsPattern.test(str) || cssValuesPattern.test(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for CSS class strings with brackets (common in the codebase)
|
||||
if (
|
||||
str.includes("[") &&
|
||||
str.includes("]") &&
|
||||
(str.includes("px") ||
|
||||
str.includes("rem") ||
|
||||
str.includes("em") ||
|
||||
str.includes("w-") ||
|
||||
str.includes("h-") ||
|
||||
str.includes("p-") ||
|
||||
str.includes("m-"))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for CSS class strings with specific patterns
|
||||
if (
|
||||
str.includes("border-") ||
|
||||
str.includes("rounded-") ||
|
||||
str.includes("cursor-") ||
|
||||
str.includes("opacity-") ||
|
||||
str.includes("disabled:") ||
|
||||
str.includes("hover:") ||
|
||||
str.includes("focus-within:") ||
|
||||
str.includes("first-of-type:") ||
|
||||
str.includes("last-of-type:") ||
|
||||
str.includes("group-data-")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it looks like a Tailwind class string
|
||||
if (/^[a-z0-9-]+(\s+[a-z0-9-]+)*$/.test(str)) {
|
||||
// Common Tailwind prefixes and patterns
|
||||
const tailwindPrefixes = [
|
||||
"bg-", "text-", "border-", "rounded-", "p-", "m-", "px-", "py-", "mx-", "my-",
|
||||
"w-", "h-", "min-w-", "min-h-", "max-w-", "max-h-", "flex-", "grid-", "gap-",
|
||||
"space-", "items-", "justify-", "self-", "col-", "row-", "order-", "object-",
|
||||
"overflow-", "opacity-", "z-", "top-", "right-", "bottom-", "left-", "inset-",
|
||||
"font-", "tracking-", "leading-", "list-", "placeholder-", "shadow-", "ring-",
|
||||
"transition-", "duration-", "ease-", "delay-", "animate-", "scale-", "rotate-",
|
||||
"translate-", "skew-", "origin-", "cursor-", "select-", "resize-", "fill-", "stroke-",
|
||||
];
|
||||
|
||||
// Check if any word in the string starts with a Tailwind prefix
|
||||
const words = str.split(/\s+/);
|
||||
for (const word of words) {
|
||||
for (const prefix of tailwindPrefixes) {
|
||||
if (word.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Tailwind modifiers
|
||||
const tailwindModifiers = [
|
||||
"hover:", "focus:", "active:", "disabled:", "visited:", "first:", "last:",
|
||||
"odd:", "even:", "group-hover:", "focus-within:", "focus-visible:", "motion-safe:",
|
||||
"motion-reduce:", "dark:", "light:", "sm:", "md:", "lg:", "xl:", "2xl:",
|
||||
];
|
||||
|
||||
for (const word of words) {
|
||||
for (const modifier of tailwindModifiers) {
|
||||
if (word.includes(modifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for CSS property combinations
|
||||
const cssProperties = [
|
||||
"border", "rounded", "px", "py", "mx", "my", "p", "m", "w", "h", "flex",
|
||||
"grid", "gap", "transition", "duration", "font", "leading", "tracking",
|
||||
];
|
||||
|
||||
// If the string contains multiple CSS properties, it's likely a CSS class string
|
||||
let cssPropertyCount = 0;
|
||||
for (const word of words) {
|
||||
if (
|
||||
cssProperties.some(
|
||||
(prop) => word === prop || word.startsWith(`${prop}-`),
|
||||
)
|
||||
) {
|
||||
cssPropertyCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cssPropertyCount >= 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for specific CSS class patterns that appear in the test failures
|
||||
if (
|
||||
str.match(
|
||||
/^(border|rounded|flex|grid|transition|duration|ease|hover:|focus:|active:|disabled:|placeholder:|text-|bg-|w-|h-|p-|m-|gap-|items-|justify-|self-|overflow-|cursor-|opacity-|z-|top-|right-|bottom-|left-|inset-|font-|tracking-|leading-|whitespace-|break-|truncate|shadow-|ring-|outline-|animate-|transform|rotate-|scale-|skew-|translate-|origin-|first-of-type:|last-of-type:|group-data-|max-|min-|px-|py-|mx-|my-|grow|shrink|resize-|underline|italic|normal)/,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// HTML tags and attributes
|
||||
if (
|
||||
/^<[a-z0-9]+>.*<\/[a-z0-9]+>$/.test(str) ||
|
||||
/^<[a-z0-9]+ [^>]+\/>$/.test(str)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for specific patterns in suggestions and examples
|
||||
if (
|
||||
str.includes("* ") &&
|
||||
(str.includes("create a") ||
|
||||
str.includes("build a") ||
|
||||
str.includes("make a"))
|
||||
) {
|
||||
// This is likely a suggestion or example, not a UI string
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for specific technical identifiers from the test failures
|
||||
if (
|
||||
/^(download_via_vscode_button_clicked|open-vscode-error-|set-indicator|settings_saved|openhands-trace-|provider-item-|last_browser_action_error)$/.test(
|
||||
str,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for URL paths and query parameters
|
||||
if (
|
||||
str.startsWith("?") ||
|
||||
str.startsWith("/") ||
|
||||
str.includes("auth.") ||
|
||||
str.includes("$1auth.")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for specific strings that should be excluded
|
||||
if (
|
||||
str === "Cache Hit:" ||
|
||||
str === "Cache Write:" ||
|
||||
str === "ADD_DOCS" ||
|
||||
str === "ADD_DOCKERFILE" ||
|
||||
str === "Verified" ||
|
||||
str === "Others" ||
|
||||
str === "Feedback" ||
|
||||
str === "JSON File" ||
|
||||
str === "mt-0.5 md:mt-0"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for long suggestion texts
|
||||
if (
|
||||
str.length > 100 &&
|
||||
(str.includes("Please write a bash script") ||
|
||||
str.includes("Please investigate the repo") ||
|
||||
str.includes("Please push the changes") ||
|
||||
str.includes("Examine the dependencies") ||
|
||||
str.includes("Investigate the documentation") ||
|
||||
str.includes("Investigate the current repo") ||
|
||||
str.includes("I want to create a Hello World app") ||
|
||||
str.includes("I want to create a VueJS app") ||
|
||||
str.includes("This should be a client-only app"))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for specific error messages and UI text
|
||||
if (
|
||||
str === "All data associated with this project will be lost." ||
|
||||
str === "You will lose any unsaved information." ||
|
||||
str ===
|
||||
"This conversation does not exist, or you do not have permission to access it." ||
|
||||
str === "Failed to fetch settings. Please try reloading." ||
|
||||
str ===
|
||||
"If you tell OpenHands to start a web server, the app will appear here." ||
|
||||
str ===
|
||||
"Your browser doesn't support downloading files. Please use Chrome, Edge, or another browser that supports the File System Access API." ||
|
||||
str ===
|
||||
"Something went wrong while fetching settings. Please reload the page." ||
|
||||
str ===
|
||||
"To help us improve, we collect feedback from your interactions to improve our prompts. By submitting this form, you consent to us collecting this data." ||
|
||||
str === "Please push the latest changes to the existing pull request."
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check against all technical patterns
|
||||
return technicalPatterns.some((pattern) => pattern.test(str));
|
||||
}
|
||||
|
||||
function isLikelyUserFacingText(str) {
|
||||
// Basic validation - skip very short strings or strings without letters
|
||||
if (!str || str.length <= 2 || !/[a-zA-Z]/.test(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a specifically excluded technical string
|
||||
if (isExcludedTechnicalString(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a raw translation key that should be wrapped in t()
|
||||
if (isRawTranslationKey(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a translation key pattern (e.g., "SETTINGS$BASE_URL")
|
||||
// These should be wrapped in t() or use I18nKey enum
|
||||
if (isLikelyTranslationKey(str) && /^[A-Z0-9_]+\$[A-Z0-9_]+$/.test(str)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// First, check if it's a common development string (not user-facing)
|
||||
if (isCommonDevelopmentString(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Multi-word phrases are likely UI text
|
||||
const hasMultipleWords = /\s+/.test(str) && str.split(/\s+/).length > 1;
|
||||
|
||||
// Sentences and questions are likely UI text
|
||||
const hasPunctuation = /[?!.,:]/.test(str);
|
||||
const isCapitalizedPhrase = /^[A-Z]/.test(str) && hasMultipleWords;
|
||||
const isTitleCase = hasMultipleWords && /\s[A-Z]/.test(str);
|
||||
const hasSentenceStructure = /^[A-Z].*[.!?]$/.test(str); // Starts with capital, ends with punctuation
|
||||
const hasQuestionForm =
|
||||
/^(What|How|Why|When|Where|Who|Can|Could|Would|Will|Is|Are|Do|Does|Did|Should|May|Might)/.test(
|
||||
str,
|
||||
);
|
||||
|
||||
// Product names and camelCase identifiers are likely UI text
|
||||
const hasInternalCapitals = /[a-z][A-Z]/.test(str); // CamelCase product names
|
||||
|
||||
// Instruction text patterns are likely UI text
|
||||
const looksLikeInstruction =
|
||||
/^(Enter|Type|Select|Choose|Provide|Specify|Search|Find|Input|Add|Write|Describe|Set|Pick|Browse|Upload|Download|Click|Tap|Press|Go to|Visit|Open|Close)/i.test(
|
||||
str,
|
||||
);
|
||||
|
||||
// Error and status messages are likely UI text
|
||||
const looksLikeErrorOrStatus =
|
||||
/(failed|error|invalid|required|missing|incorrect|wrong|unavailable|not found|not available|try again|success|completed|finished|done|saved|updated|created|deleted|removed|added)/i.test(
|
||||
str,
|
||||
);
|
||||
|
||||
// Single word check - assume it's UI text unless proven otherwise
|
||||
const isSingleWord =
|
||||
!str.includes(" ") && str.length > 1 && /^[a-zA-Z]+$/.test(str);
|
||||
|
||||
// For single words, we need to be more careful
|
||||
if (isSingleWord) {
|
||||
// Skip common programming terms and variable names
|
||||
const isCommonProgrammingTerm =
|
||||
/^(null|undefined|true|false|function|class|interface|type|enum|const|let|var|return|import|export|default|async|await|try|catch|finally|throw|new|this|super|extends|implements|instanceof|typeof|void|delete|in|of|for|while|do|if|else|switch|case|break|continue|yield|static|get|set|public|private|protected|readonly|abstract|implements|namespace|module|declare|as|from|with)$/i.test(
|
||||
str,
|
||||
);
|
||||
|
||||
if (isCommonProgrammingTerm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip common variable name patterns
|
||||
const looksLikeVariableName =
|
||||
/^[a-z][a-zA-Z0-9]*$/.test(str) && str.length <= 20;
|
||||
|
||||
if (looksLikeVariableName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip common CSS values
|
||||
const isCommonCssValue =
|
||||
/^(auto|none|hidden|visible|block|inline|flex|grid|row|column|wrap|nowrap|center|start|end|stretch|cover|contain|fixed|absolute|relative|static|sticky|pointer|default|inherit|initial|unset)$/i.test(
|
||||
str,
|
||||
);
|
||||
|
||||
if (isCommonCssValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip common file extensions
|
||||
const isFileExtension = /^\.[a-z0-9]+$/i.test(str);
|
||||
if (isFileExtension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip common abbreviations
|
||||
const isCommonAbbreviation =
|
||||
/^(id|src|href|url|alt|img|btn|nav|div|span|ul|li|ol|dl|dt|dd|svg|png|jpg|gif|pdf|doc|txt|md|js|ts|jsx|tsx|css|scss|less|html|xml|json|yaml|yml|toml|csv|mp3|mp4|wav|avi|mov|mpeg|webm|webp|ttf|woff|eot|otf)$/i.test(
|
||||
str,
|
||||
);
|
||||
|
||||
if (isCommonAbbreviation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it's a single word that's not a programming term, variable name, CSS value, file extension, or abbreviation,
|
||||
// it might be UI text, but we'll be conservative and return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it has multiple words, punctuation, or looks like a sentence, it's likely UI text
|
||||
return (
|
||||
hasMultipleWords ||
|
||||
hasPunctuation ||
|
||||
isCapitalizedPhrase ||
|
||||
isTitleCase ||
|
||||
hasSentenceStructure ||
|
||||
hasQuestionForm ||
|
||||
hasInternalCapitals ||
|
||||
looksLikeInstruction ||
|
||||
looksLikeErrorOrStatus
|
||||
);
|
||||
}
|
||||
|
||||
function isInTranslationContext(path) {
|
||||
// Check if the JSX text is inside a <Trans> component
|
||||
let current = path;
|
||||
while (current.parentPath) {
|
||||
if (
|
||||
current.isJSXElement() &&
|
||||
current.node.openingElement &&
|
||||
current.node.openingElement.name &&
|
||||
current.node.openingElement.name.name === "Trans"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
current = current.parentPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function scanFileForUnlocalizedStrings(filePath) {
|
||||
// Skip all suggestion files as they contain special strings
|
||||
if (filePath.includes("suggestions")) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const unlocalizedStrings = [];
|
||||
|
||||
// Skip files that are too large
|
||||
if (content.length > 1000000) {
|
||||
console.warn(`Skipping large file: ${filePath}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the file
|
||||
const ast = parser.parse(content, {
|
||||
sourceType: "module",
|
||||
plugins: ["jsx", "typescript", "classProperties", "decorators-legacy"],
|
||||
});
|
||||
|
||||
// Traverse the AST
|
||||
traverse(ast, {
|
||||
// Find JSX text content
|
||||
JSXText(jsxTextPath) {
|
||||
const text = jsxTextPath.node.value.trim();
|
||||
if (
|
||||
text &&
|
||||
isLikelyUserFacingText(text) &&
|
||||
!isInTranslationContext(jsxTextPath)
|
||||
) {
|
||||
unlocalizedStrings.push(text);
|
||||
}
|
||||
},
|
||||
|
||||
// Find string literals in JSX attributes
|
||||
JSXAttribute(jsxAttrPath) {
|
||||
const attrName = jsxAttrPath.node.name.name.toString();
|
||||
|
||||
// Skip technical attributes that don't contain user-facing text
|
||||
if (NON_TEXT_ATTRIBUTES.includes(attrName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip styling attributes
|
||||
if (
|
||||
attrName === "className" ||
|
||||
attrName === "class" ||
|
||||
attrName === "style"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip data attributes and event handlers
|
||||
if (attrName.startsWith("data-") || attrName.startsWith("on")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the attribute value
|
||||
const value = jsxAttrPath.node.value;
|
||||
if (value && value.type === "StringLiteral") {
|
||||
const text = value.value.trim();
|
||||
if (text && isLikelyUserFacingText(text)) {
|
||||
unlocalizedStrings.push(text);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Find string literals in code
|
||||
StringLiteral(stringPath) {
|
||||
// Skip if parent is JSX attribute (already handled above)
|
||||
if (stringPath.parent.type === "JSXAttribute") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if parent is import/export declaration
|
||||
if (
|
||||
stringPath.parent.type === "ImportDeclaration" ||
|
||||
stringPath.parent.type === "ExportDeclaration"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if parent is object property key
|
||||
if (
|
||||
stringPath.parent.type === "ObjectProperty" &&
|
||||
stringPath.parent.key === stringPath.node
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if inside a t() call or Trans component
|
||||
let isInsideTranslation = false;
|
||||
let current = stringPath;
|
||||
|
||||
while (current.parentPath && !isInsideTranslation) {
|
||||
// Check for t() function call
|
||||
if (
|
||||
current.parent.type === "CallExpression" &&
|
||||
current.parent.callee &&
|
||||
((current.parent.callee.type === "Identifier" &&
|
||||
current.parent.callee.name === "t") ||
|
||||
(current.parent.callee.type === "MemberExpression" &&
|
||||
current.parent.callee.property &&
|
||||
current.parent.callee.property.name === "t"))
|
||||
) {
|
||||
isInsideTranslation = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for <Trans> component
|
||||
if (
|
||||
current.parent.type === "JSXElement" &&
|
||||
current.parent.openingElement &&
|
||||
current.parent.openingElement.name &&
|
||||
current.parent.openingElement.name.name === "Trans"
|
||||
) {
|
||||
isInsideTranslation = true;
|
||||
break;
|
||||
}
|
||||
|
||||
current = current.parentPath;
|
||||
}
|
||||
|
||||
if (!isInsideTranslation) {
|
||||
const text = stringPath.node.value.trim();
|
||||
if (text && isLikelyUserFacingText(text)) {
|
||||
unlocalizedStrings.push(text);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return unlocalizedStrings;
|
||||
} catch (error) {
|
||||
console.error(`Error parsing file ${filePath}:`, error);
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading file ${filePath}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function scanDirectoryForUnlocalizedStrings(dirPath) {
|
||||
const results = new Map();
|
||||
|
||||
function scanDir(currentPath) {
|
||||
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentPath, entry.name);
|
||||
|
||||
if (!shouldIgnorePath(fullPath)) {
|
||||
if (entry.isDirectory()) {
|
||||
scanDir(fullPath);
|
||||
} else if (
|
||||
entry.isFile() &&
|
||||
SCAN_EXTENSIONS.includes(path.extname(fullPath))
|
||||
) {
|
||||
const unlocalized = scanFileForUnlocalizedStrings(fullPath);
|
||||
if (unlocalized.length > 0) {
|
||||
results.set(fullPath, unlocalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanDir(dirPath);
|
||||
return results;
|
||||
}
|
||||
|
||||
// Run the check
|
||||
try {
|
||||
const srcPath = path.resolve(__dirname, '../src');
|
||||
console.log('Checking for unlocalized strings in frontend code...');
|
||||
|
||||
// Get unlocalized strings using the AST scanner
|
||||
const results = scanDirectoryForUnlocalizedStrings(srcPath);
|
||||
|
||||
// If we found any unlocalized strings, format them for output and exit with error
|
||||
if (results.size > 0) {
|
||||
const formattedResults = Array.from(results.entries())
|
||||
.map(([file, strings]) => `\n${file}:\n ${strings.join('\n ')}`)
|
||||
.join('\n');
|
||||
|
||||
console.error(`Error: Found unlocalized strings in the following files:${formattedResults}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ No unlocalized strings found in frontend code.');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error running unlocalized strings check:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
38
frontend/src/api/file-service/file-service.api.ts
Normal file
38
frontend/src/api/file-service/file-service.api.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { openHands } from "../open-hands-axios";
|
||||
import { GetFilesResponse, GetFileResponse } from "./file-service.types";
|
||||
import { getConversationUrl } from "./file-service.utils";
|
||||
|
||||
export class FileService {
|
||||
/**
|
||||
* Retrieve the list of files available in the workspace
|
||||
* @param conversationId ID of the conversation
|
||||
* @param path Path to list files from. If provided, it lists all the files in the given path
|
||||
* @returns List of files available in the given path. If path is not provided, it lists all the files in the workspace
|
||||
*/
|
||||
static async getFiles(
|
||||
conversationId: string,
|
||||
path?: string,
|
||||
): Promise<GetFilesResponse> {
|
||||
const url = `${getConversationUrl(conversationId)}/list-files`;
|
||||
const { data } = await openHands.get<GetFilesResponse>(url, {
|
||||
params: { path },
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the content of a file
|
||||
* @param conversationId ID of the conversation
|
||||
* @param path Full path of the file to retrieve
|
||||
* @returns Code content of the file
|
||||
*/
|
||||
static async getFile(conversationId: string, path: string): Promise<string> {
|
||||
const url = `${getConversationUrl(conversationId)}/select-file`;
|
||||
const { data } = await openHands.get<GetFileResponse>(url, {
|
||||
params: { file: path },
|
||||
});
|
||||
|
||||
return data.code;
|
||||
}
|
||||
}
|
||||
5
frontend/src/api/file-service/file-service.types.ts
Normal file
5
frontend/src/api/file-service/file-service.types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type GetFilesResponse = string[];
|
||||
|
||||
export interface GetFileResponse {
|
||||
code: string;
|
||||
}
|
||||
7
frontend/src/api/file-service/file-service.utils.ts
Normal file
7
frontend/src/api/file-service/file-service.utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Returns a URL compatible for the file service
|
||||
* @param conversationId ID of the conversation
|
||||
* @returns URL of the conversation
|
||||
*/
|
||||
export const getConversationUrl = (conversationId: string) =>
|
||||
`/api/conversations/${conversationId}`;
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
SaveFileSuccessResponse,
|
||||
FileUploadSuccessResponse,
|
||||
Feedback,
|
||||
FeedbackResponse,
|
||||
GitHubAccessTokenResponse,
|
||||
ErrorResponse,
|
||||
GetConfigResponse,
|
||||
GetVSCodeUrlResponse,
|
||||
AuthenticateResponse,
|
||||
@@ -53,80 +50,6 @@ class OpenHands {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of files available in the workspace
|
||||
* @param path Path to list files from
|
||||
* @returns List of files available in the given path. If path is not provided, it lists all the files in the workspace
|
||||
*/
|
||||
static async getFiles(
|
||||
conversationId: string,
|
||||
path?: string,
|
||||
): Promise<string[]> {
|
||||
const url = `/api/conversations/${conversationId}/list-files`;
|
||||
const { data } = await openHands.get<string[]>(url, {
|
||||
params: { path },
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the content of a file
|
||||
* @param path Full path of the file to retrieve
|
||||
* @returns Content of the file
|
||||
*/
|
||||
static async getFile(conversationId: string, path: string): Promise<string> {
|
||||
const url = `/api/conversations/${conversationId}/select-file`;
|
||||
const { data } = await openHands.get<{ code: string }>(url, {
|
||||
params: { file: path },
|
||||
});
|
||||
|
||||
return data.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the content of a file
|
||||
* @param path Full path of the file to save
|
||||
* @param content Content to save in the file
|
||||
* @returns Success message or error message
|
||||
*/
|
||||
static async saveFile(
|
||||
conversationId: string,
|
||||
path: string,
|
||||
content: string,
|
||||
): Promise<SaveFileSuccessResponse> {
|
||||
const url = `/api/conversations/${conversationId}/save-file`;
|
||||
const { data } = await openHands.post<
|
||||
SaveFileSuccessResponse | ErrorResponse
|
||||
>(url, {
|
||||
filePath: path,
|
||||
content,
|
||||
});
|
||||
|
||||
if ("error" in data) throw new Error(data.error);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to the workspace
|
||||
* @param file File to upload
|
||||
* @returns Success message or error message
|
||||
*/
|
||||
static async uploadFiles(
|
||||
conversationId: string,
|
||||
files: File[],
|
||||
): Promise<FileUploadSuccessResponse> {
|
||||
const url = `/api/conversations/${conversationId}/upload-files`;
|
||||
const formData = new FormData();
|
||||
files.forEach((file) => formData.append("files", file));
|
||||
|
||||
const { data } = await openHands.post<
|
||||
FileUploadSuccessResponse | ErrorResponse
|
||||
>(url, formData);
|
||||
|
||||
if ("error" in data) throw new Error(data.error);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send feedback to the server
|
||||
* @param data Feedback data
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ChatMessage({
|
||||
onClick={handleCopyToClipboard}
|
||||
mode={isCopy ? "copied" : "copy"}
|
||||
/>
|
||||
<div className="text-sm overflow-auto break-words">
|
||||
<div className="text-sm break-words">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Link } from "react-router";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
import CheckCircle from "#/icons/check-circle-solid.svg?react";
|
||||
import XCircle from "#/icons/x-circle-solid.svg?react";
|
||||
import { OpenHandsAction } from "#/types/core/actions";
|
||||
import { OpenHandsObservation } from "#/types/core/observations";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import { MonoComponent } from "./mono-component";
|
||||
import { PathComponent } from "./path-component";
|
||||
|
||||
const trimText = (text: string, maxLength: number): string => {
|
||||
if (!text) return "";
|
||||
return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text;
|
||||
};
|
||||
|
||||
interface ExpandableMessageProps {
|
||||
id?: string;
|
||||
message: string;
|
||||
type: string;
|
||||
success?: boolean;
|
||||
observation?: PayloadAction<OpenHandsObservation>;
|
||||
action?: PayloadAction<OpenHandsAction>;
|
||||
}
|
||||
|
||||
export function ExpandableMessage({
|
||||
@@ -25,20 +37,63 @@ export function ExpandableMessage({
|
||||
message,
|
||||
type,
|
||||
success,
|
||||
observation,
|
||||
action,
|
||||
}: ExpandableMessageProps) {
|
||||
const { data: config } = useConfig();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(true);
|
||||
const [headline, setHeadline] = useState("");
|
||||
const [details, setDetails] = useState(message);
|
||||
const [translationId, setTranslationId] = useState<string | undefined>(id);
|
||||
const [translationParams, setTranslationParams] = useState<
|
||||
Record<string, unknown>
|
||||
>({
|
||||
observation,
|
||||
action,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (id && i18n.exists(id)) {
|
||||
setHeadline(t(id));
|
||||
let processedObservation = observation;
|
||||
let processedAction = action;
|
||||
|
||||
if (action && action.payload.action === "run") {
|
||||
const trimmedCommand = trimText(action.payload.args.command, 80);
|
||||
processedAction = {
|
||||
...action,
|
||||
payload: {
|
||||
...action.payload,
|
||||
args: {
|
||||
...action.payload.args,
|
||||
command: trimmedCommand,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (observation && observation.payload.observation === "run") {
|
||||
const trimmedCommand = trimText(observation.payload.extras.command, 80);
|
||||
processedObservation = {
|
||||
...observation,
|
||||
payload: {
|
||||
...observation.payload,
|
||||
extras: {
|
||||
...observation.payload.extras,
|
||||
command: trimmedCommand,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setTranslationId(id);
|
||||
setTranslationParams({
|
||||
observation: processedObservation,
|
||||
action: processedAction,
|
||||
});
|
||||
setDetails(message);
|
||||
setShowDetails(false);
|
||||
}
|
||||
}, [id, message, i18n.language]);
|
||||
}, [id, message, observation, action, i18n.language]);
|
||||
|
||||
const statusIconClasses = "h-4 w-4 ml-2 inline";
|
||||
|
||||
@@ -78,36 +133,44 @@ export function ExpandableMessage({
|
||||
<div className="flex flex-row justify-between items-center w-full">
|
||||
<span
|
||||
className={cn(
|
||||
headline ? "font-bold" : "",
|
||||
"font-bold",
|
||||
type === "error" ? "text-danger" : "text-neutral-300",
|
||||
)}
|
||||
>
|
||||
{headline && (
|
||||
<>
|
||||
{headline}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
className="cursor-pointer text-left"
|
||||
>
|
||||
{showDetails ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
{translationId && i18n.exists(translationId) ? (
|
||||
<Trans
|
||||
i18nKey={translationId}
|
||||
values={translationParams}
|
||||
components={{
|
||||
bold: <strong />,
|
||||
path: <PathComponent />,
|
||||
cmd: <MonoComponent />,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
message
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
className="cursor-pointer text-left"
|
||||
>
|
||||
{showDetails ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
{type === "action" && success !== undefined && (
|
||||
<span className="flex-shrink-0">
|
||||
@@ -125,8 +188,8 @@ export function ExpandableMessage({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(!headline || showDetails) && (
|
||||
<div className="text-sm overflow-auto">
|
||||
{showDetails && (
|
||||
<div className="text-sm">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
|
||||
@@ -26,6 +26,8 @@ export const Messages: React.FC<MessagesProps> = React.memo(
|
||||
id={message.translationID}
|
||||
message={message.content}
|
||||
success={message.success}
|
||||
observation={message.observation}
|
||||
action={message.action}
|
||||
/>
|
||||
{shouldShowConfirmationButtons && <ConfirmationButtons />}
|
||||
</div>
|
||||
|
||||
37
frontend/src/components/features/chat/mono-component.tsx
Normal file
37
frontend/src/components/features/chat/mono-component.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ReactNode } from "react";
|
||||
import EventLogger from "#/utils/event-logger";
|
||||
|
||||
const decodeHtmlEntities = (text: string): string => {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
};
|
||||
|
||||
function MonoComponent(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
|
||||
const decodeString = (str: string): string => {
|
||||
try {
|
||||
return decodeHtmlEntities(str);
|
||||
} catch (e) {
|
||||
EventLogger.error(String(e));
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
const processedChildren = children.map((child) =>
|
||||
typeof child === "string" ? decodeString(child) : child,
|
||||
);
|
||||
|
||||
return <strong className="font-mono">{processedChildren}</strong>;
|
||||
}
|
||||
|
||||
if (typeof children === "string") {
|
||||
return <strong className="font-mono">{decodeString(children)}</strong>;
|
||||
}
|
||||
|
||||
return <strong className="font-mono">{children}</strong>;
|
||||
}
|
||||
|
||||
export { MonoComponent };
|
||||
67
frontend/src/components/features/chat/path-component.tsx
Normal file
67
frontend/src/components/features/chat/path-component.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ReactNode } from "react";
|
||||
import EventLogger from "#/utils/event-logger";
|
||||
|
||||
/**
|
||||
* Decodes HTML entities in a string
|
||||
* @param text The text to decode
|
||||
* @returns The decoded text
|
||||
*/
|
||||
const decodeHtmlEntities = (text: string): string => {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the filename from a path
|
||||
* @param path The full path
|
||||
* @returns The filename (last part of the path)
|
||||
*/
|
||||
const extractFilename = (path: string): string => {
|
||||
if (!path) return "";
|
||||
// Handle both Unix and Windows paths
|
||||
const parts = path.split(/[/\\]/);
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that displays only the filename in the text but shows the full path on hover
|
||||
* Similar to MonoComponent but with path-specific functionality
|
||||
*/
|
||||
function PathComponent(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
|
||||
const processPath = (path: string) => {
|
||||
try {
|
||||
// First decode any HTML entities in the path
|
||||
const decodedPath = decodeHtmlEntities(path);
|
||||
// Extract the filename from the decoded path
|
||||
const filename = extractFilename(decodedPath);
|
||||
return (
|
||||
<span className="font-mono" title={decodedPath}>
|
||||
{filename}
|
||||
</span>
|
||||
);
|
||||
} catch (e) {
|
||||
// Just log the error without any message to avoid localization issues
|
||||
EventLogger.error(String(e));
|
||||
return <span className="font-mono">{path}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
const processedChildren = children.map((child) =>
|
||||
typeof child === "string" ? processPath(child) : child,
|
||||
);
|
||||
|
||||
return <strong className="font-mono">{processedChildren}</strong>;
|
||||
}
|
||||
|
||||
if (typeof children === "string") {
|
||||
return <strong>{processPath(children)}</strong>;
|
||||
}
|
||||
|
||||
return <strong className="font-mono">{children}</strong>;
|
||||
}
|
||||
|
||||
export { PathComponent };
|
||||
@@ -2,8 +2,6 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import AllHandsLogo from "#/assets/branding/all-hands-logo.svg?react";
|
||||
import { JoinWaitlistAnchor } from "./join-waitlist-anchor";
|
||||
import { WaitlistMessage } from "./waitlist-message";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { TOSCheckbox } from "./tos-checkbox";
|
||||
@@ -11,15 +9,11 @@ import { handleCaptureConsent } from "#/utils/handle-capture-consent";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import GitHubLogo from "#/assets/branding/github-logo.svg?react";
|
||||
|
||||
interface WaitlistModalProps {
|
||||
ghTokenIsSet: boolean;
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
}
|
||||
|
||||
export function WaitlistModal({
|
||||
ghTokenIsSet,
|
||||
githubAuthUrl,
|
||||
}: WaitlistModalProps) {
|
||||
export function AuthModal({ githubAuthUrl }: AuthModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isTosAccepted, setIsTosAccepted] = React.useState(false);
|
||||
|
||||
@@ -34,23 +28,24 @@ export function WaitlistModal({
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="border border-tertiary">
|
||||
<AllHandsLogo width={68} height={46} />
|
||||
<WaitlistMessage content={ghTokenIsSet ? "waitlist" : "sign-in"} />
|
||||
<div className="flex flex-col gap-2 w-full items-center text-center">
|
||||
<h1 className="text-2xl font-bold">
|
||||
{t(I18nKey.AUTH$SIGN_IN_WITH_GITHUB)}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<TOSCheckbox onChange={() => setIsTosAccepted((prev) => !prev)} />
|
||||
|
||||
{!ghTokenIsSet && (
|
||||
<BrandButton
|
||||
isDisabled={!isTosAccepted}
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitHubAuth}
|
||||
className="w-full"
|
||||
startContent={<GitHubLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITHUB$CONNECT_TO_GITHUB)}
|
||||
</BrandButton>
|
||||
)}
|
||||
{ghTokenIsSet && <JoinWaitlistAnchor />}
|
||||
<BrandButton
|
||||
isDisabled={!isTosAccepted}
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleGitHubAuth}
|
||||
className="w-full"
|
||||
startContent={<GitHubLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.GITHUB$CONNECT_TO_GITHUB)}
|
||||
</BrandButton>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export function JoinWaitlistAnchor() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<a
|
||||
href="https://www.all-hands.dev/join-waitlist"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="rounded bg-[#FFE165] text-black text-sm font-bold py-[10px] w-full text-center hover:opacity-80"
|
||||
>
|
||||
{t(I18nKey.WAITLIST$JOIN_WAITLIST)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface WaitlistMessageProps {
|
||||
content: "waitlist" | "sign-in";
|
||||
}
|
||||
|
||||
export function WaitlistMessage({ content }: WaitlistMessageProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full items-center text-center">
|
||||
<h1 className="text-2xl font-bold">
|
||||
{content === "sign-in" && t(I18nKey.AUTH$SIGN_IN_WITH_GITHUB)}
|
||||
{content === "waitlist" && t(I18nKey.WAITLIST$ALMOST_THERE)}
|
||||
</h1>
|
||||
{content === "sign-in" && (
|
||||
<p>
|
||||
{t(I18nKey.LANDING$OR)}{" "}
|
||||
<a
|
||||
href="https://www.all-hands.dev/join-waitlist"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="text-blue-500 hover:underline underline-offset-2"
|
||||
>
|
||||
{t(I18nKey.WAITLIST$JOIN)}
|
||||
</a>{" "}
|
||||
{t(I18nKey.WAITLIST$IF_NOT_JOINED)}
|
||||
</p>
|
||||
)}
|
||||
{content === "waitlist" && (
|
||||
<p className="text-sm">{t(I18nKey.WAITLIST$PATIENCE_MESSAGE)}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConversation } from "#/context/conversation-context";
|
||||
|
||||
type UploadFilesArgs = {
|
||||
files: File[];
|
||||
};
|
||||
|
||||
export const useUploadFiles = () => {
|
||||
const { conversationId } = useConversation();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ files }: UploadFilesArgs) =>
|
||||
OpenHands.uploadFiles(conversationId, files),
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConversation } from "#/context/conversation-context";
|
||||
import { FileService } from "#/api/file-service/file-service.api";
|
||||
|
||||
interface UseListFileConfig {
|
||||
path: string;
|
||||
@@ -9,8 +9,8 @@ interface UseListFileConfig {
|
||||
export const useListFile = (config: UseListFileConfig) => {
|
||||
const { conversationId } = useConversation();
|
||||
return useQuery({
|
||||
queryKey: ["file", conversationId, config.path],
|
||||
queryFn: () => OpenHands.getFile(conversationId, config.path),
|
||||
queryKey: ["files", conversationId, config.path],
|
||||
queryFn: () => FileService.getFile(conversationId, config.path),
|
||||
enabled: false, // don't fetch by default, trigger manually via `refetch`
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSelector } from "react-redux";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConversation } from "#/context/conversation-context";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { FileService } from "#/api/file-service/file-service.api";
|
||||
|
||||
interface UseListFilesConfig {
|
||||
path?: string;
|
||||
@@ -17,12 +17,12 @@ const DEFAULT_CONFIG: UseListFilesConfig = {
|
||||
export const useListFiles = (config: UseListFilesConfig = DEFAULT_CONFIG) => {
|
||||
const { conversationId } = useConversation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const isActive = !RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const runtimeIsActive = !RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["files", conversationId, config?.path],
|
||||
queryFn: () => OpenHands.getFiles(conversationId, config?.path),
|
||||
enabled: !!(isActive && config?.enabled),
|
||||
queryFn: () => FileService.getFiles(conversationId, config?.path),
|
||||
enabled: runtimeIsActive && !!config?.enabled,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { generateAgentStateChangeEvent } from "#/services/agent-state-service";
|
||||
import { addErrorMessage } from "#/state/chat-slice";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { ErrorObservation } from "#/types/core/observations";
|
||||
import { useEndSession } from "../../../hooks/use-end-session";
|
||||
import { useEndSession } from "./use-end-session";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
|
||||
interface ServerError {
|
||||
@@ -301,6 +301,7 @@ export enum I18nKey {
|
||||
STATUS$ERROR_LLM_SERVICE_UNAVAILABLE = "STATUS$ERROR_LLM_SERVICE_UNAVAILABLE",
|
||||
STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR = "STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR",
|
||||
STATUS$ERROR_LLM_OUT_OF_CREDITS = "STATUS$ERROR_LLM_OUT_OF_CREDITS",
|
||||
STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION = "STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION",
|
||||
STATUS$ERROR_RUNTIME_DISCONNECTED = "STATUS$ERROR_RUNTIME_DISCONNECTED",
|
||||
STATUS$LLM_RETRY = "STATUS$LLM_RETRY",
|
||||
AGENT_ERROR$BAD_ACTION = "AGENT_ERROR$BAD_ACTION",
|
||||
|
||||
@@ -4751,19 +4751,19 @@
|
||||
"tr": "Çalışma alanını kapat"
|
||||
},
|
||||
"ACTION_MESSAGE$RUN": {
|
||||
"en": "Running a bash command",
|
||||
"zh-CN": "运行",
|
||||
"zh-TW": "執行",
|
||||
"ko-KR": "실행",
|
||||
"ja": "実行",
|
||||
"no": "Kjører en bash-kommando",
|
||||
"ar": "تشغيل أمر باش",
|
||||
"de": "Führt einen Bash-Befehl aus",
|
||||
"fr": "Exécution d'une commande bash",
|
||||
"it": "Esecuzione di un comando bash",
|
||||
"pt": "Executando um comando bash",
|
||||
"es": "Ejecutando un comando bash",
|
||||
"tr": "Bash komutu çalıştırılıyor"
|
||||
"en": "Running <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"zh-CN": "运行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"zh-TW": "執行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ko-KR": "실행 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ja": "実行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"no": "Kjører <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ar": "تشغيل <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"de": "Führt <cmd>{{action.payload.args.command}}</cmd> aus",
|
||||
"fr": "Exécution de <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"it": "Esecuzione di <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"pt": "Executando <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"es": "Ejecutando <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"tr": "<cmd>{{action.payload.args.command}}</cmd> çalıştırılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$RUN_IPYTHON": {
|
||||
"en": "Running a Python command",
|
||||
@@ -4781,49 +4781,49 @@
|
||||
"tr": "Python komutu çalıştırılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$READ": {
|
||||
"en": "Reading the contents of a file",
|
||||
"zh-CN": "读取",
|
||||
"zh-TW": "讀取",
|
||||
"ko-KR": "읽기",
|
||||
"ja": "読み取り",
|
||||
"no": "Leser innholdet i en fil",
|
||||
"ar": "قراءة محتويات ملف",
|
||||
"de": "Liest den Inhalt einer Datei",
|
||||
"fr": "Lecture du contenu d'un fichier",
|
||||
"it": "Lettura del contenuto di un file",
|
||||
"pt": "Lendo o conteúdo de um arquivo",
|
||||
"es": "Leyendo el contenido de un archivo",
|
||||
"tr": "Dosya içeriği okunuyor"
|
||||
"en": "Reading <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "读取 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "讀取 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "읽기 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "読み取り <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Leser <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "قراءة <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Liest <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Lecture de <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Lettura di <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Lendo <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Leyendo <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> okunuyor"
|
||||
},
|
||||
"ACTION_MESSAGE$EDIT": {
|
||||
"en": "Editing the contents of a file",
|
||||
"zh-CN": "编辑",
|
||||
"zh-TW": "編輯",
|
||||
"ko-KR": "편집",
|
||||
"ja": "編集",
|
||||
"no": "Redigerer innholdet i en fil",
|
||||
"ar": "تحرير محتويات ملف",
|
||||
"de": "Bearbeitet den Inhalt einer Datei",
|
||||
"fr": "Modification du contenu d'un fichier",
|
||||
"it": "Modifica del contenuto di un file",
|
||||
"pt": "Editando o conteúdo de um arquivo",
|
||||
"es": "Editando el contenido de un archivo",
|
||||
"tr": "Dosya içeriği düzenleniyor"
|
||||
"en": "Editing <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "编辑 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "編輯 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "편집 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "編集 <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Redigerer <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "تحرير <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Bearbeitet <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Modification de <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Modifica di <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Editando <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Editando <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> düzenleniyor"
|
||||
},
|
||||
"ACTION_MESSAGE$WRITE": {
|
||||
"en": "Writing to a file",
|
||||
"zh-CN": "写入",
|
||||
"zh-TW": "寫入",
|
||||
"ko-KR": "쓰기",
|
||||
"ja": "書き込み",
|
||||
"no": "Skriver til en fil",
|
||||
"ar": "الكتابة إلى ملف",
|
||||
"de": "Schreibt in eine Datei",
|
||||
"fr": "Écriture dans un fichier",
|
||||
"it": "Scrittura su file",
|
||||
"pt": "Escrevendo em um arquivo",
|
||||
"es": "Escribiendo en un archivo",
|
||||
"tr": "Dosyaya yazılıyor"
|
||||
"en": "Writing to <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "写入 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "寫入 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "쓰기 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "書き込み <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Skriver til <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "الكتابة إلى <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Schreibt in <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Écriture dans <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Scrittura su <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Escrevendo em <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Escribiendo en <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> dosyasına yazılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$BROWSE": {
|
||||
"en": "Browsing the web",
|
||||
@@ -4871,19 +4871,19 @@
|
||||
"tr": "Düşünüyor"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RUN": {
|
||||
"en": "Ran a bash command",
|
||||
"zh-CN": "运行",
|
||||
"zh-TW": "執行",
|
||||
"ko-KR": "실행",
|
||||
"ja": "実行",
|
||||
"no": "Kjørte en bash-kommando",
|
||||
"ar": "تم تشغيل أمر باش",
|
||||
"de": "Führte einen Bash-Befehl aus",
|
||||
"fr": "A exécuté une commande bash",
|
||||
"it": "Ha eseguito un comando bash",
|
||||
"pt": "Executou um comando bash",
|
||||
"es": "Ejecutó un comando bash",
|
||||
"tr": "Bash komutu çalıştırıldı"
|
||||
"en": "Ran <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"zh-CN": "运行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"zh-TW": "執行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ko-KR": "실행 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ja": "実行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"no": "Kjørte <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ar": "تم تشغيل <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"de": "Führte <cmd>{{observation.payload.extras.command}}</cmd> aus",
|
||||
"fr": "A exécuté <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"it": "Ha eseguito <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"pt": "Executou <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"es": "Ejecutó <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"tr": "<cmd>{{observation.payload.extras.command}}</cmd> çalıştırıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RUN_IPYTHON": {
|
||||
"en": "Ran a Python command",
|
||||
@@ -4901,49 +4901,49 @@
|
||||
"tr": "Python komutu çalıştırıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$READ": {
|
||||
"en": "Read the contents of a file",
|
||||
"zh-CN": "读取",
|
||||
"zh-TW": "讀取",
|
||||
"ko-KR": "읽기",
|
||||
"ja": "読み取り",
|
||||
"no": "Leste innholdet i en fil",
|
||||
"ar": "تمت قراءة محتويات ملف",
|
||||
"de": "Las den Inhalt einer Datei",
|
||||
"fr": "A lu le contenu d'un fichier",
|
||||
"it": "Ha letto il contenuto di un file",
|
||||
"pt": "Leu o conteúdo de um arquivo",
|
||||
"es": "Leyó el contenido de un archivo",
|
||||
"tr": "Dosya içeriği okundu"
|
||||
"en": "Read <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "读取 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "讀取 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "읽기 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "読み取り <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Leste <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تمت قراءة <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Las <path>{{observation.payload.extras.path}}</path>",
|
||||
"fr": "A lu <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha letto <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Leu <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Leyó <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> okundu"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$EDIT": {
|
||||
"en": "Edited the contents of a file",
|
||||
"zh-CN": "编辑",
|
||||
"zh-TW": "編輯",
|
||||
"ko-KR": "편집",
|
||||
"ja": "編集",
|
||||
"no": "Redigerte innholdet i en fil",
|
||||
"ar": "تم تحرير محتويات ملف",
|
||||
"de": "Hat den Inhalt einer Datei bearbeitet",
|
||||
"fr": "A modifié le contenu d'un fichier",
|
||||
"it": "Ha modificato il contenuto di un file",
|
||||
"pt": "Editou o conteúdo de um arquivo",
|
||||
"es": "Editó el contenido de un archivo",
|
||||
"tr": "Dosya içeriği düzenlendi"
|
||||
"en": "Edited <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "编辑 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "編輯 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "편집 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "編集 <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Redigerte <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تم تحرير <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Hat <path>{{observation.payload.extras.path}}</path> bearbeitet",
|
||||
"fr": "A modifié <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha modificato <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Editou <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Editó <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> düzenlendi"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$WRITE": {
|
||||
"en": "Wrote to a file",
|
||||
"zh-CN": "写入",
|
||||
"zh-TW": "寫入",
|
||||
"ko-KR": "쓰기",
|
||||
"ja": "書き込み",
|
||||
"no": "Skrev til en fil",
|
||||
"ar": "تمت الكتابة إلى ملف",
|
||||
"de": "Hat in eine Datei geschrieben",
|
||||
"fr": "A écrit dans un fichier",
|
||||
"it": "Ha scritto su un file",
|
||||
"pt": "Escreveu em um arquivo",
|
||||
"es": "Escribió en un archivo",
|
||||
"tr": "Dosyaya yazıldı"
|
||||
"en": "Wrote to <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "写入 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "寫入 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "쓰기 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "書き込み <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Skrev til <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تمت الكتابة إلى <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Hat in <path>{{observation.payload.extras.path}}</path> geschrieben",
|
||||
"fr": "A écrit dans <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha scritto su <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Escreveu em <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Escribió en <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> dosyasına yazıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$BROWSE": {
|
||||
"en": "Browsing completed",
|
||||
|
||||
6
frontend/src/message.d.ts
vendored
6
frontend/src/message.d.ts
vendored
@@ -1,3 +1,7 @@
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
import { OpenHandsObservation } from "./types/core/observations";
|
||||
import { OpenHandsAction } from "./types/core/actions";
|
||||
|
||||
export type Message = {
|
||||
sender: "user" | "assistant";
|
||||
content: string;
|
||||
@@ -8,4 +12,6 @@ export type Message = {
|
||||
pending?: boolean;
|
||||
translationID?: string;
|
||||
eventID?: number;
|
||||
observation?: PayloadAction<OpenHandsObservation>;
|
||||
action?: PayloadAction<OpenHandsAction>;
|
||||
};
|
||||
|
||||
39
frontend/src/mocks/file-service-handlers.ts
Normal file
39
frontend/src/mocks/file-service-handlers.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { delay, http, HttpResponse } from "msw";
|
||||
|
||||
export const FILE_VARIANTS_1 = ["file1.txt", "file2.txt", "file3.txt"];
|
||||
export const FILE_VARIANTS_2 = [
|
||||
"reboot_skynet.exe",
|
||||
"target_list.txt",
|
||||
"terminator_blueprint.txt",
|
||||
];
|
||||
|
||||
export const FILE_SERVICE_HANDLERS = [
|
||||
http.get(
|
||||
"/api/conversations/:conversationId/list-files",
|
||||
async ({ params }) => {
|
||||
await delay();
|
||||
|
||||
const cid = params.conversationId?.toString();
|
||||
if (!cid) return HttpResponse.json(null, { status: 400 });
|
||||
|
||||
return cid === "test-conversation-id-2"
|
||||
? HttpResponse.json(FILE_VARIANTS_2)
|
||||
: HttpResponse.json(FILE_VARIANTS_1);
|
||||
},
|
||||
),
|
||||
|
||||
http.get(
|
||||
"/api/conversations/:conversationId/select-file",
|
||||
async ({ request }) => {
|
||||
await delay();
|
||||
|
||||
const url = new URL(request.url);
|
||||
const file = url.searchParams.get("file")?.toString();
|
||||
if (file) {
|
||||
return HttpResponse.json({ code: `Content of ${file}` });
|
||||
}
|
||||
|
||||
return HttpResponse.json(null, { status: 404 });
|
||||
},
|
||||
),
|
||||
];
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||
import { STRIPE_BILLING_HANDLERS } from "./billing-handlers";
|
||||
import { ApiSettings, PostApiSettings } from "#/types/settings";
|
||||
import { FILE_SERVICE_HANDLERS } from "./file-service-handlers";
|
||||
import { GitUser } from "#/types/git";
|
||||
|
||||
export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
|
||||
@@ -91,52 +92,6 @@ const openHandsHandlers = [
|
||||
HttpResponse.json(["mock-invariant"]),
|
||||
),
|
||||
|
||||
http.get(
|
||||
"http://localhost:3001/api/conversations/:conversationId/list-files",
|
||||
async ({ params }) => {
|
||||
await delay();
|
||||
|
||||
const cid = params.conversationId?.toString();
|
||||
if (!cid) return HttpResponse.json([], { status: 404 });
|
||||
|
||||
let data = ["file1.txt", "file2.txt", "file3.txt"];
|
||||
if (cid === "3") {
|
||||
data = [
|
||||
"reboot_skynet.exe",
|
||||
"target_list.txt",
|
||||
"terminator_blueprint.txt",
|
||||
];
|
||||
}
|
||||
|
||||
return HttpResponse.json(data);
|
||||
},
|
||||
),
|
||||
|
||||
http.post("http://localhost:3001/api/save-file", () =>
|
||||
HttpResponse.json(null, { status: 200 }),
|
||||
),
|
||||
|
||||
http.get("http://localhost:3001/api/select-file", async ({ request }) => {
|
||||
await delay();
|
||||
|
||||
const token = request.headers
|
||||
.get("Authorization")
|
||||
?.replace("Bearer", "")
|
||||
.trim();
|
||||
|
||||
if (!token) {
|
||||
return HttpResponse.json([], { status: 401 });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const file = url.searchParams.get("file")?.toString();
|
||||
if (file) {
|
||||
return HttpResponse.json({ code: `Content of ${file}` });
|
||||
}
|
||||
|
||||
return HttpResponse.json(null, { status: 404 });
|
||||
}),
|
||||
|
||||
http.post("http://localhost:3001/api/submit-feedback", async () => {
|
||||
await delay(1200);
|
||||
|
||||
@@ -149,6 +104,7 @@ const openHandsHandlers = [
|
||||
|
||||
export const handlers = [
|
||||
...STRIPE_BILLING_HANDLERS,
|
||||
...FILE_SERVICE_HANDLERS,
|
||||
...openHandsHandlers,
|
||||
http.get("/api/user/repositories", () =>
|
||||
HttpResponse.json([
|
||||
|
||||
@@ -6,17 +6,17 @@ import {
|
||||
} from "@react-router/dev/routes";
|
||||
|
||||
export default [
|
||||
layout("routes/_oh/route.tsx", [
|
||||
index("routes/_oh._index/route.tsx"),
|
||||
layout("routes/root-layout.tsx", [
|
||||
index("routes/home.tsx"),
|
||||
route("settings", "routes/settings.tsx", [
|
||||
index("routes/account-settings.tsx"),
|
||||
route("billing", "routes/billing.tsx"),
|
||||
]),
|
||||
route("conversations/:conversationId", "routes/_oh.app/route.tsx", [
|
||||
index("routes/_oh.app._index/route.tsx"),
|
||||
route("browser", "routes/_oh.app.browser.tsx"),
|
||||
route("jupyter", "routes/_oh.app.jupyter.tsx"),
|
||||
route("served", "routes/app.tsx"),
|
||||
route("conversations/:conversationId", "routes/conversation.tsx", [
|
||||
index("routes/editor-tab.tsx"),
|
||||
route("browser", "routes/browser-tab.tsx"),
|
||||
route("jupyter", "routes/jupyter-tab.tsx"),
|
||||
route("served", "routes/served-tab.tsx"),
|
||||
]),
|
||||
]),
|
||||
] satisfies RouteConfig;
|
||||
|
||||
@@ -18,9 +18,9 @@ import GlobeIcon from "#/icons/globe.svg?react";
|
||||
import ListIcon from "#/icons/list-type-number.svg?react";
|
||||
import { clearJupyter } from "#/state/jupyter-slice";
|
||||
import { FilesProvider } from "#/context/files";
|
||||
import { ChatInterface } from "../../components/features/chat/chat-interface";
|
||||
import { ChatInterface } from "../components/features/chat/chat-interface";
|
||||
import { WsClientProvider } from "#/context/ws-client-provider";
|
||||
import { EventHandler } from "./event-handler";
|
||||
import { EventHandler } from "../wrapper/event-handler";
|
||||
import { useConversationConfig } from "#/hooks/query/use-conversation-config";
|
||||
import { Container } from "#/components/layout/container";
|
||||
import {
|
||||
@@ -5,8 +5,8 @@ import { setReplayJson } from "#/state/initial-query-slice";
|
||||
import { useGitUser } from "#/hooks/query/use-git-user";
|
||||
import { useGitHubAuthUrl } from "#/hooks/use-github-auth-url";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { ReplaySuggestionBox } from "#/components/features/suggestions/replay-suggestion-box";
|
||||
import { GitRepositoriesSuggestionBox } from "#/components/features/git/git-repositories-suggestion-box";
|
||||
import { ReplaySuggestionBox } from "../../components/features/suggestions/replay-suggestion-box";
|
||||
import { CodeNotInGitLink } from "#/components/features/git/code-not-in-github-link";
|
||||
import { HeroHeading } from "#/components/shared/hero-heading";
|
||||
import { TaskForm } from "#/components/shared/task-form";
|
||||
@@ -14,10 +14,9 @@ import { useGitHubAuthUrl } from "#/hooks/use-github-auth-url";
|
||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { Sidebar } from "#/components/features/sidebar/sidebar";
|
||||
import { WaitlistModal } from "#/components/features/waitlist/waitlist-modal";
|
||||
import { AuthModal } from "#/components/features/waitlist/auth-modal";
|
||||
import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal";
|
||||
import { useSettings } from "#/hooks/query/use-settings";
|
||||
import { useAuth } from "#/context/auth-context";
|
||||
import { useMigrateUserConsent } from "#/hooks/use-migrate-user-consent";
|
||||
import { useBalance } from "#/hooks/query/use-balance";
|
||||
import { SetupPaymentModal } from "#/components/features/payment/setup-payment-modal";
|
||||
@@ -60,7 +59,6 @@ export default function MainApp() {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { providersAreSet } = useAuth();
|
||||
const { data: settings } = useSettings();
|
||||
const { error, isFetching } = useBalance();
|
||||
const { migrateUserConsent } = useMigrateUserConsent();
|
||||
@@ -114,7 +112,7 @@ export default function MainApp() {
|
||||
}, [error?.status, pathname, isFetching]);
|
||||
|
||||
const userIsAuthed = !!isAuthed && !authError;
|
||||
const renderWaitlistModal =
|
||||
const renderAuthModal =
|
||||
!isFetchingAuth && !userIsAuthed && config.data?.APP_MODE === "saas";
|
||||
|
||||
return (
|
||||
@@ -131,13 +129,7 @@ export default function MainApp() {
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
{renderWaitlistModal && (
|
||||
<WaitlistModal
|
||||
ghTokenIsSet={providersAreSet}
|
||||
githubAuthUrl={gitHubAuthUrl}
|
||||
/>
|
||||
)}
|
||||
|
||||
{renderAuthModal && <AuthModal githubAuthUrl={gitHubAuthUrl} />}
|
||||
{config.data?.APP_MODE === "oss" && consentFormIsOpen && (
|
||||
<AnalyticsConsentFormModal
|
||||
onClose={() => {
|
||||
@@ -2,14 +2,14 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import type { Message } from "#/message";
|
||||
|
||||
import { ActionSecurityRisk } from "#/state/security-analyzer-slice";
|
||||
import {
|
||||
OpenHandsObservation,
|
||||
CommandObservation,
|
||||
IPythonObservation,
|
||||
RecallObservation,
|
||||
} from "#/types/core/observations";
|
||||
import { OpenHandsAction } from "#/types/core/actions";
|
||||
import { OpenHandsEventType } from "#/types/core/base";
|
||||
import {
|
||||
CommandObservation,
|
||||
IPythonObservation,
|
||||
OpenHandsObservation,
|
||||
RecallObservation,
|
||||
} from "#/types/core/observations";
|
||||
|
||||
type SliceState = { messages: Message[] };
|
||||
|
||||
@@ -135,6 +135,7 @@ export const chatSlice = createSlice({
|
||||
content: text,
|
||||
imageUrls: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
action,
|
||||
};
|
||||
|
||||
state.messages.push(message);
|
||||
@@ -224,6 +225,7 @@ export const chatSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
causeMessage.translationID = translationID;
|
||||
causeMessage.observation = observation;
|
||||
// Set success property based on observation type
|
||||
if (observationID === "run") {
|
||||
const commandObs = observation.payload as CommandObservation;
|
||||
@@ -253,9 +255,7 @@ export const chatSlice = createSlice({
|
||||
if (content.length > MAX_CONTENT_LENGTH) {
|
||||
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...`;
|
||||
}
|
||||
content = `${
|
||||
causeMessage.content
|
||||
}\n\nOutput:\n\`\`\`\n${content.trim() || "[Command finished execution with no output]"}\n\`\`\``;
|
||||
content = `${causeMessage.content}\n\nOutput:\n\`\`\`\n${content.trim() || "[Command finished execution with no output]"}\n\`\`\``;
|
||||
causeMessage.content = content; // Observation content includes the action
|
||||
} else if (observationID === "read") {
|
||||
causeMessage.content = `\`\`\`\n${observation.payload.content}\n\`\`\``; // Content is already truncated by the ACI
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { useHandleWSEvents } from "./hooks/use-handle-ws-events";
|
||||
import { useHandleRuntimeActive } from "./hooks/use-handle-runtime-active";
|
||||
import { useHandleWSEvents } from "../hooks/use-handle-ws-events";
|
||||
import { useHandleRuntimeActive } from "../hooks/use-handle-runtime-active";
|
||||
|
||||
export function EventHandler({ children }: React.PropsWithChildren) {
|
||||
useHandleWSEvents();
|
||||
@@ -3,7 +3,9 @@ import logging
|
||||
import sys
|
||||
from uuid import uuid4
|
||||
|
||||
from termcolor import colored
|
||||
from prompt_toolkit import PromptSession, print_formatted_text
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
|
||||
import openhands.agenthub # noqa F401 (we import this to get the agents registered)
|
||||
from openhands.core.config import (
|
||||
@@ -36,41 +38,90 @@ from openhands.events.observation import (
|
||||
CmdOutputObservation,
|
||||
FileEditObservation,
|
||||
)
|
||||
from openhands.io import read_input, read_task
|
||||
from openhands.io import read_task
|
||||
|
||||
prompt_session = PromptSession()
|
||||
|
||||
|
||||
def display_message(message: str):
|
||||
print(colored('🤖 ' + message + '\n', 'yellow'))
|
||||
def display_message(message: str) -> None:
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('ansiyellow', '🤖 '),
|
||||
('ansiyellow', message),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def display_command(command: str):
|
||||
print('❯ ' + colored(command + '\n', 'green'))
|
||||
def display_command(command: str) -> None:
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('', '❯ '),
|
||||
('ansigreen', command),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def display_confirmation(confirmation_state: ActionConfirmationStatus):
|
||||
def display_confirmation(confirmation_state: ActionConfirmationStatus) -> None:
|
||||
if confirmation_state == ActionConfirmationStatus.CONFIRMED:
|
||||
print(colored('✅ ' + confirmation_state + '\n', 'green'))
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('ansigreen', '✅ '),
|
||||
('ansigreen', str(confirmation_state)),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
elif confirmation_state == ActionConfirmationStatus.REJECTED:
|
||||
print(colored('❌ ' + confirmation_state + '\n', 'red'))
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('ansired', '❌ '),
|
||||
('ansired', str(confirmation_state)),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(colored('⏳ ' + confirmation_state + '\n', 'yellow'))
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('ansiyellow', '⏳ '),
|
||||
('ansiyellow', str(confirmation_state)),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def display_command_output(output: str):
|
||||
def display_command_output(output: str) -> None:
|
||||
lines = output.split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('[Python Interpreter') or line.startswith('openhands@'):
|
||||
# TODO: clean this up once we clean up terminal output
|
||||
continue
|
||||
print(colored(line, 'blue'))
|
||||
print('\n')
|
||||
print_formatted_text(FormattedText([('ansiblue', line)]))
|
||||
print_formatted_text('')
|
||||
|
||||
|
||||
def display_file_edit(event: FileEditAction | FileEditObservation):
|
||||
print(colored(str(event), 'green'))
|
||||
def display_file_edit(event: FileEditAction | FileEditObservation) -> None:
|
||||
print_formatted_text(
|
||||
FormattedText(
|
||||
[
|
||||
('ansigreen', str(event)),
|
||||
('', '\n'),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def display_event(event: Event, config: AppConfig):
|
||||
def display_event(event: Event, config: AppConfig) -> None:
|
||||
if isinstance(event, Action):
|
||||
if hasattr(event, 'thought'):
|
||||
display_message(event.thought)
|
||||
@@ -89,7 +140,42 @@ def display_event(event: Event, config: AppConfig):
|
||||
display_confirmation(event.confirmation_state)
|
||||
|
||||
|
||||
async def main(loop: asyncio.AbstractEventLoop):
|
||||
async def read_prompt_input(multiline=False):
|
||||
try:
|
||||
if multiline:
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add('c-d')
|
||||
def _(event):
|
||||
event.current_buffer.validate_and_handle()
|
||||
|
||||
message = await prompt_session.prompt_async(
|
||||
'Enter your message and press Ctrl+D to finish:\n',
|
||||
multiline=True,
|
||||
key_bindings=kb,
|
||||
)
|
||||
else:
|
||||
message = await prompt_session.prompt_async(
|
||||
'>> ',
|
||||
)
|
||||
return message
|
||||
except KeyboardInterrupt:
|
||||
return 'exit'
|
||||
except EOFError:
|
||||
return 'exit'
|
||||
|
||||
|
||||
async def read_confirmation_input():
|
||||
try:
|
||||
confirmation = await prompt_session.prompt_async(
|
||||
'Confirm action (possible security risk)? (y/n) >> ',
|
||||
)
|
||||
return confirmation.lower() == 'y'
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
return False
|
||||
|
||||
|
||||
async def main(loop: asyncio.AbstractEventLoop) -> None:
|
||||
"""Runs the agent in CLI mode."""
|
||||
|
||||
args = parse_arguments()
|
||||
@@ -121,11 +207,8 @@ async def main(loop: asyncio.AbstractEventLoop):
|
||||
|
||||
event_stream = runtime.event_stream
|
||||
|
||||
async def prompt_for_next_task():
|
||||
# Run input() in a thread pool to avoid blocking the event loop
|
||||
next_message = await loop.run_in_executor(
|
||||
None, read_input, config.cli_multiline_input
|
||||
)
|
||||
async def prompt_for_next_task() -> None:
|
||||
next_message = await read_prompt_input(config.cli_multiline_input)
|
||||
if not next_message.strip():
|
||||
await prompt_for_next_task()
|
||||
if next_message == 'exit':
|
||||
@@ -136,13 +219,7 @@ async def main(loop: asyncio.AbstractEventLoop):
|
||||
action = MessageAction(content=next_message)
|
||||
event_stream.add_event(action, EventSource.USER)
|
||||
|
||||
async def prompt_for_user_confirmation():
|
||||
user_confirmation = await loop.run_in_executor(
|
||||
None, lambda: input('Confirm action (possible security risk)? (y/n) >> ')
|
||||
)
|
||||
return user_confirmation.lower() == 'y'
|
||||
|
||||
async def on_event_async(event: Event):
|
||||
async def on_event_async(event: Event) -> None:
|
||||
display_event(event, config)
|
||||
if isinstance(event, AgentStateChangedObservation):
|
||||
if event.agent_state in [
|
||||
@@ -151,7 +228,7 @@ async def main(loop: asyncio.AbstractEventLoop):
|
||||
]:
|
||||
await prompt_for_next_task()
|
||||
if event.agent_state == AgentState.AWAITING_USER_CONFIRMATION:
|
||||
user_confirmed = await prompt_for_user_confirmation()
|
||||
user_confirmed = await read_confirmation_input()
|
||||
if user_confirmed:
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_CONFIRMED),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import ClassVar
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from pydantic import BaseModel, Field, SecretStr
|
||||
|
||||
@@ -50,7 +50,7 @@ class AppConfig(BaseModel):
|
||||
"""
|
||||
|
||||
llms: dict[str, LLMConfig] = Field(default_factory=dict)
|
||||
agents: dict = Field(default_factory=dict)
|
||||
agents: dict[str, AgentConfig] = Field(default_factory=dict)
|
||||
default_agent: str = Field(default=OH_DEFAULT_AGENT)
|
||||
sandbox: SandboxConfig = Field(default_factory=SandboxConfig)
|
||||
security: SecurityConfig = Field(default_factory=SecurityConfig)
|
||||
@@ -93,7 +93,7 @@ class AppConfig(BaseModel):
|
||||
|
||||
model_config = {'extra': 'forbid'}
|
||||
|
||||
def get_llm_config(self, name='llm') -> LLMConfig:
|
||||
def get_llm_config(self, name: str = 'llm') -> LLMConfig:
|
||||
"""'llm' is the name for default config (for backward compatibility prior to 0.8)."""
|
||||
if name in self.llms:
|
||||
return self.llms[name]
|
||||
@@ -105,10 +105,10 @@ class AppConfig(BaseModel):
|
||||
self.llms['llm'] = LLMConfig()
|
||||
return self.llms['llm']
|
||||
|
||||
def set_llm_config(self, value: LLMConfig, name='llm') -> None:
|
||||
def set_llm_config(self, value: LLMConfig, name: str = 'llm') -> None:
|
||||
self.llms[name] = value
|
||||
|
||||
def get_agent_config(self, name='agent') -> AgentConfig:
|
||||
def get_agent_config(self, name: str = 'agent') -> AgentConfig:
|
||||
"""'agent' is the name for default config (for backward compatibility prior to 0.8)."""
|
||||
if name in self.agents:
|
||||
return self.agents[name]
|
||||
@@ -116,22 +116,24 @@ class AppConfig(BaseModel):
|
||||
self.agents['agent'] = AgentConfig()
|
||||
return self.agents['agent']
|
||||
|
||||
def set_agent_config(self, value: AgentConfig, name='agent') -> None:
|
||||
def set_agent_config(self, value: AgentConfig, name: str = 'agent') -> None:
|
||||
self.agents[name] = value
|
||||
|
||||
def get_agent_to_llm_config_map(self) -> dict[str, LLMConfig]:
|
||||
"""Get a map of agent names to llm configs."""
|
||||
return {name: self.get_llm_config_from_agent(name) for name in self.agents}
|
||||
|
||||
def get_llm_config_from_agent(self, name='agent') -> LLMConfig:
|
||||
def get_llm_config_from_agent(self, name: str = 'agent') -> LLMConfig:
|
||||
agent_config: AgentConfig = self.get_agent_config(name)
|
||||
llm_config_name = agent_config.llm_config
|
||||
llm_config_name = (
|
||||
agent_config.llm_config if agent_config.llm_config is not None else 'llm'
|
||||
)
|
||||
return self.get_llm_config(llm_config_name)
|
||||
|
||||
def get_agent_configs(self) -> dict[str, AgentConfig]:
|
||||
return self.agents
|
||||
|
||||
def model_post_init(self, __context):
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
"""Post-initialization hook, called when the instance is created with only default values."""
|
||||
super().model_post_init(__context)
|
||||
if not AppConfig.defaults_dict: # Only set defaults_dict if it's empty
|
||||
|
||||
@@ -151,7 +151,7 @@ class LLMConfig(BaseModel):
|
||||
|
||||
return llm_mapping
|
||||
|
||||
def model_post_init(self, __context: Any):
|
||||
def model_post_init(self, __context: Any) -> None:
|
||||
"""Post-initialization hook to assign OpenRouter-related variables to environment variables.
|
||||
|
||||
This ensures that these values are accessible to litellm at runtime.
|
||||
@@ -164,8 +164,8 @@ class LLMConfig(BaseModel):
|
||||
if self.openrouter_app_name:
|
||||
os.environ['OR_APP_NAME'] = self.openrouter_app_name
|
||||
|
||||
# Assign an API version for Azure models
|
||||
# While it doesn't seem required, the format supported by the API without version seems old and will likely break.
|
||||
# Azure issue: https://github.com/All-Hands-AI/OpenHands/issues/6777
|
||||
# Set an API version by default for Azure models
|
||||
# Required for newer models.
|
||||
# Azure issue: https://github.com/All-Hands-AI/OpenHands/issues/7755
|
||||
if self.model.startswith('azure') and self.api_version is None:
|
||||
self.api_version = '2024-08-01-preview'
|
||||
self.api_version = '2024-12-01-preview'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user