mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-19 06:30:30 -04:00
Compare commits
62 Commits
network-fs-wal
...
beta
| Author | SHA1 | Date | |
|---|---|---|---|
| 6baad7fc3e | |||
| 0762d63b6a | |||
| 4df0591025 | |||
| 30cb420900 | |||
| 3b8d3ca639 | |||
| 044d04df06 | |||
| 4b9d89e943 | |||
| 5ff6bb87cf | |||
| 511b4556a2 | |||
| cb39ea1136 | |||
| ff9452bf03 | |||
| 97265f8ac5 | |||
| 56e66656b8 | |||
| 02f3f3cb3e | |||
| b0c3a16ead | |||
| 594c395576 | |||
| b7402c264d | |||
| 3f79699bce | |||
| 67fe76057e | |||
| bac474aaa0 | |||
| 98c717cb5b | |||
| 5c8d46ab4b | |||
| d5e83fefda | |||
| 46378dda50 | |||
| b38d9d812f | |||
| 8df039d261 | |||
| c92fb2d41b | |||
| 958308c913 | |||
| 16390ca47d | |||
| 643eed300d | |||
| c3a6721de2 | |||
| 47af7462ea | |||
| a76ab93673 | |||
| dfb60e37a3 | |||
| 5105913568 | |||
| 28b4cade9e | |||
| 1f61eb5ca9 | |||
| ea1ff90e42 | |||
| eb3c4c82fe | |||
| 7188d42b99 | |||
| 9391987443 | |||
| 4d79337d42 | |||
| 401a154ec3 | |||
| 18c25ae406 | |||
| cd156f9f1c | |||
| b968a314d3 | |||
| 93cdfafa78 | |||
| b6a8d99da5 | |||
| 8cacf150db | |||
| 76e7b556b6 | |||
| a888ba4da7 | |||
| a1eca087d4 | |||
| 5252cefab2 | |||
| 839343e5de | |||
| 30d778cf5f | |||
| 616aba7bbd | |||
| 006a0a4b34 | |||
| 3facbe1dd3 | |||
| 1430a460f1 | |||
| 1f96af5ea7 | |||
| 15f0fc9e00 | |||
| cd3133038b |
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@opencode-ai/plugin": patch
|
||||||
|
"@opencode-ai/core": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add transport-neutral Session model request hooks and provider-scoped hook registration so eligible OpenAI Responses requests can prefer WebSocket without bypassing HTTP-only middleware.
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
name: generate
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- v2
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
generate:
|
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
||||||
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: ./.github/actions/setup-bun
|
|
||||||
|
|
||||||
- name: Setup git committer
|
|
||||||
id: committer
|
|
||||||
uses: ./.github/actions/setup-git-committer
|
|
||||||
with:
|
|
||||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
|
||||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
|
||||||
|
|
||||||
- name: Generate
|
|
||||||
run: ./script/generate.ts
|
|
||||||
|
|
||||||
- name: Commit and push
|
|
||||||
run: |
|
|
||||||
if [ -z "$(git status --porcelain)" ]; then
|
|
||||||
echo "No changes to commit"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
git add -A
|
|
||||||
git commit -m "chore: generate" --allow-empty
|
|
||||||
git push origin HEAD:${{ github.ref_name }} --no-verify
|
|
||||||
# if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
|
|
||||||
# echo ""
|
|
||||||
# echo "============================================"
|
|
||||||
# echo "Failed to push generated code."
|
|
||||||
# echo "Please run locally and push:"
|
|
||||||
# echo ""
|
|
||||||
# echo " ./script/generate.ts"
|
|
||||||
# echo " git add -A && git commit -m \"chore: generate\" && git push"
|
|
||||||
# echo ""
|
|
||||||
# echo "============================================"
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
@@ -82,6 +82,7 @@ jobs:
|
|||||||
build-cli:
|
build-cli:
|
||||||
needs: version
|
needs: version
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
timeout-minutes: 30
|
||||||
if: github.repository == 'anomalyco/opencode'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
@@ -196,7 +197,7 @@ jobs:
|
|||||||
|
|
||||||
build-node-cli:
|
build-node-cli:
|
||||||
needs: version
|
needs: version
|
||||||
if: github.repository == 'anomalyco/opencode'
|
if: github.repository == 'anomalyco/opencode' && false # Temporarily disabled
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -594,6 +595,7 @@ jobs:
|
|||||||
path: packages/cli/dist
|
path: packages/cli/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
if: needs.build-node-cli.result == 'success'
|
||||||
with:
|
with:
|
||||||
pattern: opencode-node-cli-*
|
pattern: opencode-node-cli-*
|
||||||
path: packages/cli/dist/node
|
path: packages/cli/dist/node
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
---
|
||||||
|
name: opencode-dev
|
||||||
|
description: Use when interactively running, debugging, or verifying opencode's own V2 CLI/TUI or server during development in this repo — starting the dev TUI, driving it with termctrl, comparing V2 against the legacy TUI, hitting the V2 server/API directly, reading log files, or attaching Bun's inspector.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Debugging opencode itself
|
||||||
|
|
||||||
|
Workflow for interactively exercising the V2 CLI/TUI and server while developing in this repo. All commands below run from `packages/cli` unless noted otherwise.
|
||||||
|
|
||||||
|
## Server/client model
|
||||||
|
|
||||||
|
opencode V2 is a client/server system, not a single monolithic process:
|
||||||
|
|
||||||
|
- **Server process** runs the Effect HTTP API (`packages/server`) and owns all domain state: sessions, database, plugins, permissions, Location services. It's started by the `serve` command (`packages/cli/src/commands/handlers/serve.ts`).
|
||||||
|
- **TUI process** is a separate process that runs no application logic itself — it's an HTTP/SSE client of the server via the generated SDK (`createOpencodeClient` / `sdk.client.v2`).
|
||||||
|
- **Discovery**: CLI processes find the shared server through a JSON registration file at `~/.local/state/opencode/service.json` (or `service-local.json` for the local/dev channel) containing `{id, version, url, pid}`. A separate password file under `~/.config/opencode/service.json` provides HTTP Basic auth. Before reusing a registration, the client calls `GET /health` to confirm the server is alive, authenticated, and version-compatible.
|
||||||
|
- **Sharing**: because of this registration/health-check dance, many concurrent `opencode`/TUI invocations converge on one shared background daemon rather than each spawning their own. If no compatible healthy daemon is found, a new one is spawned detached (`serve --service`) and registers itself.
|
||||||
|
- **`bun dev service start|status|stop|restart`** manages this shared background daemon's lifecycle directly — useful when you need to force a fresh server, confirm one is running, or kill a stuck one.
|
||||||
|
- **Standalone mode** (`--standalone`) opts a single invocation out of the shared daemon: it spawns a private one-off `serve --stdio --port 0` child tied to that invocation's lifetime, with its own random password. Use this to isolate a debugging session from your other running opencode sessions.
|
||||||
|
- Every log line is tagged `role=server` or `role=cli` and a per-process `run=<id>`, so you can distinguish server-side and client-side activity in one shared log file (see "Logs" below) even when both roles are interleaved from concurrent processes.
|
||||||
|
|
||||||
|
## Starting the dev TUI
|
||||||
|
|
||||||
|
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
|
||||||
|
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
|
||||||
|
|
||||||
|
## Interactive debugging with termctrl
|
||||||
|
|
||||||
|
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
|
||||||
|
- Use a dedicated session name and do not reuse or kill an unrelated session.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||||
|
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
|
||||||
|
termctrl show opencode-v2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`.
|
||||||
|
- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl send opencode-v2-dev 'text:example prompt' enter
|
||||||
|
termctrl send opencode-v2-dev ctrl-c
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits.
|
||||||
|
- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png
|
||||||
|
```
|
||||||
|
|
||||||
|
- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl resize opencode-v2-dev --cols 100 --rows 30
|
||||||
|
termctrl show opencode-v2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
|
||||||
|
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
||||||
|
- Always clean up the Terminal Control session when the check is complete:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl stop opencode-v2-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparing V2 against the legacy TUI
|
||||||
|
|
||||||
|
Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From packages/cli: local V2 TUI
|
||||||
|
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||||
|
|
||||||
|
# Released legacy TUI behavior reference
|
||||||
|
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
|
||||||
|
|
||||||
|
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png
|
||||||
|
termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints.
|
||||||
|
- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`.
|
||||||
|
|
||||||
|
## Server/API debugging
|
||||||
|
|
||||||
|
- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI.
|
||||||
|
- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering.
|
||||||
|
- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev api get /health
|
||||||
|
bun dev api get /openapi.json
|
||||||
|
bun dev api <operationId> --param key=value
|
||||||
|
```
|
||||||
|
|
||||||
|
- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`.
|
||||||
|
- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control.
|
||||||
|
- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters.
|
||||||
|
|
||||||
|
## Auditing installed `opencode2` sessions
|
||||||
|
|
||||||
|
Installed next-channel sessions normally use `~/.local/share/opencode/opencode-next.db` and `~/.local/share/opencode/log/opencode.log`; `OPENCODE_DB` can override the database. Before calling `opencode2 api`, inspect `~/.local/state/opencode/service.json` because the command may start a daemon when none is healthy.
|
||||||
|
|
||||||
|
For a supplied `ses_...` ID, compare three sources:
|
||||||
|
|
||||||
|
- `opencode2 api get /api/session/active` and the Session/message endpoints for live server state.
|
||||||
|
- The database's ordered `event` rows for durable history.
|
||||||
|
- `packages/tui/src/context/data.tsx` and the relevant route for client projection and rendering.
|
||||||
|
|
||||||
|
Locate an uncertain database without modifying it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SESSION=ses_...
|
||||||
|
for db in ~/.local/share/opencode/*.db; do
|
||||||
|
sqlite3 "file:$db?mode=ro" "select 1 from session where id='$SESSION' limit 1" 2>/dev/null | grep -q 1 && printf '%s\n' "$db"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
|
||||||
|
- Log files live under `~/.local/share/opencode/log/`. In a local/dev checkout the active file is `opencode-local.log`; `opencode.log` is used for non-local (released) channel installs. Both are append-only, shared across every CLI and server process on the machine.
|
||||||
|
- Each line is structured `key=value` text: `timestamp`, `level`, `run=<id>` (per-process run ID), `message`, and a `role=cli` or `role=server` tag. Use `run=` to isolate one process's activity and `role=` to separate client-side from server-side log lines, since a shared daemon interleaves many processes' output in one file.
|
||||||
|
- Tail the live file while reproducing an issue instead of guessing from stale output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -f ~/.local/share/opencode/log/opencode-local.log
|
||||||
|
```
|
||||||
|
|
||||||
|
- Filter to one run or role when the file is noisy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep 'run=8fc3b1d5' ~/.local/share/opencode/log/opencode-local.log
|
||||||
|
grep 'role=server' ~/.local/share/opencode/log/opencode-local.log
|
||||||
|
```
|
||||||
|
|
||||||
|
- `OPENCODE_LOG_LEVEL` controls verbosity (default `INFO`); set it before starting `bun dev` or `serve` to get `DEBUG` output for a specific repro.
|
||||||
|
- `OPENCODE_PRINT_LOGS=1` additionally tees log output to stderr of the process that emitted it, which is useful when a process fails before you'd think to check the shared log file.
|
||||||
|
- `termctrl logs <session>` surfaces stdout/stderr for a Terminal Control session specifically (e.g. inspector output or startup failures before the TUI renderer starts) — use the log file above for anything emitted by a separate server/daemon process instead.
|
||||||
|
|
||||||
|
## Heap snapshots
|
||||||
|
|
||||||
|
The CLI installs a `SIGUSR1` listener on non-Windows processes in `packages/cli/src/heap.ts`. Use it to capture the installed `opencode2` server without restarting it or attaching an inspector.
|
||||||
|
|
||||||
|
1. Find the processes and inspect their roles and memory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pgrep -a -f 'opencode2\.exe|opencode2'
|
||||||
|
ps -o pid,ppid,rss,vsz,lstart,etime,cmd -p <pid>,<pid>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Signal the process whose heap needs investigation. For shared-service memory, target the `opencode2.exe serve --service` child, not the short wrapper/TUI process:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kill -USR1 <server-pid>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Wait for `heap snapshot written` in the channel's log before opening the file. Snapshots are written to the same log directory as `heap-<pid>-<timestamp>.heapsnapshot`; writing a large heap can take several seconds and the file is incomplete until the completion message appears:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep 'heap snapshot' ~/.local/share/opencode/log/opencode.log | tail
|
||||||
|
find ~/.local/share/opencode/log -maxdepth 1 -name 'heap-<server-pid>-*.heapsnapshot' -printf '%T@ %s %p\n' | sort -nr | head
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `opencode-local.log` instead for a local/dev channel process. The log's `path=` field is authoritative.
|
||||||
|
|
||||||
|
4. Analyze the snapshot with Chrome DevTools, a V8 heap snapshot parser, or a temporary tool installed outside the repository. Start with the largest retained objects, dominators, object counts grouped by constructor/name, and retainer paths back to GC roots. Relate suspicious names and paths back to the source tree rather than treating large shallow allocations as leaks.
|
||||||
|
|
||||||
|
For command-line analysis, install tooling under `/tmp/opencode`, not in the repository. For example, MemLab can rank dominators and trace a reported heap object ID back to a GC root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --prefix /tmp/opencode/heap-analysis @memlab/cli
|
||||||
|
/tmp/opencode/heap-analysis/node_modules/.bin/memlab analyze object-size --snapshot <snapshot>
|
||||||
|
/tmp/opencode/heap-analysis/node_modules/.bin/memlab analyze shape --snapshot <snapshot>
|
||||||
|
/tmp/opencode/heap-analysis/node_modules/.bin/memlab trace --snapshot <snapshot> --node-id=<id>
|
||||||
|
```
|
||||||
|
|
||||||
|
A single snapshot explains what retains memory at one point in time, but does not by itself prove a leak. For leak confirmation, capture a baseline, perform a controlled repeated workload, allow idle cleanup/GC when possible, capture another snapshot, and compare growth and retainer paths. Also compare snapshot heap size with process RSS: a large difference can indicate native allocations, database mappings, allocator fragmentation, or other memory outside the JavaScript heap.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /proc/<pid>/smaps_rollup
|
||||||
|
pmap -x <pid> | sort -k3 -nr | head -25
|
||||||
|
```
|
||||||
|
|
||||||
|
Heap serialization itself can temporarily increase RSS and allocator high-water marks, so record `ps`/`smaps_rollup` both before and after capture. Large anonymous mappings with a comparatively small live heap require native-allocation or allocator investigation; they cannot be explained from JavaScript retainer paths alone.
|
||||||
|
|
||||||
|
## Debugger
|
||||||
|
|
||||||
|
- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
|
||||||
|
bun run --inspect=ws://localhost:6499/ src/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until a debugger attaches.
|
||||||
|
- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Run `bun typecheck` from `packages/cli` after CLI adapter changes.
|
||||||
|
- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root.
|
||||||
|
- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state.
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"sst": "catalog:",
|
"sst": "catalog:",
|
||||||
"turbo": "2.10.2",
|
"turbo": "2.10.2",
|
||||||
"vitest": "4.1.10",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ai": {
|
"packages/ai": {
|
||||||
@@ -63,7 +62,6 @@
|
|||||||
"@dnd-kit/solid": "0.5.0",
|
"@dnd-kit/solid": "0.5.0",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
"@opencode-ai/session-ui": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -121,6 +119,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "1.2.1",
|
"@agentclientprotocol/sdk": "1.2.1",
|
||||||
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
@@ -591,9 +590,9 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opencode-ai/theme": "workspace:*",
|
"@opencode-ai/theme": "workspace:*",
|
||||||
"@opentui/core": ">=0.5.3",
|
"@opentui/core": ">=0.5.4",
|
||||||
"@opentui/keymap": ">=0.5.3",
|
"@opentui/keymap": ">=0.5.4",
|
||||||
"@opentui/solid": ">=0.5.3",
|
"@opentui/solid": ">=0.5.4",
|
||||||
"solid-js": ">=1.9.0",
|
"solid-js": ">=1.9.0",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
@@ -686,9 +685,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
|
||||||
"@opencode-ai/sdk": "file:../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@shikijs/stream": "catalog:",
|
"@shikijs/stream": "catalog:",
|
||||||
"@solid-primitives/event-listener": "2.4.5",
|
"@solid-primitives/event-listener": "2.4.5",
|
||||||
@@ -1090,9 +1088,9 @@
|
|||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@opentui/core": "0.5.3",
|
"@opentui/core": "0.5.4",
|
||||||
"@opentui/keymap": "0.5.3",
|
"@opentui/keymap": "0.5.4",
|
||||||
"@opentui/solid": "0.5.3",
|
"@opentui/solid": "0.5.4",
|
||||||
"@pierre/diffs": "1.2.10",
|
"@pierre/diffs": "1.2.10",
|
||||||
"@playwright/test": "1.59.1",
|
"@playwright/test": "1.59.1",
|
||||||
"@sentry/solid": "10.36.0",
|
"@sentry/solid": "10.36.0",
|
||||||
@@ -2114,27 +2112,27 @@
|
|||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.5.3", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.3", "@opentui/core-darwin-x64": "0.5.3", "@opentui/core-linux-arm64": "0.5.3", "@opentui/core-linux-arm64-musl": "0.5.3", "@opentui/core-linux-x64": "0.5.3", "@opentui/core-linux-x64-musl": "0.5.3", "@opentui/core-win32-arm64": "0.5.3", "@opentui/core-win32-x64": "0.5.3" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-K8EQu44cx0rhnn3v3baCQW18Bpci3GltZayOwVpGGsbiAGL1WUYqwQjuaWsmS0c4dCa9rQ5xCEoHB1C4936nDg=="],
|
"@opentui/core": ["@opentui/core@0.5.4", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.4", "@opentui/core-darwin-x64": "0.5.4", "@opentui/core-linux-arm64": "0.5.4", "@opentui/core-linux-arm64-musl": "0.5.4", "@opentui/core-linux-x64": "0.5.4", "@opentui/core-linux-x64-musl": "0.5.4", "@opentui/core-win32-arm64": "0.5.4", "@opentui/core-win32-x64": "0.5.4" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-czcJKQ72QhTWvu1eWfKg4EPN1GLLND6cP9MOhDyqYzCdIZgxQSJVWYzz6c4/CQGOu/qQLRyce2y1efVu4lgQ0w=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R39YeUqaMb/rH1h6G4MkB4MLVKIrRaUaXLfVqorZM4xgU5BxnfPetRk1vWR9vuLCvDwskg+kQ589kULw0o6AWA=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oETbn6tg/0g+mOvGXy3iot+1Zv7CGr65U1lRaPJ7kpsKGnOxftCpDD1qA0I6eQwfPJa9k7h9D/9yGB3IbweGcg=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-1pmUas/chTVFGeiN19kaOx+5Xbte/DLhcgKyACwWO0M3+xE3z1v/6QGSyX6CoP5HBpmDroiX+JHv1ic/JlGd/g=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-TIeqCNfAV8xvNAv6oVBYsoGBz/p8CxcYm668OQIeBPUO+irqbQ72vJJa/SwFZrUEAHFmsDELaB6y80oHU5Jm6g=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nMo9Q9VIaQVdw2SNKlwIEWMmf3z+cI4jRdCkh36e2RU1FO7LrIBAEmV1ZuRp1CIFVGPkqCXIizCeckZHTr4yQ=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-UrOsX3D5BOO9TI30WvRwEK/lyPPhY75+LwSqeRRe8SV2ON+Ez5QqY4oryTyYC6+yNahTJufz34n0YgwnQaxTNA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-QOYAxbbWrhYo27Cd6m0ATzpEx9YCAKAq82LgfUn0xu+VKXLNu+Q3hMNSVbG0SepUQQZil5rz228R9o9Cs8995w=="],
|
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-8vFWd1dsPZj9fHQKlsGHue3ukp7MRjTSpmIrdneIruOcoK2etccHfd6bqIo4FcNr+HA0eI2FP4ZIL9xhE3r9/w=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-hdAYLriLpTj3lvpMyL25GPBzvM2w/n2KCSbIwTmgS2F/dPZYCKJxHEETj2lCvtStSp7KuY8tkg3Xl5RAq1v7gA=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-1RXzSl6d347O7mUXviXFWlFFyILr7qsVt4jwD3k0UiKtENeEDNi+glM1bKHbXwCdQjfA835QgFA2mbILybK+aQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-BkVIiPQ1TOf5/FfmIpf7DQU5rT/FO6ASW5R/o/wonI5Pdul7XiDCu86gzGyk1x5k9Sbh6GLeq1fe8/tPmI7IaA=="],
|
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YYREqUB3v5K0qWij3A5YWzJkkVXp/EqIiuHZKsAjrUAY/0+Bp8Hn0baLvvvkuklpG9whW+GfwIeUsmp4fxqmCA=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-AjObTyZPU0xsK3Yk8GmhkboK6OcMoHBbydqYAybeHD4+v6axScSuZ3OEI9J05JJ9T7H2nZNky75tsdnjsvZJmg=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-F9sB6suPJmwkLF2MwKEC+OtwP6cn5QspIm4MCh2hmu3mVqSz9GDpYID1X3sAh9/V79EVocqiOSqI3KeCTRouKQ=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.3", "", { "os": "win32", "cpu": "x64" }, "sha512-e3nRlF2nSkLKCUPBF32OL9EDgtQDIh2pBo7tjhumpTyJ3qoNOa3us7DsM290Vw4xnakM6jpk9r9NzRf72CuMVg=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-2/6dPPPJ9xL/bWz9jh+lZeV2g/tbxZ9N4FBIqwhnqSfIYy5jOnscsPZpcN4X6PstfJAo2yf0Ebk/tLTOzOS6TQ=="],
|
||||||
|
|
||||||
"@opentui/keymap": ["@opentui/keymap@0.5.3", "", { "dependencies": { "@opentui/core": "0.5.3" }, "peerDependencies": { "@opentui/react": "0.5.3", "@opentui/solid": "0.5.3", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-VdvUabUDWnmgolzp1qiO55wz1zv0mMu5Z6tv+UQk9JqZRfKt5Q8XTDi0uQzhBb6qeLsHvXtRFOsnn6EFAcLixA=="],
|
"@opentui/keymap": ["@opentui/keymap@0.5.4", "", { "dependencies": { "@opentui/core": "0.5.4" }, "peerDependencies": { "@opentui/react": "0.5.4", "@opentui/solid": "0.5.4", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-WAB6EH6C7SicmK/ycvx+mUu00sQ755gToZqBx8vgLhS7qZ07s/WLi88Ys0D6AA0i8lOiSIPA4uqei1jkV8UnBQ=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.5.3", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.3", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-IB6YC3ajucQeo06BMSNYRD7LHf8ndm6G5ETcYsS+51Mli5yW0ioyUr+6+qgqVtaQyOcJvaD1KWmK23pXLhsiwg=="],
|
"@opentui/solid": ["@opentui/solid@0.5.4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.4", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-1injDvieTyYXD3RLC2EclaJK8XZ2+qyJ7wGeQPSzpcYEEcanb7G79KVWFlN7YUzqVAhTIictZDoQ6SRvIwL4Ww=="],
|
||||||
|
|
||||||
"@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
"@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
||||||
|
|
||||||
@@ -3232,7 +3230,7 @@
|
|||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||||
|
|
||||||
"@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
|
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||||
|
|
||||||
"@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="],
|
"@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="],
|
||||||
|
|
||||||
@@ -3242,7 +3240,7 @@
|
|||||||
|
|
||||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="],
|
"@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="],
|
||||||
|
|
||||||
"@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
|
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||||
|
|
||||||
"@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="],
|
"@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="],
|
||||||
|
|
||||||
@@ -3544,7 +3542,7 @@
|
|||||||
|
|
||||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||||
|
|
||||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||||
|
|
||||||
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
"chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="],
|
||||||
|
|
||||||
@@ -3948,7 +3946,7 @@
|
|||||||
|
|
||||||
"es-get-iterator": ["es-get-iterator@1.1.3", "", { "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", "is-string": "^1.0.7", "isarray": "^2.0.5", "stop-iteration-iterator": "^1.0.0" } }, "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw=="],
|
"es-get-iterator": ["es-get-iterator@1.1.3", "", { "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", "is-string": "^1.0.7", "isarray": "^2.0.5", "stop-iteration-iterator": "^1.0.0" } }, "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw=="],
|
||||||
|
|
||||||
"es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||||
|
|
||||||
"es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
|
"es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
|
||||||
|
|
||||||
@@ -5620,7 +5618,7 @@
|
|||||||
|
|
||||||
"tinyclip": ["tinyclip@0.1.15", "", {}, "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A=="],
|
"tinyclip": ["tinyclip@0.1.15", "", {}, "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||||
|
|
||||||
@@ -6054,6 +6052,8 @@
|
|||||||
|
|
||||||
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Q07lZsq4ir+xwAGVfSbY2WNGLrbfWINFaL2I/vTBFrkuXeP7VZh+UtQgLOKZS6Z/6flNFW22jDYdbINvwTV/PA=="],
|
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Q07lZsq4ir+xwAGVfSbY2WNGLrbfWINFaL2I/vTBFrkuXeP7VZh+UtQgLOKZS6Z/6flNFW22jDYdbINvwTV/PA=="],
|
||||||
|
|
||||||
|
"@antfu/install-pkg/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"@astrojs/cloudflare/vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
|
"@astrojs/cloudflare/vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
|
||||||
|
|
||||||
"@astrojs/cloudflare/wrangler": ["wrangler@4.110.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260708.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260708.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260708.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg=="],
|
"@astrojs/cloudflare/wrangler": ["wrangler@4.110.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260708.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260708.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260708.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg=="],
|
||||||
@@ -6070,8 +6070,6 @@
|
|||||||
|
|
||||||
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="],
|
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="],
|
||||||
|
|
||||||
"@astrojs/mdx/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
|
||||||
|
|
||||||
"@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"@astrojs/node/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.2", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.3.0", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ=="],
|
"@astrojs/node/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.2", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.3.0", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ=="],
|
||||||
@@ -6370,8 +6368,6 @@
|
|||||||
|
|
||||||
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
||||||
|
|
||||||
"@opencode-ai/session-ui/@opencode-ai/sdk": ["@opencode-ai/sdk@../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-C2nfk4x0sPINwE5V6DPkFSuH3PkUmKPWHPzxpXC1j+3Ui5hslLCWJbkk8WcOG1Lyt3C0+yp4ea64v/kmtYCO4w=="],
|
|
||||||
|
|
||||||
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||||
|
|
||||||
"@opencode-ai/storybook/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
|
"@opencode-ai/storybook/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
|
||||||
@@ -6470,8 +6466,6 @@
|
|||||||
|
|
||||||
"@slack/web-api/p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
"@slack/web-api/p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
||||||
|
|
||||||
"@solidjs/start/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
|
||||||
|
|
||||||
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||||
|
|
||||||
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||||
@@ -6542,6 +6536,12 @@
|
|||||||
|
|
||||||
"@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
"@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||||
|
|
||||||
|
"@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||||
|
|
||||||
|
"@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||||
|
|
||||||
|
"@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
|
||||||
|
|
||||||
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
|
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
|
||||||
|
|
||||||
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.127", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.31", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SiD2kyR2J4fEY0FTXYpNznwWN90ohrwzAMi2laZCGkI/lL8DmyJ31zFzMYJvzRusp2IQYmvkYQWOpZpBl01xYw=="],
|
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.127", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.31", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SiD2kyR2J4fEY0FTXYpNznwWN90ohrwzAMi2laZCGkI/lL8DmyJ31zFzMYJvzRusp2IQYmvkYQWOpZpBl01xYw=="],
|
||||||
@@ -6580,14 +6580,10 @@
|
|||||||
|
|
||||||
"astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
|
"astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
|
||||||
|
|
||||||
"astro/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
|
||||||
|
|
||||||
"astro/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
"astro/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
||||||
|
|
||||||
"astro/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
|
"astro/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
|
||||||
|
|
||||||
"astro/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
|
||||||
|
|
||||||
"astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
|
|
||||||
"astro/vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
|
"astro/vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
|
||||||
@@ -6878,10 +6874,6 @@
|
|||||||
|
|
||||||
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
||||||
|
|
||||||
"storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
|
||||||
|
|
||||||
"storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
|
||||||
|
|
||||||
"storybook/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"storybook/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
"storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
"storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||||
@@ -6932,11 +6924,15 @@
|
|||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
|
||||||
|
|
||||||
"vite-plugin-dynamic-import/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
|
||||||
|
|
||||||
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
||||||
|
|
||||||
"vite-plugin-icons-spritesheet/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
"vitest/@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
|
||||||
|
|
||||||
|
"vitest/@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
|
||||||
|
|
||||||
|
"vitest/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
|
"vitest/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"vitest/vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="],
|
"vitest/vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="],
|
||||||
|
|
||||||
@@ -7028,6 +7024,8 @@
|
|||||||
|
|
||||||
"@astrojs/node/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
"@astrojs/node/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
|
"@astrojs/node/astro/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
"@astrojs/node/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"@astrojs/node/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
"@astrojs/node/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
"@astrojs/node/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
||||||
@@ -7046,6 +7044,8 @@
|
|||||||
|
|
||||||
"@astrojs/node/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
"@astrojs/node/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
||||||
|
|
||||||
|
"@astrojs/node/astro/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"@astrojs/node/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
"@astrojs/node/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
||||||
|
|
||||||
"@astrojs/node/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"@astrojs/node/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
@@ -7082,6 +7082,8 @@
|
|||||||
|
|
||||||
"@astrojs/vercel/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
"@astrojs/vercel/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
|
"@astrojs/vercel/astro/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
"@astrojs/vercel/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
"@astrojs/vercel/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
||||||
|
|
||||||
"@astrojs/vercel/astro/get-tsconfig": ["get-tsconfig@5.0.0-beta.4", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ=="],
|
"@astrojs/vercel/astro/get-tsconfig": ["get-tsconfig@5.0.0-beta.4", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ=="],
|
||||||
@@ -7098,6 +7100,8 @@
|
|||||||
|
|
||||||
"@astrojs/vercel/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
"@astrojs/vercel/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
||||||
|
|
||||||
|
"@astrojs/vercel/astro/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"@astrojs/vercel/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
"@astrojs/vercel/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
||||||
|
|
||||||
"@astrojs/vercel/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"@astrojs/vercel/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
@@ -7456,6 +7460,8 @@
|
|||||||
|
|
||||||
"@opencode-ai/www/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
"@opencode-ai/www/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
|
"@opencode-ai/www/astro/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
"@opencode-ai/www/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"@opencode-ai/www/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
"@opencode-ai/www/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
"@opencode-ai/www/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
||||||
@@ -7474,6 +7480,8 @@
|
|||||||
|
|
||||||
"@opencode-ai/www/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
"@opencode-ai/www/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
||||||
|
|
||||||
|
"@opencode-ai/www/astro/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"@opencode-ai/www/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
"@opencode-ai/www/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
||||||
|
|
||||||
"@opencode-ai/www/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"@opencode-ai/www/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
@@ -7582,6 +7590,8 @@
|
|||||||
|
|
||||||
"@vercel/routing-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
"@vercel/routing-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||||
|
|
||||||
|
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
|
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
|
||||||
@@ -7646,6 +7656,8 @@
|
|||||||
|
|
||||||
"blume/@astrojs/mdx/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
|
"blume/@astrojs/mdx/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
|
||||||
|
|
||||||
|
"blume/@astrojs/mdx/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
"blume/@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"blume/@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"blume/@clack/prompts/@clack/core": ["@clack/core@1.4.3", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ=="],
|
"blume/@clack/prompts/@clack/core": ["@clack/core@1.4.3", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ=="],
|
||||||
@@ -7676,6 +7688,8 @@
|
|||||||
|
|
||||||
"blume/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
"blume/astro/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||||
|
|
||||||
|
"blume/astro/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||||
|
|
||||||
"blume/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"blume/astro/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
"blume/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
"blume/astro/fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
||||||
@@ -7688,6 +7702,8 @@
|
|||||||
|
|
||||||
"blume/astro/p-queue": ["p-queue@9.3.3", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA=="],
|
"blume/astro/p-queue": ["p-queue@9.3.3", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA=="],
|
||||||
|
|
||||||
|
"blume/astro/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
|
||||||
|
|
||||||
"blume/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
"blume/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
||||||
|
|
||||||
"blume/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"blume/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
@@ -7824,12 +7840,6 @@
|
|||||||
|
|
||||||
"rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
"rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||||
|
|
||||||
"storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
|
||||||
|
|
||||||
"storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
|
||||||
|
|
||||||
"storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
|
||||||
|
|
||||||
"storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
"storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
|
|
||||||
"storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
"storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||||
@@ -7902,6 +7912,8 @@
|
|||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Q07lZsq4ir+xwAGVfSbY2WNGLrbfWINFaL2I/vTBFrkuXeP7VZh+UtQgLOKZS6Z/6flNFW22jDYdbINvwTV/PA=="],
|
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.42", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Q07lZsq4ir+xwAGVfSbY2WNGLrbfWINFaL2I/vTBFrkuXeP7VZh+UtQgLOKZS6Z/6flNFW22jDYdbINvwTV/PA=="],
|
||||||
|
|
||||||
|
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||||
|
|
||||||
"vitest/vite/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"vitest/vite/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
"vitest/vite/lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
"vitest/vite/lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||||
@@ -8784,8 +8796,6 @@
|
|||||||
|
|
||||||
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
"storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
|
||||||
|
|
||||||
"temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
"temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||||
|
|
||||||
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|||||||
+4
-5
@@ -47,9 +47,9 @@
|
|||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/standard-validator": "0.2.0",
|
"@hono/standard-validator": "0.2.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@opentui/core": "0.5.3",
|
"@opentui/core": "0.5.4",
|
||||||
"@opentui/keymap": "0.5.3",
|
"@opentui/keymap": "0.5.4",
|
||||||
"@opentui/solid": "0.5.3",
|
"@opentui/solid": "0.5.4",
|
||||||
"@tanstack/solid-virtual": "3.13.32",
|
"@tanstack/solid-virtual": "3.13.32",
|
||||||
"@shikijs/stream": "4.2.0",
|
"@shikijs/stream": "4.2.0",
|
||||||
"@standard-schema/spec": "1.1.0",
|
"@standard-schema/spec": "1.1.0",
|
||||||
@@ -121,8 +121,7 @@
|
|||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"sst": "catalog:",
|
"sst": "catalog:",
|
||||||
"turbo": "2.10.2",
|
"turbo": "2.10.2"
|
||||||
"vitest": "4.1.10"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.933.0",
|
"@aws-sdk/client-s3": "3.933.0",
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ If you find yourself copying a 3-to-5-line snippet between two protocols, lift i
|
|||||||
|
|
||||||
`LLMRequest.system` is the initial privileged prompt that applies ahead of the conversation. `Message.system(...)` is a separate, provider-neutral chronological operator update inside `LLMRequest.messages`; it applies only from its position in history onward and accepts text content only.
|
`LLMRequest.system` is the initial privileged prompt that applies ahead of the conversation. `Message.system(...)` is a separate, provider-neutral chronological operator update inside `LLMRequest.messages`; it applies only from its position in history onward and accepts text content only.
|
||||||
|
|
||||||
Native chronological system messages are route/model-specific. Anthropic Messages lowers them natively for Claude Opus 4.8 (`claude-opus-4-8`). Other routes and models intentionally lower the update in place into ordinary user-compatible text using this stable escaped representation:
|
Native chronological system messages are route/model-specific. Open Responses lowers them to standard `developer` messages, while Anthropic Messages lowers them to native system messages for Claude Opus 4.8 (`claude-opus-4-8`). Other routes and models intentionally lower the update in place into ordinary user-compatible text using this stable escaped representation:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
<system-update>
|
<system-update>
|
||||||
|
|||||||
@@ -437,10 +437,19 @@ const lowerToolResultContent = Effect.fnUntraced(function* (part: ToolResultPart
|
|||||||
return yield* Effect.forEach(content, lowerToolResultContentItem)
|
return yield* Effect.forEach(content, lowerToolResultContentItem)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mid-conversation system messages are a native Claude API feature only for
|
// Mid-conversation system messages became available with Opus 4.8 and version
|
||||||
// Opus 4.8. Other Anthropic models intentionally use the same visible wrapped-
|
// 5 of the other supported Claude families. Treat later family versions as
|
||||||
// user fallback as non-Anthropic routes rather than sending a role they reject.
|
// compatible without assuming that every Anthropic Messages model is Claude.
|
||||||
const supportsNativeSystemUpdates = (request: LLMRequest) => String(request.model.id) === "claude-opus-4-8"
|
const supportsNativeSystemUpdates = (request: LLMRequest) => {
|
||||||
|
const match = /(?:^|[./])claude-(fable|haiku|mythos|opus|sonnet)-(\d+)(?:[.-](\d+))?/.exec(
|
||||||
|
String(request.model.id).toLowerCase(),
|
||||||
|
)
|
||||||
|
if (!match) return false
|
||||||
|
const major = Number(match[2])
|
||||||
|
if (match[1] !== "opus") return major >= 5
|
||||||
|
if (major !== 4) return major >= 5
|
||||||
|
return match[3] !== undefined && match[3].length <= 2 && Number(match[3]) >= 8
|
||||||
|
}
|
||||||
|
|
||||||
const endsInServerToolUse = (message: LLMRequest["messages"][number]) => {
|
const endsInServerToolUse = (message: LLMRequest["messages"][number]) => {
|
||||||
const last = message.content.at(-1)
|
const last = message.content.at(-1)
|
||||||
@@ -896,6 +905,7 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
|
|||||||
|
|
||||||
if (delta?.type === "input_json_delta" && event.index !== undefined) {
|
if (delta?.type === "input_json_delta" && event.index !== undefined) {
|
||||||
if (!delta.partial_json) return [state, NO_EVENTS] satisfies StepResult
|
if (!delta.partial_json) return [state, NO_EVENTS] satisfies StepResult
|
||||||
|
if (!state.tools[event.index]) return [state, NO_EVENTS] satisfies StepResult
|
||||||
const result = ToolStream.appendExisting(
|
const result = ToolStream.appendExisting(
|
||||||
ADAPTER,
|
ADAPTER,
|
||||||
state.tools,
|
state.tools,
|
||||||
@@ -957,9 +967,12 @@ const onMessageDelta = (state: ParserState, event: AnthropicEvent): StepResult =
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMessageStop = (state: ParserState): StepResult => {
|
const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state: ParserState) {
|
||||||
|
const result = yield* ToolStream.finishAll(ADAPTER, state.tools)
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
|
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle
|
||||||
|
events.push(...result.events)
|
||||||
|
const finished = Lifecycle.finish(lifecycle, events, {
|
||||||
reason: state.pendingFinish?.reason ?? {
|
reason: state.pendingFinish?.reason ?? {
|
||||||
normalized: "unknown",
|
normalized: "unknown",
|
||||||
raw: undefined,
|
raw: undefined,
|
||||||
@@ -967,8 +980,8 @@ const onMessageStop = (state: ParserState): StepResult => {
|
|||||||
usage: state.usage,
|
usage: state.usage,
|
||||||
providerMetadata: state.pendingFinish?.providerMetadata,
|
providerMetadata: state.pendingFinish?.providerMetadata,
|
||||||
})
|
})
|
||||||
return [{ ...state, lifecycle }, events]
|
return [{ ...state, lifecycle: finished, tools: result.tools }, events] satisfies StepResult
|
||||||
}
|
})
|
||||||
|
|
||||||
// Prefix `error.type` so overloads, rate limits, and quota errors are visible
|
// Prefix `error.type` so overloads, rate limits, and quota errors are visible
|
||||||
// even when the provider message is generic or empty.
|
// even when the provider message is generic or empty.
|
||||||
@@ -992,7 +1005,7 @@ const step = (state: ParserState, event: AnthropicEvent) => {
|
|||||||
if (event.type === "content_block_delta") return onContentBlockDelta(state, event)
|
if (event.type === "content_block_delta") return onContentBlockDelta(state, event)
|
||||||
if (event.type === "content_block_stop") return onContentBlockStop(state, event)
|
if (event.type === "content_block_stop") return onContentBlockStop(state, event)
|
||||||
if (event.type === "message_delta") return Effect.succeed(onMessageDelta(state, event))
|
if (event.type === "message_delta") return Effect.succeed(onMessageDelta(state, event))
|
||||||
if (event.type === "message_stop") return Effect.succeed(onMessageStop(state))
|
if (event.type === "message_stop") return onMessageStop(state)
|
||||||
if (event.type === "error") return onError(event)
|
if (event.type === "error") return onError(event)
|
||||||
return Effect.succeed<StepResult>([state, NO_EVENTS])
|
return Effect.succeed<StepResult>([state, NO_EVENTS])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const OpenResponsesFunctionCallOutput = Schema.Union([
|
|||||||
|
|
||||||
export const InputItem = Schema.Union([
|
export const InputItem = Schema.Union([
|
||||||
Schema.Struct({ role: Schema.tag("system"), content: Schema.String }),
|
Schema.Struct({ role: Schema.tag("system"), content: Schema.String }),
|
||||||
|
Schema.Struct({ role: Schema.tag("developer"), content: Schema.String }),
|
||||||
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenResponsesInputContent) }),
|
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenResponsesInputContent) }),
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
role: Schema.tag("assistant"),
|
role: Schema.tag("assistant"),
|
||||||
@@ -153,6 +154,7 @@ export const coreFields = {
|
|||||||
tools: optionalArray(Tool),
|
tools: optionalArray(Tool),
|
||||||
tool_choice: Schema.optional(ToolChoice),
|
tool_choice: Schema.optional(ToolChoice),
|
||||||
store: Schema.optional(Schema.Boolean),
|
store: Schema.optional(Schema.Boolean),
|
||||||
|
truncation: Schema.optional(OpenResponsesOptions.TruncationSchema),
|
||||||
service_tier: Schema.optional(OpenResponsesOptions.ServiceTierSchema),
|
service_tier: Schema.optional(OpenResponsesOptions.ServiceTierSchema),
|
||||||
prompt_cache_key: Schema.optional(Schema.String),
|
prompt_cache_key: Schema.optional(Schema.String),
|
||||||
include: optionalArray(OpenResponsesOptions.ResponseIncludableSchema),
|
include: optionalArray(OpenResponsesOptions.ResponseIncludableSchema),
|
||||||
@@ -439,14 +441,10 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
|
|||||||
|
|
||||||
for (const message of request.messages) {
|
for (const message of request.messages) {
|
||||||
if (message.role === "system") {
|
if (message.role === "system") {
|
||||||
const part = yield* ProviderShared.wrappedSystemUpdate(extension.name, message)
|
input.push({
|
||||||
const previous = input.at(-1)
|
role: "developer",
|
||||||
if (previous && "role" in previous && previous.role === "user")
|
content: ProviderShared.joinText(yield* ProviderShared.systemUpdateText(extension.name, message)),
|
||||||
input[input.length - 1] = {
|
})
|
||||||
role: "user",
|
|
||||||
content: [...previous.content, { type: "input_text", text: part.text }],
|
|
||||||
}
|
|
||||||
else input.push({ role: "user", content: [{ type: "input_text", text: part.text }] })
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,6 +578,7 @@ const lowerOptions = (request: LLMRequest) => {
|
|||||||
: {}),
|
: {}),
|
||||||
...(options.textVerbosity ? { text: { verbosity: options.textVerbosity } } : {}),
|
...(options.textVerbosity ? { text: { verbosity: options.textVerbosity } } : {}),
|
||||||
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
|
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
|
||||||
|
...(options.truncation ? { truncation: options.truncation } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,19 +16,25 @@ export type ResponseIncludable = (typeof ResponseIncludables)[number]
|
|||||||
export const ServiceTiers = ["auto", "default", "flex", "priority"] as const
|
export const ServiceTiers = ["auto", "default", "flex", "priority"] as const
|
||||||
export type ServiceTier = (typeof ServiceTiers)[number]
|
export type ServiceTier = (typeof ServiceTiers)[number]
|
||||||
|
|
||||||
|
export const Truncations = ["auto", "disabled"] as const
|
||||||
|
export type Truncation = (typeof Truncations)[number]
|
||||||
|
|
||||||
const TEXT_VERBOSITY = new Set<string>(["low", "medium", "high"])
|
const TEXT_VERBOSITY = new Set<string>(["low", "medium", "high"])
|
||||||
const INCLUDABLES = new Set<string>(ResponseIncludables)
|
const INCLUDABLES = new Set<string>(ResponseIncludables)
|
||||||
const SERVICE_TIERS = new Set<string>(ServiceTiers)
|
const SERVICE_TIERS = new Set<string>(ServiceTiers)
|
||||||
|
const TRUNCATIONS = new Set<string>(Truncations)
|
||||||
|
|
||||||
const isTextVerbosity = (value: unknown): value is Schema.Schema.Type<typeof TextVerbosity> =>
|
const isTextVerbosity = (value: unknown): value is Schema.Schema.Type<typeof TextVerbosity> =>
|
||||||
typeof value === "string" && TEXT_VERBOSITY.has(value)
|
typeof value === "string" && TEXT_VERBOSITY.has(value)
|
||||||
|
|
||||||
const isServiceTier = (value: unknown): value is ServiceTier => typeof value === "string" && SERVICE_TIERS.has(value)
|
const isServiceTier = (value: unknown): value is ServiceTier => typeof value === "string" && SERVICE_TIERS.has(value)
|
||||||
|
const isTruncation = (value: unknown): value is Truncation => typeof value === "string" && TRUNCATIONS.has(value)
|
||||||
|
|
||||||
export const ReasoningEffort = Schema.String
|
export const ReasoningEffort = Schema.String
|
||||||
export const TextVerbositySchema = TextVerbosity
|
export const TextVerbositySchema = TextVerbosity
|
||||||
export const ResponseIncludableSchema = Schema.Literals(ResponseIncludables)
|
export const ResponseIncludableSchema = Schema.Literals(ResponseIncludables)
|
||||||
export const ServiceTierSchema = Schema.Literals(ServiceTiers)
|
export const ServiceTierSchema = Schema.Literals(ServiceTiers)
|
||||||
|
export const TruncationSchema = Schema.Literals(Truncations)
|
||||||
|
|
||||||
export interface Resolved {
|
export interface Resolved {
|
||||||
readonly instructions?: string
|
readonly instructions?: string
|
||||||
@@ -38,6 +44,7 @@ export interface Resolved {
|
|||||||
readonly include?: ReadonlyArray<ResponseIncludable>
|
readonly include?: ReadonlyArray<ResponseIncludable>
|
||||||
readonly textVerbosity?: Schema.Schema.Type<typeof TextVerbosity>
|
readonly textVerbosity?: Schema.Schema.Type<typeof TextVerbosity>
|
||||||
readonly serviceTier?: ServiceTier
|
readonly serviceTier?: ServiceTier
|
||||||
|
readonly truncation?: Truncation
|
||||||
}
|
}
|
||||||
|
|
||||||
export const resolve = (request: LLMRequest): Resolved => {
|
export const resolve = (request: LLMRequest): Resolved => {
|
||||||
@@ -57,6 +64,7 @@ export const resolve = (request: LLMRequest): Resolved => {
|
|||||||
include: include.length > 0 ? include : undefined,
|
include: include.length > 0 ? include : undefined,
|
||||||
textVerbosity: isTextVerbosity(input?.textVerbosity) ? input.textVerbosity : undefined,
|
textVerbosity: isTextVerbosity(input?.textVerbosity) ? input.textVerbosity : undefined,
|
||||||
serviceTier: isServiceTier(input?.serviceTier) ? input.serviceTier : undefined,
|
serviceTier: isServiceTier(input?.serviceTier) ? input.serviceTier : undefined,
|
||||||
|
truncation: isTruncation(input?.truncation) ? input.truncation : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ResponseIncludable, ServiceTier } from "../protocols/utils/open-responses-options.js"
|
import type { ResponseIncludable, ServiceTier, Truncation } from "../protocols/utils/open-responses-options.js"
|
||||||
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema/index.js"
|
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema/index.js"
|
||||||
|
|
||||||
export interface OpenResponsesOptionsInput {
|
export interface OpenResponsesOptionsInput {
|
||||||
@@ -10,6 +10,7 @@ export interface OpenResponsesOptionsInput {
|
|||||||
readonly include?: ReadonlyArray<ResponseIncludable>
|
readonly include?: ReadonlyArray<ResponseIncludable>
|
||||||
readonly textVerbosity?: TextVerbosity
|
readonly textVerbosity?: TextVerbosity
|
||||||
readonly serviceTier?: ServiceTier
|
readonly serviceTier?: ServiceTier
|
||||||
|
readonly truncation?: Truncation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OpenResponsesProviderOptionsInput = ProviderOptions & {
|
export type OpenResponsesProviderOptionsInput = ProviderOptions & {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
type FinishReasonDetails,
|
type FinishReasonDetails,
|
||||||
type AIError,
|
type AIError,
|
||||||
type LLMRequest,
|
type LLMRequest,
|
||||||
|
type ProviderMetadata,
|
||||||
type UsageInput,
|
type UsageInput,
|
||||||
} from "./schema/index.js"
|
} from "./schema/index.js"
|
||||||
import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect"
|
import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect"
|
||||||
@@ -33,13 +34,22 @@ export interface LayerOptions {
|
|||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ai/TestLLM") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/ai/TestLLM") {}
|
||||||
|
|
||||||
export const complete = (
|
export const complete = (
|
||||||
options: { readonly reason: FinishReasonDetails; readonly usage?: UsageInput },
|
options: {
|
||||||
|
readonly reason: FinishReasonDetails
|
||||||
|
readonly usage?: UsageInput
|
||||||
|
readonly providerMetadata?: ProviderMetadata
|
||||||
|
},
|
||||||
...events: readonly LLMEvent[]
|
...events: readonly LLMEvent[]
|
||||||
) => [
|
) => [
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
...events,
|
...events,
|
||||||
LLMEvent.stepFinish({ index: 0, reason: options.reason, usage: options.usage }),
|
LLMEvent.stepFinish({
|
||||||
LLMEvent.finish({ reason: options.reason }),
|
index: 0,
|
||||||
|
reason: options.reason,
|
||||||
|
usage: options.usage,
|
||||||
|
providerMetadata: options.providerMetadata,
|
||||||
|
}),
|
||||||
|
LLMEvent.finish({ reason: options.reason, providerMetadata: options.providerMetadata }),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const stop = (...events: readonly LLMEvent[]) => complete({ reason: { normalized: "stop" } }, ...events)
|
export const stop = (...events: readonly LLMEvent[]) => complete({ reason: { normalized: "stop" } }, ...events)
|
||||||
|
|||||||
@@ -136,6 +136,33 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("supports native chronological system updates on documented and later Claude family versions", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const ids = [
|
||||||
|
"claude-opus-4-8",
|
||||||
|
"claude-opus-5-1",
|
||||||
|
"claude-sonnet-5",
|
||||||
|
"claude-haiku-5-1",
|
||||||
|
"claude-fable-6",
|
||||||
|
"anthropic/claude-mythos-7.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
const prepared = yield* Effect.forEach(ids, (id) =>
|
||||||
|
compileRequest(
|
||||||
|
LLM.request({
|
||||||
|
model: AnthropicMessages.route
|
||||||
|
.with({ endpoint: { baseURL: "https://api.anthropic.test/v1/" }, auth: Auth.header("x-api-key", "test") })
|
||||||
|
.model({ id }),
|
||||||
|
messages: [Message.user("Before."), Message.system("Update."), Message.assistant("After.")],
|
||||||
|
cache: "none",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.map((item) => item.body.messages[1]?.role)).toEqual(ids.map(() => "system"))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("lowers chronological system updates to wrapped user text for unsupported Anthropic models", () =>
|
it.effect("lowers chronological system updates to wrapped user text for unsupported Anthropic models", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
@@ -163,6 +190,34 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("does not infer native system update support for older or undocumented Claude families", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const ids = [
|
||||||
|
"claude-opus-4-7",
|
||||||
|
"claude-opus-4-20250514",
|
||||||
|
"claude-sonnet-4-9",
|
||||||
|
"claude-haiku-4-9",
|
||||||
|
"custom-model-7",
|
||||||
|
]
|
||||||
|
|
||||||
|
const prepared = yield* Effect.forEach(ids, (id) =>
|
||||||
|
compileRequest(
|
||||||
|
LLM.request({
|
||||||
|
model: AnthropicMessages.route
|
||||||
|
.with({ endpoint: { baseURL: "https://api.anthropic.test/v1/" }, auth: Auth.header("x-api-key", "test") })
|
||||||
|
.model({ id }),
|
||||||
|
messages: [Message.user("Before."), Message.system("Update."), Message.assistant("After.")],
|
||||||
|
cache: "none",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.map((item) => item.body.messages.some((message) => message.role === "system"))).toEqual(
|
||||||
|
ids.map(() => false),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("rejects non-text chronological system update content before send", () =>
|
it.effect("rejects non-text chronological system update content before send", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const error = yield* compileRequest(
|
||||||
@@ -955,6 +1010,65 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("ignores tool input deltas without a matching tool start", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ type: "message_start", message: { usage: { input_tokens: 5 } } },
|
||||||
|
{ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
|
||||||
|
{ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Hello" } },
|
||||||
|
{
|
||||||
|
type: "content_block_delta",
|
||||||
|
index: 1,
|
||||||
|
delta: { type: "input_json_delta", partial_json: '{"query":"orphaned"}' },
|
||||||
|
},
|
||||||
|
{ type: "content_block_stop", index: 0 },
|
||||||
|
{ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 1 } },
|
||||||
|
{ type: "message_stop" },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text).toBe("Hello")
|
||||||
|
expect(response.toolCalls).toEqual([])
|
||||||
|
expect(response.finishReason).toEqual({ normalized: "stop", raw: "end_turn" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("settles pending tool calls at message_stop", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ type: "message_start", message: { usage: { input_tokens: 5 } } },
|
||||||
|
{
|
||||||
|
type: "content_block_start",
|
||||||
|
index: 0,
|
||||||
|
content_block: { type: "tool_use", id: "call_1", name: "lookup" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "content_block_delta",
|
||||||
|
index: 0,
|
||||||
|
delta: { type: "input_json_delta", partial_json: '{"query":"weather"}' },
|
||||||
|
},
|
||||||
|
{ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { output_tokens: 1 } },
|
||||||
|
{ type: "message_stop" },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.toolCalls).toMatchObject([
|
||||||
|
{ id: "call_1", name: "lookup", input: { query: "weather" } },
|
||||||
|
])
|
||||||
|
expect(response.finishReason).toEqual({ normalized: "tool-calls", raw: "tool_use" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("assembles and persists multiple tool calls from one Anthropic response", () =>
|
it.effect("assembles and persists multiple tool calls from one Anthropic response", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const response = yield* LLMClient.generate(request).pipe(
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
|||||||
@@ -56,6 +56,28 @@ describe("Open Responses-compatible route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("lowers chronological system updates as standard developer messages", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const model = configure({
|
||||||
|
apiKey: "test-key",
|
||||||
|
baseURL: "https://responses.example.test/v1",
|
||||||
|
provider: "example",
|
||||||
|
}).model("example-model")
|
||||||
|
const prepared = yield* compileRequest(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [Message.user("Before."), Message.system("Operator update."), Message.assistant("After.")],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.input).toEqual([
|
||||||
|
{ role: "user", content: [{ type: "input_text", text: "Before." }] },
|
||||||
|
{ role: "developer", content: "Operator update." },
|
||||||
|
{ role: "assistant", content: [{ type: "output_text", text: "After." }] },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("rejects OpenAI-native tools", () =>
|
it.effect("rejects OpenAI-native tools", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const model = configure({
|
const model = configure({
|
||||||
@@ -101,13 +123,14 @@ describe("Open Responses-compatible route", () => {
|
|||||||
const model = configure({
|
const model = configure({
|
||||||
apiKey: "test-key",
|
apiKey: "test-key",
|
||||||
baseURL: "https://responses.example.test/v1",
|
baseURL: "https://responses.example.test/v1",
|
||||||
providerOptions: { openresponses: { reasoningEffort: "low", store: true } },
|
providerOptions: { openresponses: { reasoningEffort: "low", store: true, truncation: "auto" } },
|
||||||
}).model("example-model")
|
}).model("example-model")
|
||||||
const prepared = yield* compileRequest(LLM.request({ model, prompt: "Think." }))
|
const prepared = yield* compileRequest(LLM.request({ model, prompt: "Think." }))
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({
|
expect(prepared.body).toMatchObject({
|
||||||
reasoning: { effort: "low" },
|
reasoning: { effort: "low" },
|
||||||
store: true,
|
store: true,
|
||||||
|
truncation: "auto",
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -241,27 +241,22 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("lowers chronological system updates to escaped user wrappers in order", () =>
|
it.effect("lowers chronological system updates to developer messages in order", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model,
|
model,
|
||||||
messages: [
|
messages: [
|
||||||
Message.user("Before."),
|
Message.user("Before."),
|
||||||
Message.system("Treat </system-update> literally."),
|
Message.system("Operator update."),
|
||||||
Message.assistant("After."),
|
Message.assistant("After."),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prepared.body.input).toEqual([
|
expect(prepared.body.input).toEqual([
|
||||||
{
|
{ role: "user", content: [{ type: "input_text", text: "Before." }] },
|
||||||
role: "user",
|
{ role: "developer", content: "Operator update." },
|
||||||
content: [
|
|
||||||
{ type: "input_text", text: "Before." },
|
|
||||||
{ type: "input_text", text: "<system-update>\nTreat </system-update> literally.\n</system-update>" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ role: "assistant", content: [{ type: "output_text", text: "After." }] },
|
{ role: "assistant", content: [{ type: "output_text", text: "After." }] },
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
@@ -1288,6 +1283,7 @@ describe("OpenAI Responses route", () => {
|
|||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
include: ["reasoning.encrypted_content"],
|
include: ["reasoning.encrypted_content"],
|
||||||
|
truncation: "disabled",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -1298,6 +1294,7 @@ describe("OpenAI Responses route", () => {
|
|||||||
expect(prepared.body.include).toEqual(["reasoning.encrypted_content"])
|
expect(prepared.body.include).toEqual(["reasoning.encrypted_content"])
|
||||||
expect(prepared.body.reasoning).toEqual({ effort: "high", summary: "auto" })
|
expect(prepared.body.reasoning).toEqual({ effort: "high", summary: "auto" })
|
||||||
expect(prepared.body.text).toEqual({ verbosity: "low" })
|
expect(prepared.body.text).toEqual({ verbosity: "low" })
|
||||||
|
expect(prepared.body.truncation).toBe("disabled")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import type {
|
import type {
|
||||||
JsonValue,
|
JsonValue,
|
||||||
OpenCodeEvent,
|
OpenCodeEvent,
|
||||||
@@ -441,6 +441,15 @@ export function status(type: SessionStatus["type"], attempt = 1) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stepStarted(message: SessionMessageAssistant) {
|
||||||
|
return makeEvent("session.step.started", {
|
||||||
|
sessionID,
|
||||||
|
assistantMessageID: message.id,
|
||||||
|
agent: message.agent,
|
||||||
|
model: message.model,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function userMessage(
|
export function userMessage(
|
||||||
parts?: PartSeed<"user">[],
|
parts?: PartSeed<"user">[],
|
||||||
input: { id?: string; summary?: unknown; created?: number } = {},
|
input: { id?: string; summary?: unknown; created?: number } = {},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function installSessionSwitchProbe(
|
|||||||
let running = true
|
let running = true
|
||||||
const reviewLevels: Record<string, string> = {
|
const reviewLevels: Record<string, string> = {
|
||||||
panel: "#review-panel",
|
panel: "#review-panel",
|
||||||
tabs: '#review-panel [data-component="tabs"]',
|
tabs: '#review-panel [data-component="tabs"]',
|
||||||
body: '#review-panel [data-slot="session-review-v2-body"]',
|
body: '#review-panel [data-slot="session-review-v2-body"]',
|
||||||
review: '#review-panel [data-component="session-review-v2"]',
|
review: '#review-panel [data-component="session-review-v2"]',
|
||||||
preview: '#review-panel [data-slot="session-review-v2-preview"]',
|
preview: '#review-panel [data-slot="session-review-v2-preview"]',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import type { JsonValue, OpenCodeEvent, SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise"
|
import type { JsonValue, OpenCodeEvent, SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||||
import type { Page } from "@playwright/test"
|
import type { Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../../utils/mock-server"
|
import { mockOpenCodeServer } from "../../utils/mock-server"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Page } from "@playwright/test"
|
import type { Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { mockOpenCodeServer } from "../../utils/mock-server"
|
import { mockOpenCodeServer } from "../../utils/mock-server"
|
||||||
import { fixture, pageMessages } from "./session-timeline-stress.fixture"
|
import { fixture, pageMessages } from "./session-timeline-stress.fixture"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test"
|
import { expect, test } from "bun:test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { fixture } from "../timeline/session-timeline-stress.fixture"
|
import { fixture } from "../timeline/session-timeline-stress.fixture"
|
||||||
import { stressSessionHref } from "../timeline/timeline-test-helpers"
|
import { stressSessionHref } from "../timeline/timeline-test-helpers"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page, type Route } from "@playwright/test"
|
import { expect, test, type Page, type Route } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { currentSession } from "../utils/mock-server"
|
import { currentSession } from "../utils/mock-server"
|
||||||
import { installSseTransport } from "../utils/sse-transport"
|
import { installSseTransport } from "../utils/sse-transport"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test } from "@playwright/test"
|
import { expect, test } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "@playwright/test"
|
import { expect, test } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectAppVisible } from "../utils/waits"
|
import { expectAppVisible } from "../utils/waits"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectAppVisible } from "../utils/waits"
|
import { expectAppVisible } from "../utils/waits"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page, type Route } from "@playwright/test"
|
import { expect, test, type Page, type Route } from "@playwright/test"
|
||||||
import { installSseTransport } from "../utils/sse-transport"
|
import { installSseTransport } from "../utils/sse-transport"
|
||||||
import { currentSession } from "../utils/mock-server"
|
import { currentSession } from "../utils/mock-server"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page, type Route } from "@playwright/test"
|
import { expect, test, type Page, type Route } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { currentSession } from "../utils/mock-server"
|
import { currentSession } from "../utils/mock-server"
|
||||||
|
|
||||||
const serverA = "http://127.0.0.1:4096"
|
const serverA = "http://127.0.0.1:4096"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test } from "@playwright/test"
|
import { expect, test } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
import { expectAppVisible, expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { installSseTransport } from "../utils/sse-transport"
|
import { installSseTransport } from "../utils/sse-transport"
|
||||||
@@ -52,7 +52,7 @@ test("shows a pending question dock", async ({ page }) => {
|
|||||||
rejectRequests.push(request.url())
|
rejectRequests.push(request.url())
|
||||||
})
|
})
|
||||||
|
|
||||||
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
await question.getByRole("button", { name: "Minimize question" }).click()
|
||||||
await expect(question).toBeVisible()
|
await expect(question).toBeVisible()
|
||||||
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||||
await expect(question.getByText("Select one answer")).toBeHidden()
|
await expect(question.getByText("Select one answer")).toBeHidden()
|
||||||
@@ -63,7 +63,7 @@ test("shows a pending question dock", async ({ page }) => {
|
|||||||
await expect(page.locator('[data-component="question-minimized-dock"]')).toHaveCount(0)
|
await expect(page.locator('[data-component="question-minimized-dock"]')).toHaveCount(0)
|
||||||
expect(rejectRequests).toEqual([])
|
expect(rejectRequests).toEqual([])
|
||||||
|
|
||||||
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
await question.getByRole("button", { name: "Restore question" }).click()
|
||||||
await expect(question).toBeVisible()
|
await expect(question).toBeVisible()
|
||||||
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||||
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeVisible()
|
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeVisible()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import type { SessionMessageAssistant } from "@opencode-ai/client/promise"
|
import type { SessionMessageAssistant } from "@opencode-ai/client/promise"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
setupTimeline,
|
setupTimeline,
|
||||||
shell,
|
shell,
|
||||||
status,
|
status,
|
||||||
|
stepStarted,
|
||||||
textPart,
|
textPart,
|
||||||
userMessage,
|
userMessage,
|
||||||
} from "../performance/timeline-stability/fixture"
|
} from "../performance/timeline-stability/fixture"
|
||||||
@@ -96,8 +97,10 @@ test("moves busy through retry and recovery to final idle content", async ({ pag
|
|||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
||||||
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
||||||
await timeline.send(status("retry"), 180)
|
await timeline.send(status("retry"), 180)
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible()
|
||||||
await timeline.send(status("busy", 2), 180)
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
||||||
|
await timeline.send(stepStarted(assistant), 180)
|
||||||
|
await expect(page.locator('[data-timeline-row="Retry"]')).toHaveCount(0)
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
||||||
await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140)
|
await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140)
|
||||||
await timeline.send(messageUpdated(completedAssistantInfo(assistant)), 100)
|
await timeline.send(messageUpdated(completedAssistantInfo(assistant)), 100)
|
||||||
|
|||||||
@@ -131,15 +131,14 @@ test("labels V2 skill tools from IDs and result metadata", async ({ page }) => {
|
|||||||
"aria-label",
|
"aria-label",
|
||||||
"sample-skill",
|
"sample-skill",
|
||||||
)
|
)
|
||||||
await expect(page.locator(`[data-timeline-part-id="${completed}"] [data-component="text-shimmer"]`)).toHaveAttribute(
|
await expect(
|
||||||
"aria-label",
|
page.locator(`[data-timeline-part-id="${completed}"] [data-component="text-shimmer"]`),
|
||||||
"OpenCode",
|
).toHaveAttribute("aria-label", "OpenCode")
|
||||||
)
|
|
||||||
for (const id of [pending, completed]) {
|
for (const id of [pending, completed]) {
|
||||||
const skill = page.locator(`[data-timeline-part-id="${id}"]`)
|
const skill = page.locator(`[data-timeline-part-id="${id}"]`)
|
||||||
await expect(skill.locator('[data-slot="skill-tool-label"]')).toHaveText("Skill")
|
await expect(skill.locator('[data-slot="skill-tool-label"]')).toHaveText("Skill")
|
||||||
await expect(skill.locator('[data-slot="skill-tool-separator"]')).toHaveText("·")
|
await expect(skill.locator('[data-slot="skill-tool-separator"]')).toHaveText("·")
|
||||||
await expect(skill.locator('use[href="#opencode-icon-post-skill"]')).toBeVisible()
|
await expect(skill.locator('use[href="#opencode-v2-icon-post-skill"]')).toBeVisible()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import type { OpenCodeEvent, SessionMessageInfo } from "@opencode-ai/client/promise"
|
import type { OpenCodeEvent, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { currentSession, mockOpenCodeServer } from "../utils/mock-server"
|
import { currentSession, mockOpenCodeServer } from "../utils/mock-server"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page, type Route } from "@playwright/test"
|
import { expect, test, type Page, type Route } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { currentSession } from "../utils/mock-server"
|
import { currentSession } from "../utils/mock-server"
|
||||||
|
|
||||||
const server = "http://127.0.0.1:4096"
|
const server = "http://127.0.0.1:4096"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { fixture, pageMessages } from "./session-timeline.fixture"
|
import { fixture, pageMessages } from "./session-timeline.fixture"
|
||||||
import { trackPageErrors, expectNoSmokeErrors } from "../utils/errors"
|
import { trackPageErrors, expectNoSmokeErrors } from "../utils/errors"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
@@ -477,7 +477,7 @@ async function configureSmokePage(page: Page, directory: string) {
|
|||||||
}
|
}
|
||||||
let recordFrame: number | undefined
|
let recordFrame: number | undefined
|
||||||
const record = () => {
|
const record = () => {
|
||||||
for (const toast of document.querySelectorAll<HTMLElement>('[data-component="toast"][data-variant="error"]')) {
|
for (const toast of document.querySelectorAll<HTMLElement>(".toast-v2--error")) {
|
||||||
const text = toast.textContent?.trim()
|
const text = toast.textContent?.trim()
|
||||||
if (text && !smoke.__timelineSmokeErrorToasts!.includes(text)) smoke.__timelineSmokeErrorToasts!.push(text)
|
if (text && !smoke.__timelineSmokeErrorToasts!.includes(text)) smoke.__timelineSmokeErrorToasts!.push(text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, type Locator, type Page } from "@playwright/test"
|
import { expect, type Locator, type Page } from "@playwright/test"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
|
|
||||||
export const APP_READY_TIMEOUT = 30_000
|
export const APP_READY_TIMEOUT = 30_000
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
"@dnd-kit/solid": "0.5.0",
|
"@dnd-kit/solid": "0.5.0",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
"@opencode-ai/session-ui": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import type { Project } from "@/types"
|
import type { Project } from "@/types"
|
||||||
import type { SessionInfo } from "@opencode-ai/client/promise"
|
import type { SessionInfo } from "@opencode-ai/client/promise"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { batch, createEffect, onCleanup, onMount, Show } from "solid-js"
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
|
||||||
@@ -102,16 +101,8 @@ function Cell(props: {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (props.inline) {
|
|
||||||
return (
|
|
||||||
<TooltipV2 value={props.tip} placement="top">
|
|
||||||
{content()}
|
|
||||||
</TooltipV2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip value={props.tip} placement="top">
|
<Tooltip appearance={props.inline ? "compact" : "standard"} value={props.tip} placement="top">
|
||||||
{content()}
|
{content()}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
@@ -151,16 +142,8 @@ function ToggleCell(props: {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (props.inline) {
|
|
||||||
return (
|
|
||||||
<TooltipV2 value={props.tip} placement="top">
|
|
||||||
{content()}
|
|
||||||
</TooltipV2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip value={props.tip} placement="top">
|
<Tooltip appearance={props.inline ? "compact" : "standard"} value={props.tip} placement="top">
|
||||||
{content()}
|
{content()}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Dialog, DialogBody } from "@opencode-ai/ui/dialog"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
import { commandPaletteOptions, formatKeybindParts, useCommand } from "@/context/command"
|
import { commandPaletteOptions, formatKeybindParts, useCommand } from "@/context/command"
|
||||||
@@ -191,7 +191,7 @@ function CommandPaletteView(props: {
|
|||||||
<Dialog class="command-palette-v2" size="large">
|
<Dialog class="command-palette-v2" size="large">
|
||||||
<DialogBody class="command-palette-v2-body">
|
<DialogBody class="command-palette-v2-body">
|
||||||
<div class="command-palette-v2-search">
|
<div class="command-palette-v2-search">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
value={query()}
|
value={query()}
|
||||||
autofocus
|
autofocus
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@@ -295,7 +295,7 @@ function PaletteRow(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.item.keybind}>
|
<Show when={props.item.keybind}>
|
||||||
<KeybindV2 keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
|
<Keybind keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={props.item.type === "session"}>
|
<Match when={props.item.type === "session"}>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function ConnectProviderDialogStory() {
|
|||||||
onMount(open)
|
onMount(open)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="secondary" onClick={open}>
|
<Button variant="neutral" onClick={open}>
|
||||||
Open connect provider dialog
|
Open connect provider dialog
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -29,7 +29,7 @@ function ProviderConnectionDialogStory(props) {
|
|||||||
onMount(open)
|
onMount(open)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="secondary" onClick={open}>
|
<Button variant="neutral" onClick={open}>
|
||||||
Open {props.provider} connection dialog
|
Open {props.provider} connection dialog
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { List } from "@opencode-ai/ui/list"
|
|||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { DialogBody, DialogHeader, DialogTitle, Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { type Component, createMemo, createUniqueId, For, Match, onMount, Show, Switch } from "solid-js"
|
import { type Component, createMemo, createUniqueId, For, Match, onMount, Show, Switch } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
@@ -75,7 +74,7 @@ export const DialogConnectProvider: Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogV2
|
<Dialog
|
||||||
containerClass="!h-[min(calc(100vh_-_16px),512px)] !w-[min(calc(100vw_-_16px),640px)]"
|
containerClass="!h-[min(calc(100vh_-_16px),512px)] !w-[min(calc(100vw_-_16px),640px)]"
|
||||||
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
||||||
>
|
>
|
||||||
@@ -99,7 +98,7 @@ export const DialogConnectProvider: Component<{
|
|||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogV2>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +167,7 @@ function ProviderPicker(props: { directory?: string; onSelect: (provider: string
|
|||||||
return (
|
return (
|
||||||
<div ref={picker} class="flex min-h-0 flex-1 flex-col gap-4" onKeyDown={handleKeyDown}>
|
<div ref={picker} class="flex min-h-0 flex-1 flex-col gap-4" onKeyDown={handleKeyDown}>
|
||||||
<div class="shrink-0 px-1 pt-px">
|
<div class="shrink-0 px-1 pt-px">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
ref={search}
|
ref={search}
|
||||||
type="search"
|
type="search"
|
||||||
class="!w-full [font-family:var(--v2-font-family-sans)]"
|
class="!w-full [font-family:var(--v2-font-family-sans)]"
|
||||||
@@ -277,8 +276,7 @@ function ProviderConnection(props: {
|
|||||||
})
|
})
|
||||||
const provider = createMemo(() => ({
|
const provider = createMemo(() => ({
|
||||||
id: props.provider,
|
id: props.provider,
|
||||||
name:
|
name: providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
|
||||||
providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
|
|
||||||
}))
|
}))
|
||||||
const methodLabel = (value?: { type?: string; label?: string }) => {
|
const methodLabel = (value?: { type?: string; label?: string }) => {
|
||||||
if (!value) return ""
|
if (!value) return ""
|
||||||
@@ -380,7 +378,7 @@ function ProviderConnection(props: {
|
|||||||
setFormStore("value", field.key, value)
|
setFormStore("value", field.key, value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button class="w-auto" type="submit" size="large" variant="primary" disabled={!valid()}>
|
<Button class="w-auto" type="submit" size="large" variant="contrast" disabled={!valid()}>
|
||||||
{language.t("common.continue")}
|
{language.t("common.continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -517,7 +515,7 @@ function ProviderConnection(props: {
|
|||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
||||||
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
||||||
{language.t("provider.connect.apiKey.label", { provider: provider().name })}
|
{language.t("provider.connect.apiKey.label", { provider: provider().name })}
|
||||||
<TextInputV2
|
<TextInput
|
||||||
ref={apiKey}
|
ref={apiKey}
|
||||||
class="!w-full"
|
class="!w-full"
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
@@ -538,9 +536,9 @@ function ProviderConnection(props: {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<ButtonV2 type="submit" variant="contrast" data-action="provider-connect-submit">
|
<Button type="submit" variant="contrast" data-action="provider-connect-submit">
|
||||||
{language.t("common.continue")}
|
{language.t("common.continue")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -586,7 +584,7 @@ function ProviderConnection(props: {
|
|||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
||||||
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
||||||
{language.t("provider.connect.oauth.code.label", { method: controller.currentMethod()?.label ?? "" })}
|
{language.t("provider.connect.oauth.code.label", { method: controller.currentMethod()?.label ?? "" })}
|
||||||
<TextInputV2
|
<TextInput
|
||||||
ref={codeInput}
|
ref={codeInput}
|
||||||
class="!w-full"
|
class="!w-full"
|
||||||
name="code"
|
name="code"
|
||||||
@@ -606,9 +604,9 @@ function ProviderConnection(props: {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<ButtonV2 type="submit" variant="contrast">
|
<Button type="submit" variant="contrast">
|
||||||
{language.t("common.continue")}
|
{language.t("common.continue")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
import { useMutation } from "@tanstack/solid-query"
|
||||||
@@ -21,20 +22,21 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog class="h-full">
|
||||||
class="h-full"
|
<DialogHeader>
|
||||||
title={
|
<DialogTitle>
|
||||||
<IconButton
|
<IconButton
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
icon="arrow-left"
|
icon={<Icon name="arrow-left" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={props.onBack}
|
onClick={props.onBack}
|
||||||
aria-label={language.t("common.goBack")}
|
aria-label={language.t("common.goBack")}
|
||||||
/>
|
/>
|
||||||
}
|
</DialogTitle>
|
||||||
transition
|
</DialogHeader>
|
||||||
>
|
<DialogBody>
|
||||||
<CustomProviderForm />
|
<CustomProviderForm />
|
||||||
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -238,7 +240,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
|||||||
</div>
|
</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
icon="trash"
|
icon={<Icon name="trash" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="mt-1.5"
|
class="mt-1.5"
|
||||||
onClick={() => removeModel(i())}
|
onClick={() => removeModel(i())}
|
||||||
@@ -282,7 +284,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
|||||||
</div>
|
</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
icon="trash"
|
icon={<Icon name="trash" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="mt-1.5"
|
class="mt-1.5"
|
||||||
onClick={() => removeHeader(i())}
|
onClick={() => removeHeader(i())}
|
||||||
@@ -301,7 +303,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
|||||||
class="w-auto self-start"
|
class="w-auto self-start"
|
||||||
type="submit"
|
type="submit"
|
||||||
size="large"
|
size="large"
|
||||||
variant="primary"
|
variant="contrast"
|
||||||
disabled={saveMutation.isPending}
|
disabled={saveMutation.isPending}
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? language.t("common.saving") : language.t("common.submit")}
|
{saveMutation.isPending ? language.t("common.saving") : language.t("common.submit")}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Dialog, DialogFooter } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Dialog, DialogFooter } from "@opencode-ai/ui/dialog"
|
||||||
import { Field } from "@opencode-ai/ui/v2/field-v2"
|
import { Field } from "@opencode-ai/ui/field"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProjectAvatar, PROJECT_AVATAR_VARIANTS } from "@opencode-ai/ui/v2/project-avatar-v2"
|
import { ProjectAvatar, PROJECT_AVATAR_VARIANTS } from "@opencode-ai/ui/project-avatar"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { TextareaV2 } from "@opencode-ai/ui/v2/textarea-v2"
|
import { Textarea } from "@opencode-ai/ui/textarea"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { For, Show, createSignal, startTransition } from "solid-js"
|
import { For, Show, createSignal, startTransition } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { getProjectAvatarVariant, type LocalProject } from "@/context/layout"
|
import { getProjectAvatarVariant, type LocalProject } from "@/context/layout"
|
||||||
@@ -37,46 +36,46 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
|
|
||||||
const Footer = () => (
|
const Footer = () => (
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<ButtonV2 type="button" variant="neutral" disabled={model.save.isPending} onClick={model.close}>
|
<Button type="button" variant="neutral" disabled={model.save.isPending} onClick={model.close}>
|
||||||
{language.t("common.cancel")}
|
{language.t("common.cancel")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
<ButtonV2 type="submit" variant="contrast" disabled={!model.supported || model.save.isPending}>
|
<Button type="submit" variant="contrast" disabled={!model.supported || model.save.isPending}>
|
||||||
{model.save.isPending ? language.t("common.saving") : language.t("common.save")}
|
{model.save.isPending ? language.t("common.saving") : language.t("common.save")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog size="x-large" variant="settings" class="project-settings-v2-dialog">
|
<Dialog size="x-large" variant="settings" class="project-settings-v2-dialog">
|
||||||
<TabsV2
|
<Tabs
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
variant="settings"
|
variant="settings"
|
||||||
value={tab()}
|
value={tab()}
|
||||||
onChange={(value) => void startTransition(() => setTab(value))}
|
onChange={(value) => void startTransition(() => setTab(value))}
|
||||||
class="project-settings-v2"
|
class="project-settings-v2"
|
||||||
>
|
>
|
||||||
<TabsV2.List>
|
<Tabs.List>
|
||||||
<div class="project-settings-v2-nav">
|
<div class="project-settings-v2-nav">
|
||||||
<TabsV2.Trigger value="general">
|
<Tabs.Trigger value="general">
|
||||||
<ProjectAvatar
|
<ProjectAvatar
|
||||||
fallback={projectName()}
|
fallback={projectName()}
|
||||||
variant={getProjectAvatarVariant(props.project.icon?.color)}
|
variant={getProjectAvatarVariant(props.project.icon?.color)}
|
||||||
class="!size-4 shrink-0"
|
class="!size-4 shrink-0"
|
||||||
/>
|
/>
|
||||||
<span class="truncate">{projectName()}</span>
|
<span class="truncate">{projectName()}</span>
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="scripts">
|
<Tabs.Trigger value="scripts">
|
||||||
<Icon name="code" size="small" />
|
<Icon name="code" size="small" />
|
||||||
{language.t("project.settings.scripts")}
|
{language.t("project.settings.scripts")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="extensions">
|
<Tabs.Trigger value="extensions">
|
||||||
<Icon name="extensions" size="small" />
|
<Icon name="extensions" size="small" />
|
||||||
{language.t("settings.tab.extensions")}
|
{language.t("settings.tab.extensions")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<TabsV2.Content value="general" class="project-settings-v2-panel">
|
<Tabs.Content value="general" class="project-settings-v2-panel">
|
||||||
<form onSubmit={model.submit} class="project-settings-v2-form">
|
<form onSubmit={model.submit} class="project-settings-v2-form">
|
||||||
<div class="project-settings-v2-scroll">
|
<div class="project-settings-v2-scroll">
|
||||||
<div class="project-settings-page-header">
|
<div class="project-settings-page-header">
|
||||||
@@ -86,7 +85,7 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
|
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Label>{language.t("dialog.project.edit.name")}</Field.Label>
|
<Field.Label>{language.t("dialog.project.edit.name")}</Field.Label>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
autofocus
|
autofocus
|
||||||
appearance="large"
|
appearance="large"
|
||||||
class="!w-full"
|
class="!w-full"
|
||||||
@@ -132,7 +131,7 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
"opacity-0": !model.store.iconHover,
|
"opacity-0": !model.store.iconHover,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconV2 name={model.store.iconOverride ? "close" : "share"} />
|
<Icon name={model.store.iconOverride ? "close" : "share"} />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
@@ -188,9 +187,9 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</form>
|
</form>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="scripts" class="project-settings-v2-panel">
|
<Tabs.Content value="scripts" class="project-settings-v2-panel">
|
||||||
<form onSubmit={model.submit} class="project-settings-v2-form">
|
<form onSubmit={model.submit} class="project-settings-v2-form">
|
||||||
<div class="project-settings-v2-scroll">
|
<div class="project-settings-v2-scroll">
|
||||||
<div class="project-settings-page-header">
|
<div class="project-settings-page-header">
|
||||||
@@ -200,7 +199,7 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
<Field>
|
<Field>
|
||||||
<Field.Label>{language.t("dialog.project.edit.worktree.startup")}</Field.Label>
|
<Field.Label>{language.t("dialog.project.edit.worktree.startup")}</Field.Label>
|
||||||
<Field.Prefix>{language.t("dialog.project.edit.worktree.startup.description")}</Field.Prefix>
|
<Field.Prefix>{language.t("dialog.project.edit.worktree.startup.description")}</Field.Prefix>
|
||||||
<TextareaV2
|
<Textarea
|
||||||
class="!w-full [&_[data-slot=textarea-v2-textarea]]:font-mono"
|
class="!w-full [&_[data-slot=textarea-v2-textarea]]:font-mono"
|
||||||
rows={5}
|
rows={5}
|
||||||
value={model.store.startup}
|
value={model.store.startup}
|
||||||
@@ -212,12 +211,12 @@ function ProjectSettingsDialog(props: { project: LocalProject; server: ServerCon
|
|||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</form>
|
</form>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="extensions" class="project-settings-v2-panel">
|
<Tabs.Content value="extensions" class="project-settings-v2-panel">
|
||||||
<ProjectSettingsExtensions />
|
<ProjectSettingsExtensions />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
</TabsV2>
|
</Tabs>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ import { useNavigate, useParams } from "@solidjs/router"
|
|||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
import { usePrompt } from "@/context/prompt"
|
import { usePrompt } from "@/context/prompt"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { extractPromptFromParts } from "@/utils/prompt"
|
import { extractPromptComments, extractPromptFromMessage } from "@/utils/prompt"
|
||||||
import { normalizeSessionMessages } from "@/utils/session-message"
|
|
||||||
import { useWorkspaceLocation } from "@/context/location"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
import { sessionHref } from "@/utils/session-route"
|
import { sessionHref } from "@/utils/session-route"
|
||||||
@@ -61,13 +60,12 @@ export const DialogFork: Component = () => {
|
|||||||
|
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const restored = extractPromptFromParts(
|
const message = data.session.message.get(sessionID, item.id)
|
||||||
normalizeSessionMessages(sessionID, data.session.message.list(sessionID)).parts.get(item.id) ?? [],
|
if (message?.type !== "user") return
|
||||||
{
|
const restored = extractPromptFromMessage(message, {
|
||||||
directory: location().directory,
|
directory: location().directory,
|
||||||
attachmentName: language.t("common.attachment"),
|
attachmentName: language.t("common.attachment"),
|
||||||
},
|
})
|
||||||
)
|
|
||||||
const dir = base64Encode(location().directory)
|
const dir = base64Encode(location().directory)
|
||||||
|
|
||||||
serverSDK.api.session
|
serverSDK.api.session
|
||||||
@@ -75,7 +73,18 @@ export const DialogFork: Component = () => {
|
|||||||
.then((forked) => {
|
.then((forked) => {
|
||||||
data.session.remember(forked)
|
data.session.remember(forked)
|
||||||
dialog.close()
|
dialog.close()
|
||||||
prompt.set(restored, undefined, { dir, id: forked.id })
|
const target = prompt.capture({ dir, id: forked.id })
|
||||||
|
target.set(restored)
|
||||||
|
target.context.replaceComments(
|
||||||
|
extractPromptComments(message).map((comment) => ({
|
||||||
|
type: "file",
|
||||||
|
path: comment.path,
|
||||||
|
selection: comment.selection,
|
||||||
|
comment: comment.comment,
|
||||||
|
preview: comment.preview,
|
||||||
|
commentOrigin: comment.origin,
|
||||||
|
})),
|
||||||
|
)
|
||||||
navigate(sessionHref(server.key, forked.id))
|
navigate(sessionHref(server.key, forked.id))
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
@@ -85,23 +94,28 @@ export const DialogFork: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={language.t("command.session.fork")}>
|
<Dialog>
|
||||||
<List
|
<DialogHeader>
|
||||||
class="flex-1 px-3 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
<DialogTitle>{language.t("command.session.fork")}</DialogTitle>
|
||||||
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
|
</DialogHeader>
|
||||||
emptyMessage={language.t("dialog.fork.empty")}
|
<DialogBody>
|
||||||
key={(x) => x.id}
|
<List
|
||||||
items={messages}
|
class="flex-1 px-3 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
||||||
filterKeys={["text"]}
|
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
|
||||||
onSelect={handleSelect}
|
emptyMessage={language.t("dialog.fork.empty")}
|
||||||
>
|
key={(x) => x.id}
|
||||||
{(item) => (
|
items={messages}
|
||||||
<div class="w-full flex items-center gap-2">
|
filterKeys={["text"]}
|
||||||
<span class="truncate flex-1 min-w-0 text-left font-normal">{item.text}</span>
|
onSelect={handleSelect}
|
||||||
<span class="text-text-weak shrink-0 font-normal">{item.time}</span>
|
>
|
||||||
</div>
|
{(item) => (
|
||||||
)}
|
<div class="w-full flex items-center gap-2">
|
||||||
</List>
|
<span class="truncate flex-1 min-w-0 text-left font-normal">{item.text}</span>
|
||||||
|
<span class="text-text-weak shrink-0 font-normal">{item.time}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/dialog"
|
||||||
import { Dialog as DialogV2, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
|
||||||
import { Switch as SwitchV2 } from "@opencode-ai/ui/v2/switch-v2"
|
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||||
import { For, Show, type Component } from "solid-js"
|
import { For, Show, type Component } from "solid-js"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
@@ -44,74 +41,80 @@ export const DialogManageModels: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog>
|
||||||
title={language.t("dialog.model.manage")}
|
<DialogHeader hideClose>
|
||||||
description={language.t("dialog.model.manage.description")}
|
<DialogTitleGroup
|
||||||
action={
|
title={language.t("dialog.model.manage")}
|
||||||
|
description={language.t("dialog.model.manage.description")}
|
||||||
|
/>
|
||||||
<Button class="h-7 -my-1 text-14-medium" icon="plus-small" tabIndex={-1} onClick={handleConnectProvider}>
|
<Button class="h-7 -my-1 text-14-medium" icon="plus-small" tabIndex={-1} onClick={handleConnectProvider}>
|
||||||
{language.t("command.provider.connect")}
|
{language.t("command.provider.connect")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
</DialogHeader>
|
||||||
>
|
<DialogBody>
|
||||||
<List
|
<List
|
||||||
class="px-3"
|
class="px-3"
|
||||||
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true }}
|
search={{ placeholder: language.t("dialog.model.search.placeholder"), autofocus: true }}
|
||||||
emptyMessage={language.t("dialog.model.empty")}
|
emptyMessage={language.t("dialog.model.empty")}
|
||||||
key={(x) => `${x?.provider?.id}:${x?.id}`}
|
key={(x) => `${x?.provider?.id}:${x?.id}`}
|
||||||
items={local.model.list()}
|
items={local.model.list()}
|
||||||
filterKeys={["provider.name", "name", "id"]}
|
filterKeys={["provider.name", "name", "id"]}
|
||||||
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
||||||
groupBy={(x) => x.provider.id}
|
groupBy={(x) => x.provider.id}
|
||||||
groupHeader={(group) => {
|
groupHeader={(group) => {
|
||||||
const provider = group.items[0].provider
|
const provider = group.items[0].provider
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>{provider.name}</span>
|
<span>{provider.name}</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement="top"
|
appearance="standard"
|
||||||
value={language.t("dialog.model.manage.provider.toggle", { provider: provider.name })}
|
placement="top"
|
||||||
>
|
value={language.t("dialog.model.manage.provider.toggle", { provider: provider.name })}
|
||||||
<Switch
|
|
||||||
class="-mr-1"
|
|
||||||
checked={providerVisible(provider.id)}
|
|
||||||
onChange={(checked) => setProviderVisibility(provider.id, checked)}
|
|
||||||
hideLabel
|
|
||||||
>
|
>
|
||||||
{provider.name}
|
<Switch
|
||||||
</Switch>
|
appearance="standard"
|
||||||
</Tooltip>
|
class="-mr-1"
|
||||||
</>
|
checked={providerVisible(provider.id)}
|
||||||
)
|
onChange={(checked) => setProviderVisibility(provider.id, checked)}
|
||||||
}}
|
hideLabel
|
||||||
sortGroupsBy={(a, b) => {
|
>
|
||||||
const aRank = providerRank(a.items[0].provider.id)
|
{provider.name}
|
||||||
const bRank = providerRank(b.items[0].provider.id)
|
</Switch>
|
||||||
const aPopular = aRank >= 0
|
</Tooltip>
|
||||||
const bPopular = bRank >= 0
|
</>
|
||||||
if (aPopular && !bPopular) return -1
|
)
|
||||||
if (!aPopular && bPopular) return 1
|
}}
|
||||||
return aRank - bRank
|
sortGroupsBy={(a, b) => {
|
||||||
}}
|
const aRank = providerRank(a.items[0].provider.id)
|
||||||
onSelect={(x) => {
|
const bRank = providerRank(b.items[0].provider.id)
|
||||||
if (!x) return
|
const aPopular = aRank >= 0
|
||||||
const key = { modelID: x.id, providerID: x.provider.id }
|
const bPopular = bRank >= 0
|
||||||
local.model.setVisibility(key, !local.model.visible(key))
|
if (aPopular && !bPopular) return -1
|
||||||
}}
|
if (!aPopular && bPopular) return 1
|
||||||
>
|
return aRank - bRank
|
||||||
{(i) => (
|
}}
|
||||||
<div class="w-full flex items-center justify-between gap-x-3">
|
onSelect={(x) => {
|
||||||
<span>{i.name}</span>
|
if (!x) return
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
const key = { modelID: x.id, providerID: x.provider.id }
|
||||||
<Switch
|
local.model.setVisibility(key, !local.model.visible(key))
|
||||||
checked={!!local.model.visible({ modelID: i.id, providerID: i.provider.id })}
|
}}
|
||||||
onChange={(checked) => {
|
>
|
||||||
local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked)
|
{(i) => (
|
||||||
}}
|
<div class="w-full flex items-center justify-between gap-x-3">
|
||||||
/>
|
<span>{i.name}</span>
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Switch
|
||||||
|
appearance="standard"
|
||||||
|
checked={!!local.model.visible({ modelID: i.id, providerID: i.provider.id })}
|
||||||
|
onChange={(checked) => {
|
||||||
|
local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</List>
|
||||||
</List>
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -154,20 +157,20 @@ export const DialogManageModelsV2: Component = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogV2 size="large" variant="settings" class="settings-v2-manage-models-dialog">
|
<Dialog size="large" variant="settings" class="settings-v2-manage-models-dialog">
|
||||||
<DialogHeader hideClose={true} closeLabel={language.t("common.close")}>
|
<DialogHeader hideClose={true} closeLabel={language.t("common.close")}>
|
||||||
<DialogTitleGroup
|
<DialogTitleGroup
|
||||||
title={language.t("dialog.model.manage")}
|
title={language.t("dialog.model.manage")}
|
||||||
description={language.t("dialog.model.manage.description")}
|
description={language.t("dialog.model.manage.description")}
|
||||||
/>
|
/>
|
||||||
<ButtonV2 variant="neutral" icon="plus" onClick={handleConnectProvider}>
|
<Button variant="neutral" icon="plus" onClick={handleConnectProvider}>
|
||||||
{language.t("command.provider.connect")}
|
{language.t("command.provider.connect")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogBody class="flex min-h-0 flex-1 flex-col">
|
<DialogBody class="flex min-h-0 flex-1 flex-col">
|
||||||
<div class="px-4 pt-px pb-3">
|
<div class="px-4 pt-px pb-3">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
class="!w-full self-stretch"
|
class="!w-full self-stretch"
|
||||||
@@ -182,12 +185,12 @@ export const DialogManageModelsV2: Component = () => {
|
|||||||
aria-label={language.t("dialog.model.search.placeholder")}
|
aria-label={language.t("dialog.model.search.placeholder")}
|
||||||
/>
|
/>
|
||||||
<Show when={list.filter()}>
|
<Show when={list.filter()}>
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
class="settings-v2-tab-search-clear"
|
class="settings-v2-tab-search-clear"
|
||||||
icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
|
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||||
onClick={() => list.clear()}
|
onClick={() => list.clear()}
|
||||||
aria-label={language.t("common.clear")}
|
aria-label={language.t("common.clear")}
|
||||||
/>
|
/>
|
||||||
@@ -225,14 +228,14 @@ export const DialogManageModelsV2: Component = () => {
|
|||||||
<h3 class="settings-v2-section-title">{group.items[0].provider.name}</h3>
|
<h3 class="settings-v2-section-title">{group.items[0].provider.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SwitchV2
|
<Switch
|
||||||
class="mr-6"
|
class="mr-6"
|
||||||
checked={providerVisible(group.category)}
|
checked={providerVisible(group.category)}
|
||||||
onChange={(checked) => setProviderVisibility(group.category, checked)}
|
onChange={(checked) => setProviderVisibility(group.category, checked)}
|
||||||
hideLabel
|
hideLabel
|
||||||
>
|
>
|
||||||
{group.items[0].provider.name}
|
{group.items[0].provider.name}
|
||||||
</SwitchV2>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SettingsListV2>
|
<SettingsListV2>
|
||||||
@@ -240,13 +243,13 @@ export const DialogManageModelsV2: Component = () => {
|
|||||||
{(item) => (
|
{(item) => (
|
||||||
<SettingsRowV2 title={item.name} description="">
|
<SettingsRowV2 title={item.name} description="">
|
||||||
<div>
|
<div>
|
||||||
<SwitchV2
|
<Switch
|
||||||
checked={local.model.visible({ modelID: item.id, providerID: item.provider.id })}
|
checked={local.model.visible({ modelID: item.id, providerID: item.provider.id })}
|
||||||
onChange={(checked) => setModelVisibility(item, checked)}
|
onChange={(checked) => setModelVisibility(item, checked)}
|
||||||
hideLabel
|
hideLabel
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</SwitchV2>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
)}
|
)}
|
||||||
@@ -260,6 +263,6 @@ export const DialogManageModelsV2: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogV2>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
|
|||||||
<Show
|
<Show
|
||||||
when={isLast()}
|
when={isLast()}
|
||||||
fallback={
|
fallback={
|
||||||
<Button variant="secondary" size="large" onClick={handleNext}>
|
<Button variant="neutral" size="large" onClick={handleNext}>
|
||||||
{language.t("dialog.releaseNotes.action.next")}
|
{language.t("dialog.releaseNotes.action.next")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button variant="primary" size="large" onClick={handleClose}>
|
<Button variant="contrast" size="large" onClick={handleClose}>
|
||||||
{language.t("dialog.releaseNotes.action.getStarted")}
|
{language.t("dialog.releaseNotes.action.getStarted")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import "@pierre/trees/web-components"
|
import "@pierre/trees/web-components"
|
||||||
import { FileTree } from "@pierre/trees"
|
import { FileTree } from "@pierre/trees"
|
||||||
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { createEffect, createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
import { createEffect, createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { useGlobal } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
@@ -28,8 +28,8 @@ import {
|
|||||||
pickerRoot,
|
pickerRoot,
|
||||||
} from "./directory-picker-domain"
|
} from "./directory-picker-domain"
|
||||||
import "./dialog-select-directory-v2.css"
|
import "./dialog-select-directory-v2.css"
|
||||||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
import { Divider } from "@opencode-ai/ui/divider"
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
|
||||||
interface DialogSelectDirectoryV2Props {
|
interface DialogSelectDirectoryV2Props {
|
||||||
title?: string
|
title?: string
|
||||||
@@ -293,10 +293,10 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{props.title ?? language.t("command.project.open")}</DialogTitle>
|
<DialogTitle>{props.title ?? language.t("command.project.open")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DividerV2 />
|
<Divider />
|
||||||
<DialogBody class="directory-picker-v2-body pt-4!">
|
<DialogBody class="directory-picker-v2-body pt-4!">
|
||||||
<div class="directory-picker-v2-path" ref={pathArea}>
|
<div class="directory-picker-v2-path" ref={pathArea}>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
value={input()}
|
value={input()}
|
||||||
autofocus
|
autofocus
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@@ -318,15 +318,15 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
|||||||
onKeyDown={handleInputKey}
|
onKeyDown={handleInputKey}
|
||||||
/>
|
/>
|
||||||
<div class="directory-picker-v2-actions">
|
<div class="directory-picker-v2-actions">
|
||||||
<ButtonV2 size="small" variant="ghost" onClick={() => void navigate(home())}>
|
<Button size="small" variant="ghost" onClick={() => void navigate(home())}>
|
||||||
~
|
~
|
||||||
</ButtonV2>
|
</Button>
|
||||||
<ButtonV2 size="small" variant="ghost" onClick={() => void navigate(pickerRoot(root()) || root())}>
|
<Button size="small" variant="ghost" onClick={() => void navigate(pickerRoot(root()) || root())}>
|
||||||
{language.t("dialog.directory.root")}
|
{language.t("dialog.directory.root")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
<ButtonV2 size="small" variant="ghost" onClick={() => void navigate(pickerParent(root()))}>
|
<Button size="small" variant="ghost" onClick={() => void navigate(pickerParent(root()))}>
|
||||||
{language.t("dialog.directory.parent")}
|
{language.t("dialog.directory.parent")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Show when={suggestionsOpen() && currentSuggestions().length > 0}>
|
<Show when={suggestionsOpen() && currentSuggestions().length > 0}>
|
||||||
<div id="directory-picker-v2-suggestions" role="listbox" class="directory-picker-v2-suggestions">
|
<div id="directory-picker-v2-suggestions" role="listbox" class="directory-picker-v2-suggestions">
|
||||||
@@ -379,12 +379,12 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
|||||||
<div class="directory-picker-v2-selection">{policy.result(root(), selected(), rootValid())}</div>
|
<div class="directory-picker-v2-selection">{policy.result(root(), selected(), rootValid())}</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<ButtonV2 variant="neutral" onClick={() => dialog.close()}>
|
<Button variant="neutral" onClick={() => dialog.close()}>
|
||||||
{language.t("common.cancel")}
|
{language.t("common.cancel")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
<ButtonV2 variant="contrast" disabled={!policy.result(root(), selected(), rootValid())} onClick={resolve}>
|
<Button variant="contrast" disabled={!policy.result(root(), selected(), rootValid())} onClick={resolve}>
|
||||||
{action[policy.action]}
|
{action[policy.action]}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|
||||||
import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
|
|
||||||
import { formatKeybind } from "@/context/command"
|
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
|
||||||
import { getRelativeTime } from "@/utils/time"
|
|
||||||
import {
|
|
||||||
createCommandPaletteFileEntry,
|
|
||||||
createCommandPaletteFileOpener,
|
|
||||||
createCommandPaletteModel,
|
|
||||||
uniqueCommandPaletteEntries,
|
|
||||||
type CommandPaletteEntry,
|
|
||||||
} from "./command-palette"
|
|
||||||
import { DialogCommandPaletteV2 } from "./dialog-command-palette-v2"
|
|
||||||
|
|
||||||
const DialogSelectFileV2 = lazy(() =>
|
|
||||||
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
|
|
||||||
)
|
|
||||||
type DialogSelectFileMode = "all" | "files"
|
|
||||||
|
|
||||||
export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
|
|
||||||
const platform = usePlatform()
|
|
||||||
const filesOnly = () => props.mode === "files"
|
|
||||||
|
|
||||||
if (!filesOnly()) {
|
|
||||||
return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filesOnly() && platform.platform === "desktop") {
|
|
||||||
return <DialogSelectFileDesktopV2 onOpenFile={props.onOpenFile} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <DialogSelectFileLegacy filesOnly={filesOnly} onOpenFile={props.onOpenFile} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) {
|
|
||||||
const language = useLanguage()
|
|
||||||
const serverSDK = useServerSDK()
|
|
||||||
const { params } = useSessionLayout()
|
|
||||||
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
|
||||||
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogSelectFileV2
|
|
||||||
server={serverSDK.server}
|
|
||||||
mode="file"
|
|
||||||
start={projectDirectory()}
|
|
||||||
title={language.t("session.header.searchFiles")}
|
|
||||||
onSelect={(result) => {
|
|
||||||
if (typeof result !== "string") return
|
|
||||||
openFile(result)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) {
|
|
||||||
const palette = createCommandPaletteModel(props)
|
|
||||||
const [grouped, setGrouped] = createSignal(false)
|
|
||||||
|
|
||||||
const items = async (text: string) => {
|
|
||||||
const query = text.trim()
|
|
||||||
setGrouped(query.length > 0)
|
|
||||||
|
|
||||||
if (!query && props.filesOnly()) {
|
|
||||||
const loaded = palette.file.tree.state("")?.loaded
|
|
||||||
const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
|
|
||||||
const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
|
|
||||||
|
|
||||||
if (loaded || next.length > 0) {
|
|
||||||
void pending
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
await pending
|
|
||||||
return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
|
||||||
|
|
||||||
if (props.filesOnly()) {
|
|
||||||
const files = await palette.file.searchFiles(query)
|
|
||||||
const category = palette.language.t("palette.group.files")
|
|
||||||
return files.map((path) => createCommandPaletteFileEntry(path, category))
|
|
||||||
}
|
|
||||||
|
|
||||||
const [files, nextSessions] = await Promise.all([
|
|
||||||
palette.file.searchFiles(query),
|
|
||||||
Promise.resolve(palette.sessions(query)),
|
|
||||||
])
|
|
||||||
const category = palette.language.t("palette.group.files")
|
|
||||||
const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
|
|
||||||
return [...palette.commandEntries(), ...nextSessions, ...entries]
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
|
|
||||||
<List
|
|
||||||
class="px-3"
|
|
||||||
search={{
|
|
||||||
placeholder: props.filesOnly()
|
|
||||||
? palette.language.t("session.header.searchFiles")
|
|
||||||
: palette.language.t("palette.search.placeholder"),
|
|
||||||
autofocus: true,
|
|
||||||
hideIcon: true,
|
|
||||||
}}
|
|
||||||
emptyMessage={palette.language.t("palette.empty")}
|
|
||||||
loadingMessage={palette.language.t("common.loading")}
|
|
||||||
items={items}
|
|
||||||
key={(item) => item.id}
|
|
||||||
filterKeys={["title", "description", "category"]}
|
|
||||||
skipFilter={(item) => item.type === "file"}
|
|
||||||
groupBy={grouped() ? (item) => item.category : () => ""}
|
|
||||||
onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
|
|
||||||
onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<Switch
|
|
||||||
fallback={
|
|
||||||
<div class="w-full flex items-center justify-between rounded-md pl-1">
|
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
|
||||||
<FileIcon node={{ path: item.path ?? "", type: "file" }} class="shrink-0 size-4" />
|
|
||||||
<div class="flex items-center text-14-regular">
|
|
||||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
|
||||||
{getDirectory(item.path ?? "")}
|
|
||||||
</span>
|
|
||||||
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path ?? "")}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Match when={item.type === "command"}>
|
|
||||||
<div class="w-full flex items-center justify-between gap-4">
|
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
|
||||||
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
|
|
||||||
<Show when={item.description}>
|
|
||||||
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<Show when={item.keybind}>
|
|
||||||
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", palette.language.t)}</Keybind>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={item.type === "session"}>
|
|
||||||
<div class="w-full flex items-center justify-between rounded-md pl-1">
|
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
|
||||||
<Icon name="bubble-5" size="small" class="shrink-0 text-icon-weak" />
|
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
|
||||||
<span
|
|
||||||
class="text-14-regular text-text-strong truncate"
|
|
||||||
classList={{ "opacity-70": !!item.archived }}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</span>
|
|
||||||
<Show when={item.description}>
|
|
||||||
<span
|
|
||||||
class="text-14-regular text-text-weak truncate"
|
|
||||||
classList={{ "opacity-70": !!item.archived }}
|
|
||||||
>
|
|
||||||
{item.description}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Show when={item.updated}>
|
|
||||||
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
|
|
||||||
{getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, createMemo, Show } from "solid-js"
|
import { Component, createMemo, Show } from "solid-js"
|
||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
import { useWorkspaceLocation } from "@/context/location"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -31,64 +31,71 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
const totalCount = createMemo(() => items().length)
|
const totalCount = createMemo(() => items().length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog>
|
||||||
title={language.t("dialog.mcp.title")}
|
<DialogHeader>
|
||||||
description={language.t("dialog.mcp.description", { enabled: enabledCount(), total: totalCount() })}
|
<DialogTitleGroup
|
||||||
>
|
title={language.t("dialog.mcp.title")}
|
||||||
<List
|
description={language.t("dialog.mcp.description", { enabled: enabledCount(), total: totalCount() })}
|
||||||
class="px-3"
|
/>
|
||||||
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
|
</DialogHeader>
|
||||||
emptyMessage={language.t("dialog.mcp.empty")}
|
<DialogBody>
|
||||||
key={(x) => x?.name ?? ""}
|
<List
|
||||||
items={items}
|
class="px-3"
|
||||||
filterKeys={["name", "status"]}
|
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
|
||||||
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
emptyMessage={language.t("dialog.mcp.empty")}
|
||||||
onSelect={(x) => {
|
key={(x) => x?.name ?? ""}
|
||||||
if (!x || x.status === "pending" || toggle.isPending) return
|
items={items}
|
||||||
toggle.mutate(x.name)
|
filterKeys={["name", "status"]}
|
||||||
}}
|
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
||||||
>
|
onSelect={(x) => {
|
||||||
{(i) => {
|
if (!x || x.status === "pending" || toggle.isPending) return
|
||||||
const mcpStatus = () =>
|
toggle.mutate(x.name)
|
||||||
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)?.status
|
}}
|
||||||
const status = () => mcpStatus()?.status
|
>
|
||||||
const statusLabel = () => {
|
{(i) => {
|
||||||
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
const mcpStatus = () =>
|
||||||
if (!key) return
|
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)
|
||||||
return language.t(key)
|
?.status
|
||||||
}
|
const status = () => mcpStatus()?.status
|
||||||
const error = () => {
|
const statusLabel = () => {
|
||||||
const s = mcpStatus()
|
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
||||||
if (s?.status === "failed") return s.error
|
if (!key) return
|
||||||
}
|
return language.t(key)
|
||||||
const enabled = () => status() === "connected"
|
}
|
||||||
return (
|
const error = () => {
|
||||||
<div class="w-full flex items-center justify-between gap-x-3">
|
const s = mcpStatus()
|
||||||
<div class="flex flex-col gap-0.5 min-w-0">
|
if (s?.status === "failed") return s.error
|
||||||
<div class="flex items-center gap-2">
|
}
|
||||||
<span class="truncate">{i.name}</span>
|
const enabled = () => status() === "connected"
|
||||||
<Show when={statusLabel()}>
|
return (
|
||||||
<span class="text-11-regular text-text-weaker">{statusLabel()}</span>
|
<div class="w-full flex items-center justify-between gap-x-3">
|
||||||
|
<div class="flex flex-col gap-0.5 min-w-0">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="truncate">{i.name}</span>
|
||||||
|
<Show when={statusLabel()}>
|
||||||
|
<span class="text-11-regular text-text-weaker">{statusLabel()}</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={error()}>
|
||||||
|
<span class="text-11-regular text-text-weaker truncate">{error()}</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={error()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<span class="text-11-regular text-text-weaker truncate">{error()}</span>
|
<Switch
|
||||||
</Show>
|
appearance="standard"
|
||||||
|
checked={enabled()}
|
||||||
|
disabled={status() === "pending" || (toggle.isPending && toggle.variables === i.name)}
|
||||||
|
onChange={() => {
|
||||||
|
if (toggle.isPending) return
|
||||||
|
toggle.mutate(i.name)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
)
|
||||||
<Switch
|
}}
|
||||||
checked={enabled()}
|
</List>
|
||||||
disabled={status() === "pending" || (toggle.isPending && toggle.variables === i.name)}
|
</DialogBody>
|
||||||
onChange={() => {
|
|
||||||
if (toggle.isPending) return
|
|
||||||
toggle.mutate(i.name)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</List>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function SelectModelWithoutProviders() {
|
|||||||
onMount(open)
|
onMount(open)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="secondary" onClick={open}>
|
<Button variant="neutral" onClick={open}>
|
||||||
Open select model dialog
|
Open select model dialog
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
import { DialogBody, DialogHeader, DialogTitle, Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Badge } from "@opencode-ai/ui/badge"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useTheme } from "@opencode-ai/ui/theme"
|
import { useTheme } from "@opencode-ai/ui/theme"
|
||||||
import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js"
|
import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js"
|
||||||
@@ -66,7 +66,7 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogV2
|
<Dialog
|
||||||
fit
|
fit
|
||||||
containerClass="!h-auto max-h-[calc(100vh_-_16px)] !w-[min(calc(100vw_-_16px),640px)]"
|
containerClass="!h-auto max-h-[calc(100vh_-_16px)] !w-[min(calc(100vw_-_16px),640px)]"
|
||||||
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
||||||
@@ -84,7 +84,7 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro
|
|||||||
</div>
|
</div>
|
||||||
<For each={freeModels()}>
|
<For each={freeModels()}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={6}
|
gutter={6}
|
||||||
@@ -105,15 +105,15 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro
|
|||||||
onClick={() => selectModel(item)}
|
onClick={() => selectModel(item)}
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate">{displayModelName(item.name)}</span>
|
<span class="min-w-0 truncate">{displayModelName(item.name)}</span>
|
||||||
<Tag class="shrink-0">{language.t("model.tag.free")}</Tag>
|
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
|
||||||
<Show when={item.latest}>
|
<Show when={item.latest}>
|
||||||
<Tag class="shrink-0">{language.t("model.tag.latest")}</Tag>
|
<Badge class="shrink-0">{language.t("model.tag.latest")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={currentKey() === modelKey(item)}>
|
<Show when={currentKey() === modelKey(item)}>
|
||||||
<Icon name="check" class="ml-auto size-4 shrink-0 text-v2-icon-icon-base" />
|
<Icon name="check" class="ml-auto size-4 shrink-0 text-v2-icon-icon-base" />
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,6 +172,6 @@ export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (pro
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogV2>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
import { List, type ListRef } from "@opencode-ai/ui/list"
|
import { List, type ListRef } from "@opencode-ai/ui/list"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
import { Badge } from "@opencode-ai/ui/badge"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { type Component, Show } from "solid-js"
|
import { type Component, Show } from "solid-js"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
@@ -40,108 +40,113 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog class="overflow-y-auto [&_[data-slot=dialog-body]]:overflow-visible [&_[data-slot=dialog-body]]:flex-none">
|
||||||
title={language.t("dialog.model.select.title")}
|
<DialogHeader>
|
||||||
class="overflow-y-auto [&_[data-slot=dialog-body]]:overflow-visible [&_[data-slot=dialog-body]]:flex-none"
|
<DialogTitle>{language.t("dialog.model.select.title")}</DialogTitle>
|
||||||
>
|
</DialogHeader>
|
||||||
<div class="flex flex-col gap-3 px-2.5" onKeyDown={handleKeyDown}>
|
<DialogBody>
|
||||||
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
|
<div class="flex flex-col gap-3 px-2.5" onKeyDown={handleKeyDown}>
|
||||||
<List
|
<div class="text-14-medium text-text-base px-2.5">{language.t("dialog.model.unpaid.freeModels.title")}</div>
|
||||||
class="px-3 [&_[data-slot=list-scroll]]:overflow-visible"
|
<List
|
||||||
ref={(ref) => (listRef = ref)}
|
class="px-3 [&_[data-slot=list-scroll]]:overflow-visible"
|
||||||
items={model.list}
|
ref={(ref) => (listRef = ref)}
|
||||||
current={model.current()}
|
items={model.list}
|
||||||
key={(x) => `${x.provider.id}:${x.id}`}
|
current={model.current()}
|
||||||
itemWrapper={(item, node) => (
|
key={(x) => `${x.provider.id}:${x.id}`}
|
||||||
<Tooltip
|
itemWrapper={(item, node) => (
|
||||||
class="w-full"
|
<Tooltip
|
||||||
placement="right-start"
|
appearance="standard"
|
||||||
gutter={12}
|
class="w-full"
|
||||||
value={
|
placement="right-start"
|
||||||
<ModelTooltip
|
gutter={12}
|
||||||
model={item}
|
value={
|
||||||
latest={item.latest}
|
<ModelTooltip
|
||||||
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
|
model={item}
|
||||||
/>
|
latest={item.latest}
|
||||||
}
|
free={item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)}
|
||||||
>
|
/>
|
||||||
{node}
|
}
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
onSelect={(x) => {
|
|
||||||
model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
|
||||||
recent: true,
|
|
||||||
})
|
|
||||||
dialog.close()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(i) => (
|
|
||||||
<div class="w-full flex items-center gap-x-2.5">
|
|
||||||
<span>{i.name}</span>
|
|
||||||
<Tag>{language.t("model.tag.free")}</Tag>
|
|
||||||
<Show when={i.latest}>
|
|
||||||
<Tag>{language.t("model.tag.latest")}</Tag>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
<div class="px-1.5 pb-1.5">
|
|
||||||
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
|
||||||
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
|
|
||||||
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
|
|
||||||
<div class="w-full">
|
|
||||||
<List
|
|
||||||
class="w-full px-3"
|
|
||||||
key={(p) => p.id}
|
|
||||||
items={providers.popular}
|
|
||||||
activeIcon="plus-small"
|
|
||||||
sortBy={(a, b) => {
|
|
||||||
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
|
||||||
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
}}
|
|
||||||
onSelect={(x) => {
|
|
||||||
if (!x) return
|
|
||||||
connect(x.id)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{(i) => (
|
{node}
|
||||||
<div class="w-full flex items-center gap-x-3">
|
</Tooltip>
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
)}
|
||||||
<span>{i.name}</span>
|
onSelect={(x) => {
|
||||||
<Show when={i.id === "opencode"}>
|
model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
||||||
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
|
recent: true,
|
||||||
</Show>
|
})
|
||||||
<Show when={i.id === "opencode"}>
|
dialog.close()
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
}}
|
||||||
</Show>
|
>
|
||||||
<Show when={i.id === "opencode-go"}>
|
{(i) => (
|
||||||
<>
|
<div class="w-full flex items-center gap-x-2.5">
|
||||||
|
<span>{i.name}</span>
|
||||||
|
<Badge appearance="standard">{language.t("model.tag.free")}</Badge>
|
||||||
|
<Show when={i.latest}>
|
||||||
|
<Badge appearance="standard">{language.t("model.tag.latest")}</Badge>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
<div class="px-1.5 pb-1.5">
|
||||||
|
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
||||||
|
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
|
||||||
|
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<List
|
||||||
|
class="w-full px-3"
|
||||||
|
key={(p) => p.id}
|
||||||
|
items={providers.popular}
|
||||||
|
activeIcon="plus-small"
|
||||||
|
sortBy={(a, b) => {
|
||||||
|
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
||||||
|
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
||||||
|
return a.name.localeCompare(b.name)
|
||||||
|
}}
|
||||||
|
onSelect={(x) => {
|
||||||
|
if (!x) return
|
||||||
|
connect(x.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(i) => (
|
||||||
|
<div class="w-full flex items-center gap-x-3">
|
||||||
|
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
||||||
|
<span>{i.name}</span>
|
||||||
|
<Show when={i.id === "opencode"}>
|
||||||
<div class="text-14-regular text-text-weak">
|
<div class="text-14-regular text-text-weak">
|
||||||
{language.t("dialog.provider.opencodeGo.tagline")}
|
{language.t("dialog.provider.opencode.tagline")}
|
||||||
</div>
|
</div>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
</Show>
|
||||||
</>
|
<Show when={i.id === "opencode"}>
|
||||||
</Show>
|
<Badge appearance="standard">{language.t("dialog.provider.tag.recommended")}</Badge>
|
||||||
<Show when={i.id === "anthropic"}>
|
</Show>
|
||||||
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
|
<Show when={i.id === "opencode-go"}>
|
||||||
</Show>
|
<>
|
||||||
</div>
|
<div class="text-14-regular text-text-weak">
|
||||||
)}
|
{language.t("dialog.provider.opencodeGo.tagline")}
|
||||||
</List>
|
</div>
|
||||||
<Button
|
<Badge appearance="standard">{language.t("dialog.provider.tag.recommended")}</Badge>
|
||||||
variant="ghost"
|
</>
|
||||||
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
</Show>
|
||||||
icon="dot-grid"
|
<Show when={i.id === "anthropic"}>
|
||||||
onClick={all}
|
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
|
||||||
>
|
</Show>
|
||||||
{language.t("dialog.provider.viewAll")}
|
</div>
|
||||||
</Button>
|
)}
|
||||||
|
</List>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
||||||
|
icon="dot-grid"
|
||||||
|
onClick={all}
|
||||||
|
>
|
||||||
|
{language.t("dialog.provider.viewAll")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import { useLocal } from "@/context/local"
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { popularProviders } from "@/hooks/use-providers"
|
import { popularProviders } from "@/hooks/use-providers"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { Badge } from "@opencode-ai/ui/badge"
|
||||||
|
import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
|
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { ModelTooltip } from "./model-tooltip"
|
import { ModelTooltip } from "./model-tooltip"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
@@ -81,6 +79,7 @@ const ModelList: Component<{
|
|||||||
}}
|
}}
|
||||||
itemWrapper={(item, node) => (
|
itemWrapper={(item, node) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
appearance="standard"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={12}
|
gutter={12}
|
||||||
@@ -101,10 +100,10 @@ const ModelList: Component<{
|
|||||||
<div class="w-full flex items-center gap-x-2 text-13-regular">
|
<div class="w-full flex items-center gap-x-2 text-13-regular">
|
||||||
<span class="truncate">{i.name}</span>
|
<span class="truncate">{i.name}</span>
|
||||||
<Show when={isFree(i.provider.id, i.cost)}>
|
<Show when={isFree(i.provider.id, i.cost)}>
|
||||||
<Tag>{language.t("model.tag.free")}</Tag>
|
<Badge appearance="standard">{language.t("model.tag.free")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={i.latest}>
|
<Show when={i.latest}>
|
||||||
<Tag>{language.t("model.tag.latest")}</Tag>
|
<Badge appearance="standard">{language.t("model.tag.latest")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -193,21 +192,19 @@ export function ModelSelectorPopover(props: {
|
|||||||
class="p-1"
|
class="p-1"
|
||||||
action={
|
action={
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Tooltip placement="top" value={language.t("command.provider.connect")}>
|
<Tooltip appearance="standard" placement="top" value={language.t("command.provider.connect")}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="plus-small"
|
icon={<Icon name="plus-small" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
iconSize="normal"
|
|
||||||
class="size-6"
|
class="size-6"
|
||||||
aria-label={language.t("command.provider.connect")}
|
aria-label={language.t("command.provider.connect")}
|
||||||
onClick={handleConnectProvider}
|
onClick={handleConnectProvider}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" value={language.t("dialog.model.manage")}>
|
<Tooltip appearance="standard" placement="top" value={language.t("dialog.model.manage")}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="sliders"
|
icon={<Icon name="sliders" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
iconSize="normal"
|
|
||||||
class="size-6"
|
class="size-6"
|
||||||
aria-label={language.t("dialog.model.manage")}
|
aria-label={language.t("dialog.model.manage")}
|
||||||
onClick={handleManage}
|
onClick={handleManage}
|
||||||
@@ -373,10 +370,10 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuV2 open={store.open} modal={false} placement="top-start" gutter={6} onOpenChange={setOpen}>
|
<Menu open={store.open} modal={false} placement="top-start" gutter={6} onOpenChange={setOpen}>
|
||||||
<MenuV2.Trigger as={props.trigger} />
|
<Menu.Trigger as={props.trigger} />
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.Content
|
<Menu.Content
|
||||||
ref={(element: HTMLDivElement) => (contentRef = element)}
|
ref={(element: HTMLDivElement) => (contentRef = element)}
|
||||||
class="w-[284px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none"
|
class="w-[284px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none"
|
||||||
onPointerDownOutside={dismiss.preventTriggerRestore}
|
onPointerDownOutside={dismiss.preventTriggerRestore}
|
||||||
@@ -449,14 +446,14 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
>
|
>
|
||||||
<For each={groups()}>
|
<For each={groups()}>
|
||||||
{(group) => (
|
{(group) => (
|
||||||
<MenuV2.Group>
|
<Menu.Group>
|
||||||
<MenuV2.GroupLabel class="gap-2 px-3">
|
<Menu.GroupLabel class="gap-2 px-3">
|
||||||
<span class="min-w-0 truncate">{group.items[0].provider.name}</span>
|
<span class="min-w-0 truncate">{group.items[0].provider.name}</span>
|
||||||
</MenuV2.GroupLabel>
|
</Menu.GroupLabel>
|
||||||
<MenuV2.RadioGroup value={props.current}>
|
<Menu.RadioGroup value={props.current}>
|
||||||
<For each={group.items}>
|
<For each={group.items}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={6}
|
gutter={6}
|
||||||
@@ -470,7 +467,7 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MenuV2.RadioItem
|
<Menu.RadioItem
|
||||||
value={modelKey(item)}
|
value={modelKey(item)}
|
||||||
data-option-key={modelKey(item)}
|
data-option-key={modelKey(item)}
|
||||||
data-selected-model={props.current === modelKey(item) ? true : undefined}
|
data-selected-model={props.current === modelKey(item) ? true : undefined}
|
||||||
@@ -484,17 +481,17 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
>
|
>
|
||||||
<span class="min-w-0 truncate leading-5">{item.name}</span>
|
<span class="min-w-0 truncate leading-5">{item.name}</span>
|
||||||
<Show when={isFree(item.provider.id, item.cost)}>
|
<Show when={isFree(item.provider.id, item.cost)}>
|
||||||
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
|
<Badge class="shrink-0">{language.t("model.tag.free")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={item.latest}>
|
<Show when={item.latest}>
|
||||||
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
|
<Badge class="shrink-0">{language.t("model.tag.latest")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.RadioItem>
|
</Menu.RadioItem>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</MenuV2.RadioGroup>
|
</Menu.RadioGroup>
|
||||||
</MenuV2.Group>
|
</Menu.Group>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -502,7 +499,7 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
<div class="h-px bg-v2-border-border-muted" />
|
<div class="h-px bg-v2-border-border-muted" />
|
||||||
<div class="flex flex-col p-0.5">
|
<div class="flex flex-col p-0.5">
|
||||||
<MenuV2.Item
|
<Menu.Item
|
||||||
data-option-key={manageKey}
|
data-option-key={manageKey}
|
||||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === manageKey }}
|
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === manageKey }}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@@ -513,11 +510,11 @@ function ModelSelectorPopoverV2View(props: {
|
|||||||
>
|
>
|
||||||
<Icon name="outline-sliders" size="small" />
|
<Icon name="outline-sliders" size="small" />
|
||||||
<span class="min-w-0 flex-1 truncate leading-5">{language.t("dialog.model.manage")}</span>
|
<span class="min-w-0 flex-1 truncate leading-5">{language.t("dialog.model.manage")}</span>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
</MenuV2.Content>
|
</Menu.Content>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,18 +537,19 @@ export const DialogSelectModel: Component<{ provider?: string; model?: ModelStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog>
|
||||||
title={language.t("dialog.model.select.title")}
|
<DialogHeader hideClose>
|
||||||
action={
|
<DialogTitle>{language.t("dialog.model.select.title")}</DialogTitle>
|
||||||
<Button class="h-7 -my-1 text-14-medium" icon="plus-small" tabIndex={-1} onClick={provider}>
|
<Button class="h-7 -my-1 text-14-medium" icon="plus-small" tabIndex={-1} onClick={provider}>
|
||||||
{language.t("command.provider.connect")}
|
{language.t("command.provider.connect")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
</DialogHeader>
|
||||||
>
|
<DialogBody>
|
||||||
<ModelList provider={props.provider} model={props.model} onSelect={() => dialog.close()} />
|
<ModelList provider={props.provider} model={props.model} onSelect={() => dialog.close()} />
|
||||||
<Button variant="ghost" class="ml-3 mt-5 mb-6 text-text-base self-start" onClick={manage}>
|
<Button variant="ghost" class="ml-3 mt-5 mb-6 text-text-base self-start" onClick={manage}>
|
||||||
{language.t("dialog.model.manage")}
|
{language.t("dialog.model.manage")}
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
@@ -82,7 +83,7 @@ function ServerForm(props: ServerFormProps) {
|
|||||||
type="text"
|
type="text"
|
||||||
label={language.t("dialog.server.add.name")}
|
label={language.t("dialog.server.add.name")}
|
||||||
placeholder={language.t("dialog.server.add.namePlaceholder")}
|
placeholder={language.t("dialog.server.add.namePlaceholder")}
|
||||||
value={props.name}
|
defaultValue={props.name}
|
||||||
disabled={props.busy}
|
disabled={props.busy}
|
||||||
onChange={props.onNameChange}
|
onChange={props.onNameChange}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={keyDown}
|
||||||
@@ -92,7 +93,7 @@ function ServerForm(props: ServerFormProps) {
|
|||||||
type="text"
|
type="text"
|
||||||
label={language.t("dialog.server.add.username")}
|
label={language.t("dialog.server.add.username")}
|
||||||
placeholder={language.t("dialog.server.add.usernamePlaceholder")}
|
placeholder={language.t("dialog.server.add.usernamePlaceholder")}
|
||||||
value={props.username}
|
defaultValue={props.username}
|
||||||
disabled={props.busy}
|
disabled={props.busy}
|
||||||
onChange={props.onUsernameChange}
|
onChange={props.onUsernameChange}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={keyDown}
|
||||||
@@ -101,7 +102,7 @@ function ServerForm(props: ServerFormProps) {
|
|||||||
type="password"
|
type="password"
|
||||||
label={language.t("dialog.server.add.password")}
|
label={language.t("dialog.server.add.password")}
|
||||||
placeholder={language.t("dialog.server.add.passwordPlaceholder")}
|
placeholder={language.t("dialog.server.add.passwordPlaceholder")}
|
||||||
value={props.password}
|
defaultValue={props.password}
|
||||||
disabled={props.busy}
|
disabled={props.busy}
|
||||||
onChange={props.onPasswordChange}
|
onChange={props.onPasswordChange}
|
||||||
onKeyDown={keyDown}
|
onKeyDown={keyDown}
|
||||||
@@ -156,49 +157,47 @@ export function ServerConnectionList(props: {
|
|||||||
/>
|
/>
|
||||||
<div class="flex items-center justify-center gap-4 pl-4">
|
<div class="flex items-center justify-center gap-4 pl-4">
|
||||||
<Show when={i.type === "http"}>
|
<Show when={i.type === "http"}>
|
||||||
<DropdownMenu>
|
<Menu appearance="standard">
|
||||||
<DropdownMenu.Trigger
|
<Menu.Trigger
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
icon="dot-grid"
|
icon={<Icon name="dot-grid" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
|
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
|
||||||
onClick={(e: MouseEvent) => e.stopPropagation()}
|
onClick={(e: MouseEvent) => e.stopPropagation()}
|
||||||
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
|
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<Menu.Portal>
|
||||||
<DropdownMenu.Content class="mt-1">
|
<Menu.Content class="mt-1">
|
||||||
<DropdownMenu.Item
|
<Menu.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (i.type !== "http") return
|
if (i.type !== "http") return
|
||||||
props.onEdit(i)
|
props.onEdit(i)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
{language.t("dialog.server.menu.edit")}
|
||||||
</DropdownMenu.Item>
|
</Menu.Item>
|
||||||
<Show when={props.domain.defaults.available() && props.domain.defaults.key() !== key}>
|
<Show when={props.domain.defaults.available() && props.domain.defaults.key() !== key}>
|
||||||
<DropdownMenu.Item onSelect={() => props.domain.defaults.set(key)}>
|
<Menu.Item onSelect={() => props.domain.defaults.set(key)}>
|
||||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.default")}</DropdownMenu.ItemLabel>
|
{language.t("dialog.server.menu.default")}
|
||||||
</DropdownMenu.Item>
|
</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.domain.defaults.available() && props.domain.defaults.key() === key}>
|
<Show when={props.domain.defaults.available() && props.domain.defaults.key() === key}>
|
||||||
<DropdownMenu.Item onSelect={() => props.domain.defaults.set(null)}>
|
<Menu.Item onSelect={() => props.domain.defaults.set(null)}>
|
||||||
<DropdownMenu.ItemLabel>
|
{language.t("dialog.server.menu.defaultRemove")}
|
||||||
{language.t("dialog.server.menu.defaultRemove")}
|
</Menu.Item>
|
||||||
</DropdownMenu.ItemLabel>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.domain.connection.canRemove(key)}>
|
<Show when={props.domain.connection.canRemove(key)}>
|
||||||
<DropdownMenu.Separator />
|
<Menu.Separator />
|
||||||
<DropdownMenu.Item
|
<Menu.Item
|
||||||
onSelect={() => props.domain.connection.remove(key)}
|
onSelect={() => props.domain.connection.remove(key)}
|
||||||
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
{language.t("dialog.server.menu.delete")}
|
||||||
</DropdownMenu.Item>
|
</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
</DropdownMenu.Content>
|
</Menu.Content>
|
||||||
</DropdownMenu.Portal>
|
</Menu.Portal>
|
||||||
</DropdownMenu>
|
</Menu>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,7 +207,7 @@ export function ServerConnectionList(props: {
|
|||||||
|
|
||||||
<div class="shrink-0 pb-5">
|
<div class="shrink-0 pb-5">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="neutral"
|
||||||
icon="plus-small"
|
icon="plus-small"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={props.onAdd}
|
onClick={props.onAdd}
|
||||||
@@ -244,7 +243,7 @@ export function ServerConnectionForm(props: { form: ServerConnectionFormControll
|
|||||||
/>
|
/>
|
||||||
<div class="shrink-0 pb-5">
|
<div class="shrink-0 pb-5">
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="contrast"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={props.form.submit}
|
onClick={props.form.submit}
|
||||||
disabled={props.form.state.busy()}
|
disabled={props.form.state.busy()}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { usePlatform } from "@/context/platform"
|
|||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/dialog"
|
||||||
import { JSX } from "solid-js"
|
import { JSX } from "solid-js"
|
||||||
|
|
||||||
export type DialogGoUpsellProps = {
|
export type DialogGoUpsellProps = {
|
||||||
@@ -30,17 +30,22 @@ export function DialogUsageExceeded(props: DialogGoUpsellProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog title={props.title} description={props.description} fit>
|
<Dialog fit>
|
||||||
<div class="flex flex-col gap-4 pl-6 pr-2.5 pb-3">
|
<DialogHeader>
|
||||||
<div class="flex justify-end gap-2">
|
<DialogTitleGroup title={props.title} description={props.description} />
|
||||||
<Button variant="ghost" size="large" onClick={dismiss}>
|
</DialogHeader>
|
||||||
{language.t("dialog.usageExceeded.dontShowAgain")}
|
<DialogBody>
|
||||||
</Button>
|
<div class="flex flex-col gap-4 pl-6 pr-2.5 pb-3">
|
||||||
<Button variant="primary" size="large" onClick={runAction}>
|
<div class="flex justify-end gap-2">
|
||||||
{props.actionLabel}
|
<Button variant="ghost" size="large" onClick={dismiss}>
|
||||||
</Button>
|
{language.t("dialog.usageExceeded.dontShowAgain")}
|
||||||
|
</Button>
|
||||||
|
<Button variant="contrast" size="large" onClick={runAction}>
|
||||||
|
{props.actionLabel}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogBody>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ export function nativePickerPath(path: string) {
|
|||||||
if (/^[A-Za-z]:\//.test(value) || value.startsWith("//")) return value.replaceAll("/", "\\")
|
if (/^[A-Za-z]:\//.test(value) || value.startsWith("//")) return value.replaceAll("/", "\\")
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { ServerSDK } from "@/context/server-sdk"
|
import { ServerSDK } from "@/context/server-sdk"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
import { useMutation } from "@tanstack/solid-query"
|
||||||
import { normalizeProjectInfo } from "@/context/global-sync/utils"
|
import { normalizeProjectInfo } from "@/context/global-sync/utils"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import "@opencode-ai/ui/v2/file-tree-v2.css"
|
import "@opencode-ai/ui/file-tree.css"
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
createMemo,
|
createMemo,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import type { FileNode } from "@/types"
|
import type { FileNode } from "@/types"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { pathToFileUrl, withFileDragImage, type Kind } from "@/components/file-tree"
|
import { pathToFileUrl, withFileDragImage, type Kind } from "@/components/file-tree"
|
||||||
import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual"
|
import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { type Component, For, Show, createEffect, createMemo, createResource, createSignal } from "solid-js"
|
import { type Component, For, Show, createEffect, createMemo, createResource, createSignal } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useMcpToggle } from "@/context/mcp"
|
import { useMcpToggle } from "@/context/mcp"
|
||||||
import { useWorkspaceLocation } from "@/context/location"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
|
import { pluginLabel } from "@/utils/plugin"
|
||||||
import { ExternalLink } from "./external-link"
|
import { ExternalLink } from "./external-link"
|
||||||
|
|
||||||
type SkillItem = {
|
type SkillItem = {
|
||||||
@@ -90,8 +91,8 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
.sort()
|
.sort()
|
||||||
})
|
})
|
||||||
const mcpEnabled = (name: string) =>
|
const mcpEnabled = (name: string) =>
|
||||||
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)?.status
|
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)
|
||||||
.status === "connected"
|
?.status.status === "connected"
|
||||||
|
|
||||||
const [globalPluginList] = createResource(
|
const [globalPluginList] = createResource(
|
||||||
() => serverSDK.connection.status() === "connected",
|
() => serverSDK.connection.status() === "connected",
|
||||||
@@ -101,10 +102,10 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
() => (serverSDK.connection.status() === "connected" ? directorySDK().directory : undefined),
|
() => (serverSDK.connection.status() === "connected" ? directorySDK().directory : undefined),
|
||||||
(directory) => serverSDK.api.plugin.list({ location: { directory } }).then((result) => result.data),
|
(directory) => serverSDK.api.plugin.list({ location: { directory } }).then((result) => result.data),
|
||||||
)
|
)
|
||||||
const globalPlugins = createMemo(() => (globalPluginList.latest ?? []).map((item) => item.id))
|
const globalPlugins = createMemo(() => (globalPluginList.latest ?? []).map(pluginLabel))
|
||||||
const projectPlugins = createMemo(() => {
|
const projectPlugins = createMemo(() => {
|
||||||
const shared = new Set(globalPlugins())
|
const shared = new Set(globalPlugins())
|
||||||
return (projectPluginList.latest ?? []).map((item) => item.id).filter((name) => !shared.has(name))
|
return (projectPluginList.latest ?? []).map(pluginLabel).filter((name) => !shared.has(name))
|
||||||
})
|
})
|
||||||
|
|
||||||
const serverSkills = createMemo(() => data.location.skill.list() ?? [])
|
const serverSkills = createMemo(() => data.location.skill.list() ?? [])
|
||||||
@@ -148,15 +149,15 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
<span>{language.t("project.settings.extensions.description")}</span>
|
<span>{language.t("project.settings.extensions.description")}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TabsV2 variant="pill" defaultValue="mcps" class="project-settings-extension-tabs">
|
<Tabs variant="pill" defaultValue="mcps" class="project-settings-extension-tabs">
|
||||||
<TabsV2.List>
|
<Tabs.List>
|
||||||
<TabsV2.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</TabsV2.Trigger>
|
<Tabs.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</TabsV2.Trigger>
|
<Tabs.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</TabsV2.Trigger>
|
<Tabs.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</Tabs.Trigger>
|
||||||
{/* TODO: Restore LSP status when V2 exposes it. */}
|
{/* TODO: Restore LSP status when V2 exposes it. */}
|
||||||
</TabsV2.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<TabsV2.Content value="mcps">
|
<Tabs.Content value="mcps">
|
||||||
<div class="project-settings-extension-section">
|
<div class="project-settings-extension-section">
|
||||||
<div class="project-settings-extension-section-header">
|
<div class="project-settings-extension-section-header">
|
||||||
<span>{language.t("project.settings.extensions.added")}</span>
|
<span>{language.t("project.settings.extensions.added")}</span>
|
||||||
@@ -167,9 +168,9 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
</Show>
|
</Show>
|
||||||
<SharedSection count={globalMcpNames().length}>{mcpRows(globalMcpNames())}</SharedSection>
|
<SharedSection count={globalMcpNames().length}>{mcpRows(globalMcpNames())}</SharedSection>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="plugins">
|
<Tabs.Content value="plugins">
|
||||||
<div class="project-settings-extension-section">
|
<div class="project-settings-extension-section">
|
||||||
<div class="project-settings-extension-section-header">
|
<div class="project-settings-extension-section-header">
|
||||||
<span>{language.t("project.settings.extensions.added")}</span>
|
<span>{language.t("project.settings.extensions.added")}</span>
|
||||||
@@ -180,9 +181,9 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
</Show>
|
</Show>
|
||||||
<SharedSection count={globalPlugins().length}>{pluginRows(globalPlugins())}</SharedSection>
|
<SharedSection count={globalPlugins().length}>{pluginRows(globalPlugins())}</SharedSection>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="skills">
|
<Tabs.Content value="skills">
|
||||||
<div class="project-settings-extension-section">
|
<div class="project-settings-extension-section">
|
||||||
<div class="project-settings-extension-section-header">
|
<div class="project-settings-extension-section-header">
|
||||||
<span>{language.t("project.settings.extensions.added")}</span>
|
<span>{language.t("project.settings.extensions.added")}</span>
|
||||||
@@ -195,9 +196,8 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
</Show>
|
</Show>
|
||||||
<SharedSection count={serverSkills().length}>{skillRows(serverSkills())}</SharedSection>
|
<SharedSection count={serverSkills().length}>{skillRows(serverSkills())}</SharedSection>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
</Tabs>
|
||||||
</TabsV2>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import type { ReferenceInfo } from "@opencode-ai/client/promise"
|
import type { ReferenceInfo } from "@opencode-ai/client/promise"
|
||||||
import { createEffect, createMemo, on, Show } from "solid-js"
|
import { createEffect, createMemo, on, Show } from "solid-js"
|
||||||
import { ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
|
import { ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
|
||||||
@@ -503,20 +503,20 @@ function PromptInputV2ModelControl(props: {
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Show when={!props.loading}>
|
<Show when={!props.loading}>
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="top"
|
placement="top"
|
||||||
gutter={4}
|
gutter={4}
|
||||||
value={
|
value={
|
||||||
<>
|
<>
|
||||||
{props.title}
|
{props.title}
|
||||||
<KeybindV2 keys={props.keybind} variant="neutral" />
|
<Keybind keys={props.keybind} variant="neutral" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={props.paid}
|
when={props.paid}
|
||||||
fallback={
|
fallback={
|
||||||
<ButtonV2
|
<Button
|
||||||
data-action="prompt-model"
|
data-action="prompt-model"
|
||||||
data-control-type="dialog"
|
data-control-type="dialog"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
@@ -527,13 +527,13 @@ function PromptInputV2ModelControl(props: {
|
|||||||
onClick={props.onUnpaidClick}
|
onClick={props.onUnpaidClick}
|
||||||
>
|
>
|
||||||
{content()}
|
{content()}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ModelSelectorPopoverV2
|
<ModelSelectorPopoverV2
|
||||||
model={props.model}
|
model={props.model}
|
||||||
trigger={(triggerProps) => (
|
trigger={(triggerProps) => (
|
||||||
<ButtonV2
|
<Button
|
||||||
{...triggerProps}
|
{...triggerProps}
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="normal"
|
size="normal"
|
||||||
@@ -544,12 +544,12 @@ function PromptInputV2ModelControl(props: {
|
|||||||
data-control-type="popover"
|
data-control-type="popover"
|
||||||
>
|
>
|
||||||
{content()}
|
{content()}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
)}
|
)}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import type { FileSelection } from "@/context/file"
|
import type { FileSelection } from "@/context/file"
|
||||||
import { encodeFilePath } from "@/context/file/path"
|
import { encodeFilePath } from "@/context/file/path"
|
||||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ beforeAll(async () => {
|
|||||||
showToast: () => 0,
|
showToast: () => 0,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@opencode-ai/core/util/encode", () => ({
|
mock.module("@opencode-ai/util/encode", () => ({
|
||||||
base64Decode: (value: string) => value,
|
base64Decode: (value: string) => value,
|
||||||
base64Encode: (value: string) => value,
|
base64Encode: (value: string) => value,
|
||||||
checksum: (value: string) => value,
|
checksum: (value: string) => value,
|
||||||
@@ -407,6 +407,12 @@ describe("prompt submit worktree selection", () => {
|
|||||||
text: "ls",
|
text: "ls",
|
||||||
files: [],
|
files: [],
|
||||||
agents: [],
|
agents: [],
|
||||||
|
metadata: {
|
||||||
|
displayText: "ls",
|
||||||
|
comments: [],
|
||||||
|
agent: "agent",
|
||||||
|
model: { providerID: "provider", modelID: "model", variant: "high" },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Data } from "@opencode-ai/client/solid"
|
import type { Data } from "@opencode-ai/client/solid"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { startTransition, type Accessor } from "solid-js"
|
import { startTransition, type Accessor } from "solid-js"
|
||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
@@ -12,7 +12,7 @@ import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt
|
|||||||
import { useWorkspaceLocation } from "@/context/location"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { getDirectory } from "@opencode-ai/core/util/path"
|
import { getDirectory } from "@opencode-ai/util/path"
|
||||||
import { buildPromptRequest } from "./build-prompt-request"
|
import { buildPromptRequest } from "./build-prompt-request"
|
||||||
import { setCursorPosition } from "./editor-dom"
|
import { setCursorPosition } from "./editor-dom"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
@@ -63,7 +63,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||||||
|
|
||||||
const [head, ...tail] = text.split(" ")
|
const [head, ...tail] = text.split(" ")
|
||||||
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
||||||
if (cmd && input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)) {
|
if (
|
||||||
|
cmd &&
|
||||||
|
input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)
|
||||||
|
) {
|
||||||
setBusy()
|
setBusy()
|
||||||
try {
|
try {
|
||||||
const messageID = Identifier.ascending("message")
|
const messageID = Identifier.ascending("message")
|
||||||
@@ -135,6 +138,15 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||||||
text: request.text,
|
text: request.text,
|
||||||
files: request.files.map((file) => ({ uri: file.uri, name: file.name, mention: file.mention })),
|
files: request.files.map((file) => ({ uri: file.uri, name: file.name, mention: file.mention })),
|
||||||
agents: request.agents,
|
agents: request.agents,
|
||||||
|
metadata: {
|
||||||
|
displayText: request.displayText,
|
||||||
|
comments: request.comments,
|
||||||
|
agent: input.draft.agent,
|
||||||
|
model: {
|
||||||
|
...input.draft.model,
|
||||||
|
...(input.draft.variant ? { variant: input.draft.variant } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -197,8 +209,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
if (!sessionID) return Promise.resolve()
|
if (!sessionID) return Promise.resolve()
|
||||||
input.onAbort?.()
|
input.onAbort?.()
|
||||||
|
|
||||||
return serverSDK.api.session.interrupt({ sessionID })
|
return serverSDK.api.session.interrupt({ sessionID }).catch(() => {})
|
||||||
.catch(() => {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoreCommentItems = (
|
const restoreCommentItems = (
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import {
|
|||||||
type ComponentProps,
|
type ComponentProps,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { ProjectAvatar } from "@opencode-ai/ui/project-avatar"
|
||||||
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
|
|
||||||
import { getProjectAvatarVariant } from "@/context/layout"
|
import { getProjectAvatarVariant } from "@/context/layout"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
||||||
@@ -277,7 +276,8 @@ export function PromptProjectSelector(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<Menu
|
||||||
|
appearance="standard"
|
||||||
open={triggerReady() && props.controller.open()}
|
open={triggerReady() && props.controller.open()}
|
||||||
placement={props.placement ?? "bottom"}
|
placement={props.placement ?? "bottom"}
|
||||||
gutter={4}
|
gutter={4}
|
||||||
@@ -287,9 +287,9 @@ export function PromptProjectSelector(props: {
|
|||||||
props.controller.setOpen(open)
|
props.controller.setOpen(open)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.Trigger as={ProjectTrigger} ref={setTriggerRef} controller={props.controller} />
|
<Menu.Trigger as={ProjectTrigger} ref={setTriggerRef} controller={props.controller} />
|
||||||
<DropdownMenu.Portal>
|
<Menu.Portal>
|
||||||
<DropdownMenu.Content
|
<Menu.Content
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
id="prompt-project-menu"
|
id="prompt-project-menu"
|
||||||
class="w-[243px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none [&[data-closed]]:!animate-none"
|
class="w-[243px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none [&[data-closed]]:!animate-none"
|
||||||
@@ -360,13 +360,13 @@ export function PromptProjectSelector(props: {
|
|||||||
<Show
|
<Show
|
||||||
when={props.controller.servers().length > 1}
|
when={props.controller.servers().length > 1}
|
||||||
fallback={
|
fallback={
|
||||||
<DropdownMenu.RadioGroup value={selectedValue()}>
|
<Menu.RadioGroup value={selectedValue()}>
|
||||||
<For each={props.controller.projects()}>
|
<For each={props.controller.projects()}>
|
||||||
{(project) => (
|
{(project) => (
|
||||||
<ProjectItem project={project} controller={props.controller} onSelect={selectProject} />
|
<ProjectItem project={project} controller={props.controller} onSelect={selectProject} />
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</DropdownMenu.RadioGroup>
|
</Menu.RadioGroup>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<For
|
<For
|
||||||
@@ -381,7 +381,7 @@ export function PromptProjectSelector(props: {
|
|||||||
<div class="flex h-7 select-none items-center pl-1.5 pr-3 text-[11px] font-[530] leading-none tracking-[0.05px] text-v2-text-text-faint">
|
<div class="flex h-7 select-none items-center pl-1.5 pr-3 text-[11px] font-[530] leading-none tracking-[0.05px] text-v2-text-text-faint">
|
||||||
{server!.name}
|
{server!.name}
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu.RadioGroup value={selectedValue()}>
|
<Menu.RadioGroup value={selectedValue()}>
|
||||||
<For
|
<For
|
||||||
each={props.controller.projects().filter((project) => project.server?.key === server!.key)}
|
each={props.controller.projects().filter((project) => project.server?.key === server!.key)}
|
||||||
>
|
>
|
||||||
@@ -389,7 +389,7 @@ export function PromptProjectSelector(props: {
|
|||||||
<ProjectItem project={project} controller={props.controller} onSelect={selectProject} />
|
<ProjectItem project={project} controller={props.controller} onSelect={selectProject} />
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</DropdownMenu.RadioGroup>
|
</Menu.RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -408,8 +408,8 @@ export function PromptProjectSelector(props: {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DropdownMenu.Sub>
|
<Menu.Sub>
|
||||||
<DropdownMenu.SubTrigger
|
<Menu.SubTrigger
|
||||||
id={props.controller.actionKey()}
|
id={props.controller.actionKey()}
|
||||||
data-option-key={props.controller.actionKey()}
|
data-option-key={props.controller.actionKey()}
|
||||||
class={projectActionClass}
|
class={projectActionClass}
|
||||||
@@ -419,24 +419,23 @@ export function PromptProjectSelector(props: {
|
|||||||
onMouseEnter={() => props.controller.setActive(props.controller.actionKey())}
|
onMouseEnter={() => props.controller.setActive(props.controller.actionKey())}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size="small" />
|
<Icon name="plus" size="small" />
|
||||||
<span data-slot="dropdown-menu-item-label" class="min-w-0 flex-1 truncate leading-5">
|
<span class="min-w-0 flex-1 truncate leading-5">
|
||||||
{props.controller.labels.add()}
|
{props.controller.labels.add()}
|
||||||
</span>
|
</span>
|
||||||
<Icon name="chevron-right" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
</Menu.SubTrigger>
|
||||||
</DropdownMenu.SubTrigger>
|
<Menu.Portal>
|
||||||
<DropdownMenu.Portal>
|
<Menu.SubContent class="min-w-[180px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 p-0.5 shadow-[var(--v2-elevation-floating)] focus:outline-none">
|
||||||
<DropdownMenu.SubContent class="min-w-[180px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 p-0.5 shadow-[var(--v2-elevation-floating)] focus:outline-none">
|
|
||||||
<For each={props.controller.servers()}>
|
<For each={props.controller.servers()}>
|
||||||
{(server) => <ServerAction server={server!} onSelect={selectAction} />}
|
{(server) => <ServerAction server={server!} onSelect={selectAction} />}
|
||||||
</For>
|
</For>
|
||||||
</DropdownMenu.SubContent>
|
</Menu.SubContent>
|
||||||
</DropdownMenu.Portal>
|
</Menu.Portal>
|
||||||
</DropdownMenu.Sub>
|
</Menu.Sub>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Content>
|
</Menu.Content>
|
||||||
</DropdownMenu.Portal>
|
</Menu.Portal>
|
||||||
</DropdownMenu>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +506,7 @@ function ProjectItem(props: {
|
|||||||
}) {
|
}) {
|
||||||
const key = () => props.controller.projectKey(props.project)
|
const key = () => props.controller.projectKey(props.project)
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.RadioItem
|
<Menu.RadioItem
|
||||||
id={key()}
|
id={key()}
|
||||||
value={key()}
|
value={key()}
|
||||||
data-option-key={key()}
|
data-option-key={key()}
|
||||||
@@ -534,11 +533,8 @@ function ProjectItem(props: {
|
|||||||
src={getProjectAvatarSource(props.project.id, props.project.icon)}
|
src={getProjectAvatarSource(props.project.id, props.project.icon)}
|
||||||
variant={getProjectAvatarVariant(props.project.icon?.color)}
|
variant={getProjectAvatarVariant(props.project.icon?.color)}
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.ItemLabel class="min-w-0 truncate leading-5">{displayName(props.project)}</DropdownMenu.ItemLabel>
|
<span class="min-w-0 truncate leading-5">{displayName(props.project)}</span>
|
||||||
<DropdownMenu.ItemIndicator style={{ width: "14px", height: "14px", right: "12px" }}>
|
</Menu.RadioItem>
|
||||||
<IconV2 name="check" size="small" class="shrink-0 text-v2-icon-icon-base" />
|
|
||||||
</DropdownMenu.ItemIndicator>
|
|
||||||
</DropdownMenu.RadioItem>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +548,7 @@ function ProjectAction(props: {
|
|||||||
}) {
|
}) {
|
||||||
const key = () => props.controller.actionKey(props.server)
|
const key = () => props.controller.actionKey(props.server)
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Item
|
<Menu.Item
|
||||||
id={key()}
|
id={key()}
|
||||||
data-option-key={key()}
|
data-option-key={key()}
|
||||||
class="h-7 gap-2 rounded-sm px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base data-[highlighted]:!bg-v2-overlay-simple-overlay-hover"
|
class="h-7 gap-2 rounded-sm px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base data-[highlighted]:!bg-v2-overlay-simple-overlay-hover"
|
||||||
@@ -573,17 +569,17 @@ function ProjectAction(props: {
|
|||||||
onSelect={() => props.onSelect(props.server)}
|
onSelect={() => props.onSelect(props.server)}
|
||||||
>
|
>
|
||||||
<Icon name="plus" size="small" />
|
<Icon name="plus" size="small" />
|
||||||
<DropdownMenu.ItemLabel class="min-w-0 truncate leading-5">
|
<span class="min-w-0 truncate leading-5">
|
||||||
{props.controller.labels.add()}
|
{props.controller.labels.add()}
|
||||||
</DropdownMenu.ItemLabel>
|
</span>
|
||||||
</DropdownMenu.Item>
|
</Menu.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServerAction(props: { server: { key: string; name: string }; onSelect: (server: string) => void }) {
|
function ServerAction(props: { server: { key: string; name: string }; onSelect: (server: string) => void }) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Item class={projectActionClass} onSelect={() => props.onSelect(props.server.key)}>
|
<Menu.Item class={projectActionClass} onSelect={() => props.onSelect(props.server.key)}>
|
||||||
<DropdownMenu.ItemLabel class="min-w-0 flex-1 truncate leading-5">{props.server.name}</DropdownMenu.ItemLabel>
|
<span class="min-w-0 flex-1 truncate leading-5">{props.server.name}</span>
|
||||||
</DropdownMenu.Item>
|
</Menu.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { sameDirectory } from "@/utils/workspace"
|
import { sameDirectory } from "@/utils/workspace"
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="top"
|
placement="top"
|
||||||
openDelay={800}
|
openDelay={800}
|
||||||
value={
|
value={
|
||||||
@@ -77,8 +77,8 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
contentClass={props.onboarding ? "max-w-[280px]" : undefined}
|
contentClass={props.onboarding ? "max-w-[280px]" : undefined}
|
||||||
class="min-w-0"
|
class="min-w-0"
|
||||||
>
|
>
|
||||||
<MenuV2 placement="bottom" gutter={4} onOpenChange={onOpenChange}>
|
<Menu placement="bottom" gutter={4} onOpenChange={onOpenChange}>
|
||||||
<MenuV2.Trigger
|
<Menu.Trigger
|
||||||
aria-description={language.t("session.new.workspace.trigger.tooltip")}
|
aria-description={language.t("session.new.workspace.trigger.tooltip")}
|
||||||
class="flex h-6 min-w-0 max-w-[203px] items-center gap-1.5 rounded-sm px-1.5 hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none data-[expanded]:bg-v2-overlay-simple-overlay-pressed data-[expanded]:text-v2-text-text-muted"
|
class="flex h-6 min-w-0 max-w-[203px] items-center gap-1.5 rounded-sm px-1.5 hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none data-[expanded]:bg-v2-overlay-simple-overlay-pressed data-[expanded]:text-v2-text-text-muted"
|
||||||
>
|
>
|
||||||
@@ -92,14 +92,14 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
<Icon name="chevron-down" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||||
</MenuV2.Trigger>
|
</Menu.Trigger>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.Content class="w-[200px]">
|
<Menu.Content class="w-[200px]">
|
||||||
<MenuV2.Group>
|
<Menu.Group>
|
||||||
<MenuV2.GroupLabel>{language.t("session.new.workspace.runIn")}</MenuV2.GroupLabel>
|
<Menu.GroupLabel>{language.t("session.new.workspace.runIn")}</Menu.GroupLabel>
|
||||||
<MenuV2.Item onSelect={() => select("main")}>
|
<Menu.Item onSelect={() => select("main")}>
|
||||||
<Icon name="monitor" />
|
<Icon name="monitor" />
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="right"
|
placement="right"
|
||||||
openDelay={800}
|
openDelay={800}
|
||||||
value={
|
value={
|
||||||
@@ -113,14 +113,14 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
class="min-w-0 flex-1"
|
class="min-w-0 flex-1"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate">{language.t("session.new.workspace.local")}</span>
|
<span class="min-w-0 truncate">{language.t("session.new.workspace.local")}</span>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
<Show when={selected() === "main"}>
|
<Show when={selected() === "main"}>
|
||||||
<Icon name="check" size="small" class="shrink-0" />
|
<Icon name="check" size="small" class="shrink-0" />
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
<MenuV2.Item onSelect={() => select("create")}>
|
<Menu.Item onSelect={() => select("create")}>
|
||||||
<Icon name="workspace-new" />
|
<Icon name="workspace-new" />
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="right"
|
placement="right"
|
||||||
openDelay={800}
|
openDelay={800}
|
||||||
value={
|
value={
|
||||||
@@ -134,25 +134,25 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
class="min-w-0 flex-1"
|
class="min-w-0 flex-1"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate">{language.t("workspace.new")}</span>
|
<span class="min-w-0 truncate">{language.t("workspace.new")}</span>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
<Show when={selected() === "create"}>
|
<Show when={selected() === "create"}>
|
||||||
<Icon name="check" size="small" class="shrink-0" />
|
<Icon name="check" size="small" class="shrink-0" />
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</MenuV2.Group>
|
</Menu.Group>
|
||||||
<Show
|
<Show
|
||||||
when={props.workspaces.length > 0}
|
when={props.workspaces.length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
<>
|
<>
|
||||||
<MenuV2.Separator class="h-[0.5px]" />
|
<Menu.Separator class="h-[0.5px]" />
|
||||||
<MenuV2.Item onSelect={() => (pending = { type: "viewAll" })}>
|
<Menu.Item onSelect={() => (pending = { type: "viewAll" })}>
|
||||||
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MenuV2.Separator class="h-[0.5px]" />
|
<Menu.Separator class="h-[0.5px]" />
|
||||||
<MenuV2.Sub
|
<Menu.Sub
|
||||||
gutter={0}
|
gutter={0}
|
||||||
overlap
|
overlap
|
||||||
overflowPadding={8}
|
overflowPadding={8}
|
||||||
@@ -166,7 +166,7 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
requestAnimationFrame(() => searchInput?.focus())
|
requestAnimationFrame(() => searchInput?.focus())
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuV2.SubTrigger
|
<Menu.SubTrigger
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (
|
if (
|
||||||
event.key === "ArrowRight" ||
|
event.key === "ArrowRight" ||
|
||||||
@@ -181,9 +181,9 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
<span class="min-w-0 flex-1 truncate">
|
<span class="min-w-0 flex-1 truncate">
|
||||||
{language.t("session.new.workspace.existing").replace(/(…|\.{3})$/, "")}
|
{language.t("session.new.workspace.existing").replace(/(…|\.{3})$/, "")}
|
||||||
</span>
|
</span>
|
||||||
</MenuV2.SubTrigger>
|
</Menu.SubTrigger>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.SubContent class="max-h-[calc(100dvh-16px)] w-[200px] overflow-y-auto">
|
<Menu.SubContent class="max-h-[calc(100dvh-16px)] w-[200px] overflow-y-auto">
|
||||||
<Show when={props.workspaces.length >= 10}>
|
<Show when={props.workspaces.length >= 10}>
|
||||||
<div class="flex h-7 items-center gap-2 rounded-sm ps-3 pe-2 text-v2-icon-icon-muted">
|
<div class="flex h-7 items-center gap-2 rounded-sm ps-3 pe-2 text-v2-icon-icon-muted">
|
||||||
<Icon name="magnifying-glass" size="small" class="shrink-0" />
|
<Icon name="magnifying-glass" size="small" class="shrink-0" />
|
||||||
@@ -211,27 +211,27 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
</Show>
|
</Show>
|
||||||
<For each={workspaces()}>
|
<For each={workspaces()}>
|
||||||
{(workspace) => (
|
{(workspace) => (
|
||||||
<MenuV2.Item onSelect={() => select(workspace)}>
|
<Menu.Item onSelect={() => select(workspace)}>
|
||||||
<Icon name="workspace-isolated" />
|
<Icon name="workspace-isolated" />
|
||||||
<span class="min-w-0 flex-1 truncate">{getFilename(workspace)}</span>
|
<span class="min-w-0 flex-1 truncate">{getFilename(workspace)}</span>
|
||||||
<Show when={selected() === workspace}>
|
<Show when={selected() === workspace}>
|
||||||
<Icon name="check" size="small" class="shrink-0" />
|
<Icon name="check" size="small" class="shrink-0" />
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<MenuV2.Separator class="h-[0.5px]" />
|
<Menu.Separator class="h-[0.5px]" />
|
||||||
<MenuV2.Item onSelect={() => (pending = { type: "viewAll" })}>
|
<Menu.Item onSelect={() => (pending = { type: "viewAll" })}>
|
||||||
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</MenuV2.SubContent>
|
</Menu.SubContent>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2.Sub>
|
</Menu.Sub>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Content>
|
</Menu.Content>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2>
|
</Menu>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
<PromptGitStatus branch={props.branch} from={selected() === "create"} class="ms-1" />
|
<PromptGitStatus branch={props.branch} from={selected() === "create"} class="ms-1" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -255,7 +255,7 @@ export function PromptGitStatus(props: { branch?: string; noGit?: boolean; from?
|
|||||||
return (
|
return (
|
||||||
<Show when={label()}>
|
<Show when={label()}>
|
||||||
{(value) => (
|
{(value) => (
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="top"
|
placement="top"
|
||||||
value={value()}
|
value={value()}
|
||||||
class={`min-w-0 max-w-[220px] ${props.class ?? ""}`}
|
class={`min-w-0 max-w-[220px] ${props.class ?? ""}`}
|
||||||
@@ -265,7 +265,7 @@ export function PromptGitStatus(props: { branch?: string; noGit?: boolean; from?
|
|||||||
<Icon name={icon()} size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
<Icon name={icon()} size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||||
<span class="min-w-0 truncate">{value()}</span>
|
<span class="min-w-0 truncate">{value()}</span>
|
||||||
</div>
|
</div>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { type Component, Show } from "solid-js"
|
import { type Component, Show } from "solid-js"
|
||||||
import type { ServerActionsController } from "@/components/server/server-management-controller"
|
import type { ServerActionsController } from "@/components/server/server-management-controller"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -59,19 +59,19 @@ export const ServerRowMenuView: Component<{
|
|||||||
const builtin = () => ServerConnection.builtin(props.server)
|
const builtin = () => ServerConnection.builtin(props.server)
|
||||||
const httpServer = () => (props.server.type === "http" ? props.server : undefined)
|
const httpServer = () => (props.server.type === "http" ? props.server : undefined)
|
||||||
return (
|
return (
|
||||||
<MenuV2 gutter={6} modal={false} placement="bottom-end" open={props.open} onOpenChange={props.onOpenChange}>
|
<Menu gutter={6} modal={false} placement="bottom-end" open={props.open} onOpenChange={props.onOpenChange}>
|
||||||
<MenuV2.Trigger
|
<Menu.Trigger
|
||||||
as={IconButtonV2}
|
as={IconButton}
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<IconV2 name="outline-dots" />}
|
icon={<Icon name="outline-dots" />}
|
||||||
aria-label={props.labels.more}
|
aria-label={props.labels.more}
|
||||||
/>
|
/>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.Content>
|
<Menu.Content>
|
||||||
<MenuV2.Group>
|
<Menu.Group>
|
||||||
<MenuV2.GroupLabel>{props.labels.server}</MenuV2.GroupLabel>
|
<Menu.GroupLabel>{props.labels.server}</Menu.GroupLabel>
|
||||||
<MenuV2.Item
|
<Menu.Item
|
||||||
disabled={builtin() || !httpServer()}
|
disabled={builtin() || !httpServer()}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
const server = httpServer()
|
const server = httpServer()
|
||||||
@@ -79,20 +79,20 @@ export const ServerRowMenuView: Component<{
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.labels.edit}
|
{props.labels.edit}
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
<Show when={props.canDefault && !props.isDefault}>
|
<Show when={props.canDefault && !props.isDefault}>
|
||||||
<MenuV2.Item onSelect={props.onSetDefault}>{props.labels.default}</MenuV2.Item>
|
<Menu.Item onSelect={props.onSetDefault}>{props.labels.default}</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.canDefault && props.isDefault}>
|
<Show when={props.canDefault && props.isDefault}>
|
||||||
<MenuV2.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</MenuV2.Item>
|
<Menu.Item onSelect={props.onRemoveDefault}>{props.labels.defaultRemove}</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.canRemove}>
|
<Show when={props.canRemove}>
|
||||||
<MenuV2.Separator />
|
<Menu.Separator />
|
||||||
<MenuV2.Item onSelect={props.onRemove}>{props.labels.delete}</MenuV2.Item>
|
<Menu.Item onSelect={props.onRemove}>{props.labels.delete}</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Group>
|
</Menu.Group>
|
||||||
</MenuV2.Content>
|
</Menu.Content>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export function ServerRow(props: ServerRowProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
appearance="standard"
|
||||||
class="flex-1 min-w-0"
|
class="flex-1 min-w-0"
|
||||||
value={tooltipValue()}
|
value={tooltipValue()}
|
||||||
contentStyle={{ "max-width": "none", "white-space": "nowrap" }}
|
contentStyle={{ "max-width": "none", "white-space": "nowrap" }}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Show, createMemo, type ComponentProps, type JSX } from "solid-js"
|
import { Show, createMemo, type ComponentProps, type JSX } from "solid-js"
|
||||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||||
import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
|
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
@@ -16,7 +15,7 @@ import { createSessionTabs } from "@/pages/session/helpers"
|
|||||||
|
|
||||||
interface SessionContextUsageProps {
|
interface SessionContextUsageProps {
|
||||||
variant?: "button" | "indicator"
|
variant?: "button" | "indicator"
|
||||||
placement?: ComponentProps<typeof TooltipV2>["placement"]
|
placement?: ComponentProps<typeof Tooltip>["placement"]
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
|
function ContextTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
|
||||||
@@ -111,24 +110,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
const circle = () => (
|
const circle = () => (
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<ProgressCircle
|
<ProgressCircle
|
||||||
|
appearance="indicator"
|
||||||
size={16}
|
size={16}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
percentage={context()?.usage ?? 0}
|
percentage={context()?.usage ?? 0}
|
||||||
style={
|
style={{
|
||||||
variant() === "indicator"
|
"--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
|
||||||
? {
|
"--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
|
||||||
"--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
|
"--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
|
||||||
"--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
|
}}
|
||||||
"--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
const circleV2 = () => (
|
const circleV2 = () => (
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<ProgressCircleV2 percentage={context()?.usage ?? 0} />
|
<ProgressCircle appearance="compact" percentage={context()?.usage ?? 0} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,11 +141,11 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={params.id}>
|
<Show when={params.id}>
|
||||||
<TooltipV2 value={tooltipValue()} placement={props.placement ?? "top"} shift={-8}>
|
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"} shift={-8}>
|
||||||
<Show
|
<Show
|
||||||
when={variant() === "indicator"}
|
when={variant() === "indicator"}
|
||||||
fallback={
|
fallback={
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="large"
|
size="large"
|
||||||
@@ -161,7 +157,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
>
|
>
|
||||||
{circle()}
|
{circle()}
|
||||||
</Show>
|
</Show>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createSignal, For, Show, type ComponentProps, type JSX } from "solid-js"
|
import { createSignal, For, Show, type ComponentProps, type JSX } from "solid-js"
|
||||||
import type { Project } from "@/types"
|
import type { Project } from "@/types"
|
||||||
@@ -17,7 +17,7 @@ export function SessionWorkspaceMenu(props: {
|
|||||||
sessionID: string
|
sessionID: string
|
||||||
project: Project
|
project: Project
|
||||||
directory: string
|
directory: string
|
||||||
placement?: ComponentProps<typeof MenuV2>["placement"]
|
placement?: ComponentProps<typeof Menu>["placement"]
|
||||||
gutter?: number
|
gutter?: number
|
||||||
class?: string
|
class?: string
|
||||||
contentClass?: string
|
contentClass?: string
|
||||||
@@ -71,57 +71,57 @@ export function SessionWorkspaceMenu(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuV2
|
<Menu
|
||||||
placement={props.placement ?? "bottom-end"}
|
placement={props.placement ?? "bottom-end"}
|
||||||
gutter={props.gutter ?? 4}
|
gutter={props.gutter ?? 4}
|
||||||
modal={false}
|
modal={false}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
>
|
>
|
||||||
<MenuV2.Trigger class={props.class} disabled={blocked()}>
|
<Menu.Trigger class={props.class} disabled={blocked()}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</MenuV2.Trigger>
|
</Menu.Trigger>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.Content class={`w-[200px] ${props.contentClass ?? ""}`}>
|
<Menu.Content class={`w-[200px] ${props.contentClass ?? ""}`}>
|
||||||
<MenuV2.Group>
|
<Menu.Group>
|
||||||
<MenuV2.GroupLabel>{language.t("workspace.move.menu.title")}</MenuV2.GroupLabel>
|
<Menu.GroupLabel>{language.t("workspace.move.menu.title")}</Menu.GroupLabel>
|
||||||
<Show when={pathKey(props.directory) !== pathKey(props.project.worktree)}>
|
<Show when={pathKey(props.directory) !== pathKey(props.project.worktree)}>
|
||||||
<MenuV2.Item disabled={!!store.selected || blocked()} onSelect={() => void move(props.project.worktree)}>
|
<Menu.Item disabled={!!store.selected || blocked()} onSelect={() => void move(props.project.worktree)}>
|
||||||
<Icon name="monitor" />
|
<Icon name="monitor" />
|
||||||
{language.t("session.new.workspace.local")}
|
{language.t("session.new.workspace.local")}
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</Show>
|
</Show>
|
||||||
<MenuV2.Item disabled={!!store.selected || blocked()} onSelect={() => void move("create")}>
|
<Menu.Item disabled={!!store.selected || blocked()} onSelect={() => void move("create")}>
|
||||||
<Icon name="workspace-new" />
|
<Icon name="workspace-new" />
|
||||||
{language.t("workspace.new")}
|
{language.t("workspace.new")}
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
<Show when={workspaces().length > 0}>
|
<Show when={workspaces().length > 0}>
|
||||||
<MenuV2.Sub gutter={0} overlap overflowPadding={8}>
|
<Menu.Sub gutter={0} overlap overflowPadding={8}>
|
||||||
<MenuV2.SubTrigger>
|
<Menu.SubTrigger>
|
||||||
<Icon name="workspace-isolated" />
|
<Icon name="workspace-isolated" />
|
||||||
{language.t("session.new.workspace.existing").replace(/(…|\.{3})$/, "")}
|
{language.t("session.new.workspace.existing").replace(/(…|\.{3})$/, "")}
|
||||||
</MenuV2.SubTrigger>
|
</Menu.SubTrigger>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.SubContent class="max-h-[calc(100dvh-16px)] w-[200px] overflow-y-auto">
|
<Menu.SubContent class="max-h-[calc(100dvh-16px)] w-[200px] overflow-y-auto">
|
||||||
<For each={workspaces()}>
|
<For each={workspaces()}>
|
||||||
{(workspace) => (
|
{(workspace) => (
|
||||||
<MenuV2.Item disabled={!!store.selected || blocked()} onSelect={() => void move(workspace)}>
|
<Menu.Item disabled={!!store.selected || blocked()} onSelect={() => void move(workspace)}>
|
||||||
<Icon name="workspace-isolated" />
|
<Icon name="workspace-isolated" />
|
||||||
<span class="min-w-0 flex-1 truncate">{getFilename(workspace)}</span>
|
<span class="min-w-0 flex-1 truncate">{getFilename(workspace)}</span>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</MenuV2.SubContent>
|
</Menu.SubContent>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2.Sub>
|
</Menu.Sub>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Group>
|
</Menu.Group>
|
||||||
<MenuV2.Separator class="h-[0.5px] bg-v2-border-border-base" />
|
<Menu.Separator class="h-[0.5px] bg-v2-border-border-base" />
|
||||||
<MenuV2.Item onSelect={() => openWorkspaces()}>
|
<Menu.Item onSelect={() => openWorkspaces()}>
|
||||||
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
<span class="min-w-0 flex-1 truncate">{language.t("common.viewAll")}</span>
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</MenuV2.Content>
|
</Menu.Content>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import { For, Show } from "solid-js"
|
|||||||
import { AppIcon } from "@opencode-ai/ui/app-icon"
|
import { AppIcon } from "@opencode-ai/ui/app-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { SplitButton, SplitButtonAction, SplitButtonMenuTrigger } from "@opencode-ai/ui/split-button"
|
||||||
import { SplitButtonV2, SplitButtonV2Action, SplitButtonV2MenuTrigger } from "@opencode-ai/ui/v2/split-button-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { type OpenApp, useOpenInApp } from "@/components/session/open-in-app"
|
import { type OpenApp, useOpenInApp } from "@/components/session/open-in-app"
|
||||||
|
|
||||||
@@ -15,13 +14,13 @@ export function OpenInAppV2(props: { directory: () => string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={props.directory() && state.canOpen()}>
|
<Show when={props.directory() && state.canOpen()}>
|
||||||
<SplitButtonV2 class="session-review-v2-open-in-app" onPointerDown={(event) => event.stopPropagation()}>
|
<SplitButton class="session-review-v2-open-in-app" onPointerDown={(event) => event.stopPropagation()}>
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
value={language.t("session.header.open.ariaLabel", { app: state.current().label })}
|
value={language.t("session.header.open.ariaLabel", { app: state.current().label })}
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<SplitButtonV2Action
|
<SplitButtonAction
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@@ -34,28 +33,28 @@ export function OpenInAppV2(props: { directory: () => string }) {
|
|||||||
<Show when={state.opening()} fallback={<AppIcon id={state.current().icon} class="size-[18px]" />}>
|
<Show when={state.opening()} fallback={<AppIcon id={state.current().icon} class="size-[18px]" />}>
|
||||||
<Spinner class="size-3.5" />
|
<Spinner class="size-3.5" />
|
||||||
</Show>
|
</Show>
|
||||||
</SplitButtonV2Action>
|
</SplitButtonAction>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
<MenuV2
|
<Menu
|
||||||
gutter={4}
|
gutter={4}
|
||||||
modal={false}
|
modal={false}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
open={state.menu.open}
|
open={state.menu.open}
|
||||||
onOpenChange={(open) => state.setMenu("open", open)}
|
onOpenChange={(open) => state.setMenu("open", open)}
|
||||||
>
|
>
|
||||||
<MenuV2.Trigger
|
<Menu.Trigger
|
||||||
as={SplitButtonV2MenuTrigger}
|
as={SplitButtonMenuTrigger}
|
||||||
disabled={state.opening()}
|
disabled={state.opening()}
|
||||||
aria-label={language.t("session.header.open.menu")}
|
aria-label={language.t("session.header.open.menu")}
|
||||||
onPointerDown={(event) => event.stopPropagation()}
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
<IconV2 name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</MenuV2.Trigger>
|
</Menu.Trigger>
|
||||||
<MenuV2.Portal>
|
<Menu.Portal>
|
||||||
<MenuV2.Content class="open-in-app-v2-menu">
|
<Menu.Content class="open-in-app-v2-menu">
|
||||||
<MenuV2.Group>
|
<Menu.Group>
|
||||||
<MenuV2.GroupLabel>{language.t("session.header.openIn")}</MenuV2.GroupLabel>
|
<Menu.GroupLabel>{language.t("session.header.openIn")}</Menu.GroupLabel>
|
||||||
<MenuV2.RadioGroup
|
<Menu.RadioGroup
|
||||||
value={state.current().id}
|
value={state.current().id}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
state.selectApp(value as OpenApp)
|
state.selectApp(value as OpenApp)
|
||||||
@@ -63,7 +62,7 @@ export function OpenInAppV2(props: { directory: () => string }) {
|
|||||||
>
|
>
|
||||||
<For each={state.options()}>
|
<For each={state.options()}>
|
||||||
{(option) => (
|
{(option) => (
|
||||||
<MenuV2.RadioItem
|
<Menu.RadioItem
|
||||||
value={option.id}
|
value={option.id}
|
||||||
disabled={state.opening()}
|
disabled={state.opening()}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@@ -74,13 +73,13 @@ export function OpenInAppV2(props: { directory: () => string }) {
|
|||||||
>
|
>
|
||||||
<AppIcon id={option.icon} />
|
<AppIcon id={option.icon} />
|
||||||
{option.label}
|
{option.label}
|
||||||
</MenuV2.RadioItem>
|
</Menu.RadioItem>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</MenuV2.RadioGroup>
|
</Menu.RadioGroup>
|
||||||
</MenuV2.Group>
|
</Menu.Group>
|
||||||
<MenuV2.Separator />
|
<Menu.Separator />
|
||||||
<MenuV2.Item
|
<Menu.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
state.setMenu("open", false)
|
state.setMenu("open", false)
|
||||||
state.copyPath()
|
state.copyPath()
|
||||||
@@ -88,11 +87,11 @@ export function OpenInAppV2(props: { directory: () => string }) {
|
|||||||
>
|
>
|
||||||
<Icon name="copy" size="small" class="text-icon-weak" />
|
<Icon name="copy" size="small" class="text-icon-weak" />
|
||||||
{language.t("session.header.open.copyPath")}
|
{language.t("session.header.open.copyPath")}
|
||||||
</MenuV2.Item>
|
</Menu.Item>
|
||||||
</MenuV2.Content>
|
</Menu.Content>
|
||||||
</MenuV2.Portal>
|
</Menu.Portal>
|
||||||
</MenuV2>
|
</Menu>
|
||||||
</SplitButtonV2>
|
</SplitButton>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import type { Message, Part } from "@/types"
|
|
||||||
import { estimateSessionContextBreakdown } from "./session-context-breakdown"
|
|
||||||
|
|
||||||
const user = (id: string) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
role: "user",
|
|
||||||
time: { created: 1 },
|
|
||||||
} as unknown as Message
|
|
||||||
}
|
|
||||||
|
|
||||||
const assistant = (id: string) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
role: "assistant",
|
|
||||||
time: { created: 1 },
|
|
||||||
} as unknown as Message
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("estimateSessionContextBreakdown", () => {
|
|
||||||
test("estimates tokens and keeps remaining tokens as other", () => {
|
|
||||||
const messages = [user("u1"), assistant("a1")]
|
|
||||||
const parts = {
|
|
||||||
u1: [{ type: "text", text: "hello world" }] as unknown as Part[],
|
|
||||||
a1: [{ type: "text", text: "assistant response" }] as unknown as Part[],
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = estimateSessionContextBreakdown({
|
|
||||||
messages,
|
|
||||||
parts,
|
|
||||||
input: 20,
|
|
||||||
systemPrompt: "system prompt",
|
|
||||||
})
|
|
||||||
|
|
||||||
const map = Object.fromEntries(output.map((segment) => [segment.key, segment.tokens]))
|
|
||||||
expect(map.system).toBe(4)
|
|
||||||
expect(map.user).toBe(3)
|
|
||||||
expect(map.assistant).toBe(5)
|
|
||||||
expect(map.other).toBe(8)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("scales segments when estimates exceed input", () => {
|
|
||||||
const messages = [user("u1"), assistant("a1")]
|
|
||||||
const parts = {
|
|
||||||
u1: [{ type: "text", text: "x".repeat(400) }] as unknown as Part[],
|
|
||||||
a1: [{ type: "text", text: "y".repeat(400) }] as unknown as Part[],
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = estimateSessionContextBreakdown({
|
|
||||||
messages,
|
|
||||||
parts,
|
|
||||||
input: 10,
|
|
||||||
systemPrompt: "z".repeat(200),
|
|
||||||
})
|
|
||||||
|
|
||||||
const total = output.reduce((sum, segment) => sum + segment.tokens, 0)
|
|
||||||
expect(total).toBeLessThanOrEqual(10)
|
|
||||||
expect(output.every((segment) => segment.width <= 100)).toBeTrue()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
import type { Message, Part } from "@/types"
|
|
||||||
|
|
||||||
export type SessionContextBreakdownKey = "system" | "user" | "assistant" | "tool" | "other"
|
|
||||||
|
|
||||||
export type SessionContextBreakdownSegment = {
|
|
||||||
key: SessionContextBreakdownKey
|
|
||||||
tokens: number
|
|
||||||
width: number
|
|
||||||
percent: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const estimateTokens = (chars: number) => Math.ceil(chars / 4)
|
|
||||||
const toPercent = (tokens: number, input: number) => (tokens / input) * 100
|
|
||||||
const toPercentLabel = (tokens: number, input: number) => Math.round(toPercent(tokens, input) * 10) / 10
|
|
||||||
|
|
||||||
const charsFromUserPart = (part: Part) => {
|
|
||||||
if (part.type === "text") return part.text.length
|
|
||||||
if (part.type === "file") return part.source?.text.value.length ?? 0
|
|
||||||
if (part.type === "agent") return part.source?.value.length ?? 0
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const charsFromAssistantPart = (part: Part) => {
|
|
||||||
if (part.type === "text") return { assistant: part.text.length, tool: 0 }
|
|
||||||
if (part.type === "reasoning") return { assistant: part.text.length, tool: 0 }
|
|
||||||
if (part.type !== "tool") return { assistant: 0, tool: 0 }
|
|
||||||
|
|
||||||
const input = Object.keys(part.state.input).length * 16
|
|
||||||
if (part.state.status === "pending") return { assistant: 0, tool: input + part.state.raw.length }
|
|
||||||
if (part.state.status === "completed") return { assistant: 0, tool: input + part.state.output.length }
|
|
||||||
if (part.state.status === "error") return { assistant: 0, tool: input + part.state.error.length }
|
|
||||||
return { assistant: 0, tool: input }
|
|
||||||
}
|
|
||||||
|
|
||||||
const build = (
|
|
||||||
tokens: { system: number; user: number; assistant: number; tool: number; other: number },
|
|
||||||
input: number,
|
|
||||||
) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: "system",
|
|
||||||
tokens: tokens.system,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "user",
|
|
||||||
tokens: tokens.user,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "assistant",
|
|
||||||
tokens: tokens.assistant,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "tool",
|
|
||||||
tokens: tokens.tool,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "other",
|
|
||||||
tokens: tokens.other,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.filter((x) => x.tokens > 0)
|
|
||||||
.map((x) => ({
|
|
||||||
key: x.key,
|
|
||||||
tokens: x.tokens,
|
|
||||||
width: toPercent(x.tokens, input),
|
|
||||||
percent: toPercentLabel(x.tokens, input),
|
|
||||||
})) as SessionContextBreakdownSegment[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function estimateSessionContextBreakdown(args: {
|
|
||||||
messages: Message[]
|
|
||||||
parts: Record<string, Part[] | undefined>
|
|
||||||
input: number
|
|
||||||
systemPrompt?: string
|
|
||||||
}) {
|
|
||||||
if (!args.input) return []
|
|
||||||
|
|
||||||
const counts = args.messages.reduce(
|
|
||||||
(acc, msg) => {
|
|
||||||
const parts = args.parts[msg.id] ?? []
|
|
||||||
if (msg.role === "user") {
|
|
||||||
const user = parts.reduce((sum, part) => sum + charsFromUserPart(part), 0)
|
|
||||||
return { ...acc, user: acc.user + user }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.role !== "assistant") return acc
|
|
||||||
const assistant = parts.reduce(
|
|
||||||
(sum, part) => {
|
|
||||||
const next = charsFromAssistantPart(part)
|
|
||||||
return {
|
|
||||||
assistant: sum.assistant + next.assistant,
|
|
||||||
tool: sum.tool + next.tool,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ assistant: 0, tool: 0 },
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
assistant: acc.assistant + assistant.assistant,
|
|
||||||
tool: acc.tool + assistant.tool,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
system: args.systemPrompt?.length ?? 0,
|
|
||||||
user: 0,
|
|
||||||
assistant: 0,
|
|
||||||
tool: 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const tokens = {
|
|
||||||
system: estimateTokens(counts.system),
|
|
||||||
user: estimateTokens(counts.user),
|
|
||||||
assistant: estimateTokens(counts.assistant),
|
|
||||||
tool: estimateTokens(counts.tool),
|
|
||||||
}
|
|
||||||
const estimated = tokens.system + tokens.user + tokens.assistant + tokens.tool
|
|
||||||
|
|
||||||
if (estimated <= args.input) {
|
|
||||||
return build({ ...tokens, other: args.input - estimated }, args.input)
|
|
||||||
}
|
|
||||||
|
|
||||||
const scale = args.input / estimated
|
|
||||||
const scaled = {
|
|
||||||
system: Math.floor(tokens.system * scale),
|
|
||||||
user: Math.floor(tokens.user * scale),
|
|
||||||
assistant: Math.floor(tokens.assistant * scale),
|
|
||||||
tool: Math.floor(tokens.tool * scale),
|
|
||||||
}
|
|
||||||
const total = scaled.system + scaled.user + scaled.assistant + scaled.tool
|
|
||||||
return build({ ...scaled, other: Math.max(0, args.input - total) }, args.input)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import type { Message } from "@/types"
|
|
||||||
import { getSessionContext } from "./session-context-metrics"
|
|
||||||
|
|
||||||
const assistant = (
|
|
||||||
id: string,
|
|
||||||
tokens: { input: number; output: number; reasoning: number; read: number; write: number },
|
|
||||||
cost: number,
|
|
||||||
providerID = "openai",
|
|
||||||
modelID = "gpt-4.1",
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
role: "assistant",
|
|
||||||
providerID,
|
|
||||||
modelID,
|
|
||||||
cost,
|
|
||||||
tokens: {
|
|
||||||
input: tokens.input,
|
|
||||||
output: tokens.output,
|
|
||||||
reasoning: tokens.reasoning,
|
|
||||||
cache: {
|
|
||||||
read: tokens.read,
|
|
||||||
write: tokens.write,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
time: { created: 1 },
|
|
||||||
} as unknown as Message
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = (id: string) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
role: "user",
|
|
||||||
cost: 0,
|
|
||||||
time: { created: 1 },
|
|
||||||
} as unknown as Message
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("getSessionContext", () => {
|
|
||||||
test("computes token totals and usage from latest assistant with tokens", () => {
|
|
||||||
const messages = [
|
|
||||||
user("u1"),
|
|
||||||
assistant("a1", { input: 600, output: 200, reasoning: 100, read: 50, write: 50 }, 0.5),
|
|
||||||
assistant("a2", { input: 300, output: 100, reasoning: 50, read: 25, write: 25 }, 1.25),
|
|
||||||
]
|
|
||||||
const providers = [
|
|
||||||
{
|
|
||||||
id: "openai",
|
|
||||||
name: "OpenAI",
|
|
||||||
models: {
|
|
||||||
"gpt-4.1": {
|
|
||||||
name: "GPT-4.1",
|
|
||||||
limit: { context: 1000 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const ctx = getSessionContext(messages, providers)
|
|
||||||
|
|
||||||
expect(ctx?.message.id).toBe("a2")
|
|
||||||
expect(ctx?.total).toBe(500)
|
|
||||||
expect(ctx?.input).toBe(300)
|
|
||||||
expect(ctx?.usage).toBe(50)
|
|
||||||
expect(ctx?.providerLabel).toBe("OpenAI")
|
|
||||||
expect(ctx?.modelLabel).toBe("GPT-4.1")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("preserves fallback labels and null usage when model metadata is missing", () => {
|
|
||||||
const messages = [assistant("a1", { input: 40, output: 10, reasoning: 0, read: 0, write: 0 }, 0.1, "p-1", "m-1")]
|
|
||||||
const providers = [{ id: "p-1", models: {} }]
|
|
||||||
|
|
||||||
const ctx = getSessionContext(messages, providers)
|
|
||||||
|
|
||||||
expect(ctx?.providerLabel).toBe("p-1")
|
|
||||||
expect(ctx?.modelLabel).toBe("m-1")
|
|
||||||
expect(ctx?.limit).toBeUndefined()
|
|
||||||
expect(ctx?.usage).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("recomputes when message array is mutated in place", () => {
|
|
||||||
const messages = [assistant("a1", { input: 10, output: 10, reasoning: 10, read: 10, write: 10 }, 0.25)]
|
|
||||||
const providers = [{ id: "openai", models: {} }]
|
|
||||||
|
|
||||||
const one = getSessionContext(messages, providers)
|
|
||||||
messages.push(assistant("a2", { input: 100, output: 20, reasoning: 0, read: 0, write: 0 }, 0.75))
|
|
||||||
const two = getSessionContext(messages, providers)
|
|
||||||
|
|
||||||
expect(one?.message.id).toBe("a1")
|
|
||||||
expect(two?.message.id).toBe("a2")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns undefined when inputs are undefined", () => {
|
|
||||||
const ctx = getSessionContext(undefined, undefined)
|
|
||||||
|
|
||||||
expect(ctx).toBeUndefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import type { AssistantMessage, Message } from "@/types"
|
|
||||||
|
|
||||||
type Provider = {
|
|
||||||
id: string
|
|
||||||
name?: string
|
|
||||||
models: Record<string, Model | undefined>
|
|
||||||
}
|
|
||||||
|
|
||||||
type Model = {
|
|
||||||
name?: string
|
|
||||||
limit: {
|
|
||||||
context: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Context = {
|
|
||||||
message: AssistantMessage
|
|
||||||
provider?: Provider
|
|
||||||
model?: Model
|
|
||||||
providerLabel: string
|
|
||||||
modelLabel: string
|
|
||||||
limit: number | undefined
|
|
||||||
input: number
|
|
||||||
total: number
|
|
||||||
usage: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokenTotal = (msg: AssistantMessage) => {
|
|
||||||
return msg.tokens.input + msg.tokens.output + msg.tokens.reasoning + msg.tokens.cache.read + msg.tokens.cache.write
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastAssistantWithTokens = (messages: Message[]) => {
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
|
||||||
const msg = messages[i]
|
|
||||||
if (msg.role !== "assistant") continue
|
|
||||||
if (tokenTotal(msg) <= 0) continue
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const build = (messages: Message[] = [], providers: Provider[] = []): Context | undefined => {
|
|
||||||
const message = lastAssistantWithTokens(messages)
|
|
||||||
if (!message) return undefined
|
|
||||||
|
|
||||||
const provider = providers.find((item) => item.id === message.providerID)
|
|
||||||
const model = provider?.models[message.modelID]
|
|
||||||
const limit = model?.limit.context
|
|
||||||
const total = tokenTotal(message)
|
|
||||||
|
|
||||||
return {
|
|
||||||
message,
|
|
||||||
provider,
|
|
||||||
model,
|
|
||||||
providerLabel: provider?.name ?? message.providerID,
|
|
||||||
modelLabel: model?.name ?? message.modelID,
|
|
||||||
limit,
|
|
||||||
input: message.tokens.input,
|
|
||||||
total,
|
|
||||||
usage: limit ? Math.round((total / limit) * 100) : null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSessionContext(messages: Message[] = [], providers: Provider[] = []) {
|
|
||||||
return build(messages, providers)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
|
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
import { checksum } from "@opencode-ai/core/util/encode"
|
import { checksum } from "@opencode-ai/util/encode"
|
||||||
import { same } from "@/utils/same"
|
import { same } from "@/utils/same"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -181,8 +181,7 @@ export function SessionContextTab() {
|
|||||||
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.tokens.reasoning) },
|
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.tokens.reasoning) },
|
||||||
{
|
{
|
||||||
label: "context.stats.cacheTokens",
|
label: "context.stats.cacheTokens",
|
||||||
value: () =>
|
value: () => `${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
|
||||||
`${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
|
|
||||||
},
|
},
|
||||||
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
||||||
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
||||||
@@ -305,9 +304,7 @@ export function SessionContextTab() {
|
|||||||
</div>
|
</div>
|
||||||
<Accordion multiple>
|
<Accordion multiple>
|
||||||
<For each={messages()}>
|
<For each={messages()}>
|
||||||
{(message) => (
|
{(message) => <RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />}
|
||||||
<RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { createMemo, Show } from "solid-js"
|
import { createMemo, Show } from "solid-js"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { Portal } from "solid-js/web"
|
import { Portal } from "solid-js/web"
|
||||||
@@ -7,10 +6,10 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { StatusPopoverV2 } from "../status-popover"
|
import { StatusPopoverV2 } from "../status-popover"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { reviewTooltipKeybind } from "../command-tooltip-keybind"
|
import { reviewTooltipKeybind } from "../command-tooltip-keybind"
|
||||||
import { useTitlebarRightMount } from "../titlebar"
|
import { useTitlebarRightMount } from "../titlebar"
|
||||||
|
|
||||||
@@ -60,24 +59,24 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
|
|||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Show when={props.state.statusVisible}>
|
<Show when={props.state.statusVisible}>
|
||||||
<Tooltip placement="bottom" value={props.state.statusLabel}>
|
<Tooltip appearance="standard" placement="bottom" value={props.state.statusLabel}>
|
||||||
<StatusPopoverV2 />
|
<StatusPopoverV2 />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.state.reviewVisible}>
|
<Show when={props.state.reviewVisible}>
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
value={
|
value={
|
||||||
<>
|
<>
|
||||||
{props.state.reviewLabel}
|
{props.state.reviewLabel}
|
||||||
<Show when={props.state.reviewKeybind.length > 0}>
|
<Show when={props.state.reviewKeybind.length > 0}>
|
||||||
<KeybindV2 keys={props.state.reviewKeybind} variant="neutral" />
|
<Keybind keys={props.state.reviewKeybind} variant="neutral" />
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="large"
|
size="large"
|
||||||
@@ -87,9 +86,9 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
|
|||||||
aria-label={props.state.reviewLabel}
|
aria-label={props.state.reviewLabel}
|
||||||
aria-expanded={props.state.reviewOpened}
|
aria-expanded={props.state.reviewOpened}
|
||||||
aria-controls="review-panel"
|
aria-controls="review-panel"
|
||||||
icon={<IconV2 name="sidebar-right" />}
|
icon={<Icon name="sidebar-right" />}
|
||||||
/>
|
/>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Mark } from "@opencode-ai/ui/logo"
|
import { Mark } from "@opencode-ai/ui/logo"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
|
|
||||||
const MAIN_WORKTREE = "main"
|
const MAIN_WORKTREE = "main"
|
||||||
const CREATE_WORKTREE = "create"
|
const CREATE_WORKTREE = "create"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createMemo, Show } from "solid-js"
|
import { createMemo, Show } from "solid-js"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { useSortable } from "@dnd-kit/solid/sortable"
|
import { useSortable } from "@dnd-kit/solid/sortable"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -40,31 +39,29 @@ export function SortableTabV2(props: {
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value={props.tab}
|
value={props.tab}
|
||||||
|
onMiddleClick={() => props.onTabClose(props.tab)}
|
||||||
|
onDblClick={() => props.onTabDoubleClick?.(props.tab)}
|
||||||
closeButton={
|
closeButton={
|
||||||
<TooltipV2
|
<Tooltip
|
||||||
value={
|
value={
|
||||||
<>
|
<>
|
||||||
{language.t("common.closeTab")}
|
{language.t("common.closeTab")}
|
||||||
<Show when={closeTabKeybind().length > 0}>
|
<Show when={closeTabKeybind().length > 0}>
|
||||||
<KeybindV2 keys={closeTabKeybind()} variant="neutral" />
|
<Keybind keys={closeTabKeybind()} variant="neutral" />
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
gutter={10}
|
gutter={10}
|
||||||
>
|
>
|
||||||
<IconButton
|
<Tabs.CloseButton
|
||||||
icon="close-small"
|
|
||||||
variant="ghost"
|
|
||||||
class="h-5 w-5"
|
class="h-5 w-5"
|
||||||
onClick={() => props.onTabClose(props.tab)}
|
onClick={() => props.onTabClose(props.tab)}
|
||||||
aria-label={language.t("common.closeTab")}
|
aria-label={language.t("common.closeTab")}
|
||||||
/>
|
/>
|
||||||
</TooltipV2>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
hideCloseButton
|
hideCloseButton
|
||||||
onMiddleClick={() => props.onTabClose(props.tab)}
|
|
||||||
onDblClick={() => props.onTabDoubleClick?.(props.tab)}
|
|
||||||
>
|
>
|
||||||
<Show when={content()}>{(value) => value()}</Show>
|
<Show when={content()}>{(value) => value()}</Show>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Show } from "solid-js"
|
import { Show } from "solid-js"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
|
||||||
export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
|
export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import type { JSX } from "solid-js"
|
|||||||
import { Show, createEffect, onCleanup } from "solid-js"
|
import { Show, createEffect, onCleanup } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useSortable } from "@dnd-kit/solid/sortable"
|
import { useSortable } from "@dnd-kit/solid/sortable"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { Menu } from "@opencode-ai/ui/menu"
|
||||||
import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
|
import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
|
||||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -116,8 +115,8 @@ export function SortableTerminalTabV2(props: { terminal: LocalPTY; index: number
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={sortable.ref} class="h-full flex items-center outline-none focus:outline-none focus-visible:outline-none">
|
<div ref={sortable.ref} class="h-full flex items-center outline-none focus:outline-none focus-visible:outline-none">
|
||||||
<MenuV2.Context>
|
<Menu.Context>
|
||||||
<MenuV2.Context.Trigger class="relative" as="div">
|
<Menu.Context.Trigger class="relative" as="div">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value={props.terminal.id}
|
value={props.terminal.id}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
@@ -131,20 +130,11 @@ export function SortableTerminalTabV2(props: { terminal: LocalPTY; index: number
|
|||||||
if (e.detail > 0) return
|
if (e.detail > 0) return
|
||||||
focus()
|
focus()
|
||||||
}}
|
}}
|
||||||
|
onMiddleClick={close}
|
||||||
closeButton={
|
closeButton={
|
||||||
<IconButton
|
<Tabs.CloseButton class="h-5 w-5" onClick={close} aria-label={language.t("terminal.close")} />
|
||||||
icon="close-small"
|
|
||||||
variant="ghost"
|
|
||||||
class="h-5 w-5"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
close()
|
|
||||||
}}
|
|
||||||
aria-label={language.t("terminal.close")}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
hideCloseButton
|
hideCloseButton
|
||||||
onMiddleClick={close}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="truncate"
|
class="truncate"
|
||||||
@@ -169,9 +159,9 @@ export function SortableTerminalTabV2(props: { terminal: LocalPTY; index: number
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.Context.Trigger>
|
</Menu.Context.Trigger>
|
||||||
<MenuV2.Context.Portal>
|
<Menu.Context.Portal>
|
||||||
<MenuV2.Context.Content
|
<Menu.Context.Content
|
||||||
onCloseAutoFocus={(e) => {
|
onCloseAutoFocus={(e) => {
|
||||||
if (!editRequested) return
|
if (!editRequested) return
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -179,11 +169,11 @@ export function SortableTerminalTabV2(props: { terminal: LocalPTY; index: number
|
|||||||
requestAnimationFrame(() => edit())
|
requestAnimationFrame(() => edit())
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuV2.Item onSelect={() => (editRequested = true)}>{language.t("common.rename")}</MenuV2.Item>
|
<Menu.Item onSelect={() => (editRequested = true)}>{language.t("common.rename")}</Menu.Item>
|
||||||
<MenuV2.Item onSelect={close}>{language.t("common.close")}</MenuV2.Item>
|
<Menu.Item onSelect={close}>{language.t("common.close")}</Menu.Item>
|
||||||
</MenuV2.Context.Content>
|
</Menu.Context.Content>
|
||||||
</MenuV2.Context.Portal>
|
</Menu.Context.Portal>
|
||||||
</MenuV2.Context>
|
</Menu.Context>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { For, Show, createMemo, lazy, onCleanup } from "solid-js"
|
import { For, Show, createMemo, lazy, onCleanup } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { DEFAULT_PALETTE_KEYBIND, formatKeybind, parseKeybind, useCommand } from "@/context/command"
|
import { DEFAULT_PALETTE_KEYBIND, formatKeybind, parseKeybind, useCommand } from "@/context/command"
|
||||||
@@ -11,7 +11,7 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { SettingsListV2 } from "./settings-v2/parts/list"
|
import { SettingsListV2 } from "./settings-v2/parts/list"
|
||||||
|
|
||||||
const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({ default: module.Icon })))
|
const Icon = lazy(() => import("@opencode-ai/ui/icon").then((module) => ({ default: module.Icon })))
|
||||||
|
|
||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
const PALETTE_ID = "command.palette"
|
const PALETTE_ID = "command.palette"
|
||||||
@@ -387,12 +387,12 @@ function SettingsKeybindsV2View(props: {
|
|||||||
<h2 class="settings-v2-tab-title">{language.t("settings.shortcuts.title")}</h2>
|
<h2 class="settings-v2-tab-title">{language.t("settings.shortcuts.title")}</h2>
|
||||||
<span class="text-11-regular text-v2-text-text-muted">{language.t("settings.shortcuts.description")}</span>
|
<span class="text-11-regular text-v2-text-text-muted">{language.t("settings.shortcuts.description")}</span>
|
||||||
</div>
|
</div>
|
||||||
<ButtonV2 variant="ghost" onClick={props.onReset} disabled={!props.hasOverrides}>
|
<Button variant="ghost" onClick={props.onReset} disabled={!props.hasOverrides}>
|
||||||
{language.t("settings.shortcuts.reset.button")}
|
{language.t("settings.shortcuts.reset.button")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-v2-tab-search">
|
<div class="settings-v2-tab-search">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
value={store.filter}
|
value={store.filter}
|
||||||
@@ -405,12 +405,12 @@ function SettingsKeybindsV2View(props: {
|
|||||||
aria-label={language.t("settings.shortcuts.search.placeholder")}
|
aria-label={language.t("settings.shortcuts.search.placeholder")}
|
||||||
/>
|
/>
|
||||||
<Show when={store.filter}>
|
<Show when={store.filter}>
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
class="settings-v2-tab-search-clear"
|
class="settings-v2-tab-search-clear"
|
||||||
icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
|
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||||
onClick={() => setStore("filter", "")}
|
onClick={() => setStore("filter", "")}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, createMemo } from "solid-js"
|
import { Component, createMemo } from "solid-js"
|
||||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { ExternalLink } from "../external-link"
|
import { ExternalLink } from "../external-link"
|
||||||
import { SettingsListV2 } from "./parts/list"
|
import { SettingsListV2 } from "./parts/list"
|
||||||
@@ -42,7 +42,7 @@ const FontSetting: Component<{
|
|||||||
return (
|
return (
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
||||||
<div class="w-full sm:w-[220px]">
|
<div class="w-full sm:w-[220px]">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
data-action={config().action}
|
data-action={config().action}
|
||||||
type="text"
|
type="text"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
@@ -83,8 +83,7 @@ export const SettingsAppearanceV2: Component = () => {
|
|||||||
title={language.t("settings.general.row.colorScheme.title")}
|
title={language.t("settings.general.row.colorScheme.title")}
|
||||||
description={language.t("settings.general.row.colorScheme.description")}
|
description={language.t("settings.general.row.colorScheme.description")}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-color-scheme"
|
data-action="settings-color-scheme"
|
||||||
options={schemeOptions}
|
options={schemeOptions}
|
||||||
current={schemeOptions.find((option) => option === appearance.scheme.current())}
|
current={schemeOptions.find((option) => option === appearance.scheme.current())}
|
||||||
@@ -110,8 +109,7 @@ export const SettingsAppearanceV2: Component = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-theme"
|
data-action="settings-theme"
|
||||||
options={appearance.theme.options()}
|
options={appearance.theme.options()}
|
||||||
current={appearance.theme.current()}
|
current={appearance.theme.current()}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/dialog"
|
||||||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
import { Divider } from "@opencode-ai/ui/divider"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
import { useMutation } from "@tanstack/solid-query"
|
||||||
import { type Component, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
import { type Component, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
||||||
@@ -69,12 +69,12 @@ export const DialogServerV2: Component<{
|
|||||||
<DialogHeader hideClose={true}>
|
<DialogHeader hideClose={true}>
|
||||||
<DialogTitle>{title()}</DialogTitle>
|
<DialogTitle>{title()}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DividerV2 />
|
<Divider />
|
||||||
<DialogBody class="flex w-full min-w-0 flex-1 flex-col px-4 pt-4 pb-2">
|
<DialogBody class="flex w-full min-w-0 flex-1 flex-col px-4 pt-4 pb-2">
|
||||||
<div class="flex w-full min-w-0 flex-col gap-6">
|
<div class="flex w-full min-w-0 flex-col gap-6">
|
||||||
<div class="flex w-full min-w-0 flex-col gap-2">
|
<div class="flex w-full min-w-0 flex-col gap-2">
|
||||||
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.url")}</label>
|
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.url")}</label>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
appearance="large"
|
appearance="large"
|
||||||
class="!w-full self-stretch"
|
class="!w-full self-stretch"
|
||||||
@@ -92,7 +92,7 @@ export const DialogServerV2: Component<{
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex w-full min-w-0 flex-col gap-2">
|
<div class="flex w-full min-w-0 flex-col gap-2">
|
||||||
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.name")}</label>
|
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.name")}</label>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
appearance="large"
|
appearance="large"
|
||||||
class="!w-full self-stretch"
|
class="!w-full self-stretch"
|
||||||
@@ -106,7 +106,7 @@ export const DialogServerV2: Component<{
|
|||||||
<div class="grid w-full min-w-0 grid-cols-2 gap-4">
|
<div class="grid w-full min-w-0 grid-cols-2 gap-4">
|
||||||
<div class="flex min-w-0 flex-col gap-2">
|
<div class="flex min-w-0 flex-col gap-2">
|
||||||
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.username")}</label>
|
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.username")}</label>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
appearance="large"
|
appearance="large"
|
||||||
class="!w-full self-stretch"
|
class="!w-full self-stretch"
|
||||||
@@ -119,7 +119,7 @@ export const DialogServerV2: Component<{
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col gap-2">
|
<div class="flex min-w-0 flex-col gap-2">
|
||||||
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.password")}</label>
|
<label class="settings-v2-server-dialog-label">{language.t("dialog.server.add.password")}</label>
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="password"
|
type="password"
|
||||||
appearance="large"
|
appearance="large"
|
||||||
class="!w-full self-stretch"
|
class="!w-full self-stretch"
|
||||||
@@ -134,12 +134,12 @@ export const DialogServerV2: Component<{
|
|||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<ButtonV2 variant="neutral" disabled={form.state.busy()} onClick={() => dialog.close()}>
|
<Button variant="neutral" disabled={form.state.busy()} onClick={() => dialog.close()}>
|
||||||
{language.t("common.cancel")}
|
{language.t("common.cancel")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
<ButtonV2 variant="contrast" disabled={form.state.busy()} onClick={form.submit}>
|
<Button variant="contrast" disabled={form.state.busy()} onClick={form.submit}>
|
||||||
{submitLabel()}
|
{submitLabel()}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, createEffect, createMemo, createSignal, startTransition } from "solid-js"
|
import { Component, createEffect, createMemo, createSignal, startTransition } from "solid-js"
|
||||||
import { Dialog } from "@opencode-ai/ui/v2/dialog-v2"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
@@ -73,66 +73,66 @@ export const DialogSettings: Component<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog size="x-large" variant="settings" class="settings-v2-dialog">
|
<Dialog size="x-large" variant="settings" class="settings-v2-dialog">
|
||||||
<TabsV2
|
<Tabs
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
variant="settings"
|
variant="settings"
|
||||||
value={tab()}
|
value={tab()}
|
||||||
onChange={(value) => void startTransition(() => setTab(value))}
|
onChange={(value) => void startTransition(() => setTab(value))}
|
||||||
class="settings-v2"
|
class="settings-v2"
|
||||||
>
|
>
|
||||||
<TabsV2.List>
|
<Tabs.List>
|
||||||
<div class="flex flex-col justify-between h-full w-full">
|
<div class="flex flex-col justify-between h-full w-full">
|
||||||
<div class="flex flex-col gap-4 w-full">
|
<div class="flex flex-col gap-4 w-full">
|
||||||
{/* Group 1: Preferences */}
|
{/* Group 1: Preferences */}
|
||||||
<div class="flex flex-col gap-1 w-full">
|
<div class="flex flex-col gap-1 w-full">
|
||||||
<TabsV2.Trigger value="general">
|
<Tabs.Trigger value="general">
|
||||||
<Icon name="sliders" />
|
<Icon name="sliders" />
|
||||||
{language.t("settings.tab.preferences")}
|
{language.t("settings.tab.preferences")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="appearance">
|
<Tabs.Trigger value="appearance">
|
||||||
<Icon name="appearance" />
|
<Icon name="appearance" />
|
||||||
{language.t("settings.general.section.appearance")}
|
{language.t("settings.general.section.appearance")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="notifications">
|
<Tabs.Trigger value="notifications">
|
||||||
<Icon name="notifications" />
|
<Icon name="notifications" />
|
||||||
{language.t("settings.tab.notifications")}
|
{language.t("settings.tab.notifications")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="shortcuts">
|
<Tabs.Trigger value="shortcuts">
|
||||||
<Icon name="keyboard" />
|
<Icon name="keyboard" />
|
||||||
{language.t("settings.tab.shortcuts")}
|
{language.t("settings.tab.shortcuts")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Group 2: Environment & Workspaces */}
|
{/* Group 2: Environment & Workspaces */}
|
||||||
<div class="flex flex-col gap-1 w-full">
|
<div class="flex flex-col gap-1 w-full">
|
||||||
<TabsV2.Trigger value="servers">
|
<Tabs.Trigger value="servers">
|
||||||
<Icon name="server" />
|
<Icon name="server" />
|
||||||
{language.t("status.popover.tab.servers")}
|
{language.t("status.popover.tab.servers")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="projects">
|
<Tabs.Trigger value="projects">
|
||||||
<Icon name="folder" />
|
<Icon name="folder" />
|
||||||
{language.t("settings.tab.projects")}
|
{language.t("settings.tab.projects")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="workspaces">
|
<Tabs.Trigger value="workspaces">
|
||||||
<Icon name="workspace-isolated" />
|
<Icon name="workspace-isolated" />
|
||||||
{language.t("settings.tab.workspaces")}
|
{language.t("settings.tab.workspaces")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Group 3: Capabilities & Extensions */}
|
{/* Group 3: Capabilities & Extensions */}
|
||||||
<div class="flex flex-col gap-1 w-full">
|
<div class="flex flex-col gap-1 w-full">
|
||||||
<TabsV2.Trigger value="providers">
|
<Tabs.Trigger value="providers">
|
||||||
<Icon name="providers" />
|
<Icon name="providers" />
|
||||||
{language.t("settings.providers.title")}
|
{language.t("settings.providers.title")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="models">
|
<Tabs.Trigger value="models">
|
||||||
<Icon name="models" />
|
<Icon name="models" />
|
||||||
{language.t("settings.models.title")}
|
{language.t("settings.models.title")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="extensions">
|
<Tabs.Trigger value="extensions">
|
||||||
<Icon name="extensions" />
|
<Icon name="extensions" />
|
||||||
{language.t("settings.tab.extensions")}
|
{language.t("settings.tab.extensions")}
|
||||||
</TabsV2.Trigger>
|
</Tabs.Trigger>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -141,41 +141,41 @@ export const DialogSettings: Component<{
|
|||||||
<span>v{platform.version}</span>
|
<span>v{platform.version}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<TabsV2.Content value="general" class="settings-v2-panel">
|
<Tabs.Content value="general" class="settings-v2-panel">
|
||||||
<SettingsGeneral server={server()} sessionID={props.sessionID} />
|
<SettingsGeneral server={server()} sessionID={props.sessionID} />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="appearance" class="settings-v2-panel">
|
<Tabs.Content value="appearance" class="settings-v2-panel">
|
||||||
<SettingsAppearanceV2 />
|
<SettingsAppearanceV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="notifications" class="settings-v2-panel">
|
<Tabs.Content value="notifications" class="settings-v2-panel">
|
||||||
<SettingsNotificationsV2 />
|
<SettingsNotificationsV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="shortcuts" class="settings-v2-panel">
|
<Tabs.Content value="shortcuts" class="settings-v2-panel">
|
||||||
<SettingsKeybinds />
|
<SettingsKeybinds />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="servers" class="settings-v2-panel">
|
<Tabs.Content value="servers" class="settings-v2-panel">
|
||||||
<SettingsServersV2 />
|
<SettingsServersV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="projects" class="settings-v2-panel">
|
<Tabs.Content value="projects" class="settings-v2-panel">
|
||||||
<SettingsProjectsV2 />
|
<SettingsProjectsV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<SettingsServerScope directory={directory()}>
|
<SettingsServerScope directory={directory()}>
|
||||||
<TabsV2.Content value="workspaces" class="settings-v2-panel">
|
<Tabs.Content value="workspaces" class="settings-v2-panel">
|
||||||
<SettingsWorkspacesV2 activeDirectory={directory()} />
|
<SettingsWorkspacesV2 activeDirectory={directory()} />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="providers" class="settings-v2-panel">
|
<Tabs.Content value="providers" class="settings-v2-panel">
|
||||||
<SettingsProvidersV2 directory={directory()} onBack={showProviders} />
|
<SettingsProvidersV2 directory={directory()} onBack={showProviders} />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="models" class="settings-v2-panel">
|
<Tabs.Content value="models" class="settings-v2-panel">
|
||||||
<SettingsModelsV2 />
|
<SettingsModelsV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
<TabsV2.Content value="extensions" class="settings-v2-panel">
|
<Tabs.Content value="extensions" class="settings-v2-panel">
|
||||||
<SettingsExtensionsV2 />
|
<SettingsExtensionsV2 />
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
</SettingsServerScope>
|
</SettingsServerScope>
|
||||||
</TabsV2>
|
</Tabs>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Component, For, createEffect, createMemo, createResource } from "solid-js"
|
import { Component, For, createEffect, createMemo, createResource } from "solid-js"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useData } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useMcpToggle } from "@/context/mcp"
|
import { useMcpToggle } from "@/context/mcp"
|
||||||
|
import { pluginLabel } from "@/utils/plugin"
|
||||||
import { ExternalLink } from "../external-link"
|
import { ExternalLink } from "../external-link"
|
||||||
import { InlineServerSelect } from "./parts/server-select"
|
import { InlineServerSelect } from "./parts/server-select"
|
||||||
import "./settings-v2.css"
|
import "./settings-v2.css"
|
||||||
@@ -44,7 +45,9 @@ export const SettingsExtensionsV2: Component = () => {
|
|||||||
() => serverSdk.connection.status() === "connected",
|
() => serverSdk.connection.status() === "connected",
|
||||||
() => serverSdk.api.plugin.list().then((result) => result.data),
|
() => serverSdk.api.plugin.list().then((result) => result.data),
|
||||||
)
|
)
|
||||||
const plugins = createMemo<PluginRowItem[]>(() => (pluginList.latest ?? []).map((item) => ({ name: item.id })))
|
const plugins = createMemo<PluginRowItem[]>(() =>
|
||||||
|
(pluginList.latest ?? []).map((item) => ({ name: pluginLabel(item) })),
|
||||||
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (serverSdk.connection.status() !== "connected") return
|
if (serverSdk.connection.status() !== "connected") return
|
||||||
@@ -65,14 +68,14 @@ export const SettingsExtensionsV2: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-v2-tab-body">
|
<div class="settings-v2-tab-body">
|
||||||
<TabsV2 variant="pill" defaultValue="mcps" class="settings-v2-extensions-tabs">
|
<Tabs variant="pill" defaultValue="mcps" class="settings-v2-extensions-tabs">
|
||||||
<TabsV2.List>
|
<Tabs.List>
|
||||||
<TabsV2.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</TabsV2.Trigger>
|
<Tabs.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</TabsV2.Trigger>
|
<Tabs.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</Tabs.Trigger>
|
||||||
<TabsV2.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</TabsV2.Trigger>
|
<Tabs.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</Tabs.Trigger>
|
||||||
</TabsV2.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<TabsV2.Content value="mcps">
|
<Tabs.Content value="mcps">
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-13-medium text-v2-text-text-base">
|
<span class="text-13-medium text-v2-text-text-base">
|
||||||
@@ -96,9 +99,9 @@ export const SettingsExtensionsV2: Component = () => {
|
|||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="plugins">
|
<Tabs.Content value="plugins">
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-13-medium text-v2-text-text-base">
|
<span class="text-13-medium text-v2-text-text-base">
|
||||||
@@ -119,9 +122,9 @@ export const SettingsExtensionsV2: Component = () => {
|
|||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="skills">
|
<Tabs.Content value="skills">
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-13-medium text-v2-text-text-base">
|
<span class="text-13-medium text-v2-text-text-base">
|
||||||
@@ -147,8 +150,8 @@ export const SettingsExtensionsV2: Component = () => {
|
|||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</Tabs.Content>
|
||||||
</TabsV2>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Show, createMemo, createResource } from "solid-js"
|
import { Component, Show, createMemo, createResource } from "solid-js"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useUpdaterAction } from "../updater-action"
|
import { useUpdaterAction } from "../updater-action"
|
||||||
@@ -100,8 +100,7 @@ const WorkspaceDestinationSetting: Component = () => {
|
|||||||
title={language.t("settings.workspaces.default.title")}
|
title={language.t("settings.workspaces.default.title")}
|
||||||
description={language.t("settings.workspaces.default.description")}
|
description={language.t("settings.workspaces.default.description")}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
options={options()}
|
options={options()}
|
||||||
current={options().find((option) => option.value === settings.workspaces.defaultDestination())}
|
current={options().find((option) => option.value === settings.workspaces.defaultDestination())}
|
||||||
value={(option) => option.value}
|
value={(option) => option.value}
|
||||||
@@ -127,8 +126,7 @@ const ShellSetting: Component<{ controller: ShellSettingsController }> = (props)
|
|||||||
title={language.t("settings.general.row.shell.title")}
|
title={language.t("settings.general.row.shell.title")}
|
||||||
description={language.t("settings.general.row.shell.description")}
|
description={language.t("settings.general.row.shell.description")}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-shell"
|
data-action="settings-shell"
|
||||||
options={options()}
|
options={options()}
|
||||||
current={options().find((option) => option.value === props.controller.current()) ?? options()[0]}
|
current={options().find((option) => option.value === props.controller.current()) ?? options()[0]}
|
||||||
@@ -156,8 +154,7 @@ const AppearanceSection: Component<{ controller: AppearanceSettingsController }>
|
|||||||
title={language.t("settings.general.row.colorScheme.title")}
|
title={language.t("settings.general.row.colorScheme.title")}
|
||||||
description={language.t("settings.general.row.colorScheme.description")}
|
description={language.t("settings.general.row.colorScheme.description")}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-color-scheme"
|
data-action="settings-color-scheme"
|
||||||
options={schemeOptions}
|
options={schemeOptions}
|
||||||
current={schemeOptions.find((option) => option === props.controller.scheme.current())}
|
current={schemeOptions.find((option) => option === props.controller.scheme.current())}
|
||||||
@@ -183,8 +180,7 @@ const AppearanceSection: Component<{ controller: AppearanceSettingsController }>
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-theme"
|
data-action="settings-theme"
|
||||||
options={props.controller.theme.options()}
|
options={props.controller.theme.options()}
|
||||||
current={props.controller.theme.current()}
|
current={props.controller.theme.current()}
|
||||||
@@ -213,7 +209,7 @@ const FontSetting: Component<{
|
|||||||
return (
|
return (
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
||||||
<div class="w-full sm:w-[220px]">
|
<div class="w-full sm:w-[220px]">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
data-action={config().action}
|
data-action={config().action}
|
||||||
type="text"
|
type="text"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
@@ -254,8 +250,7 @@ const SoundSetting: Component<{
|
|||||||
const config = () => soundSettings[props.kind]
|
const config = () => soundSettings[props.kind]
|
||||||
return (
|
return (
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action={config().action}
|
data-action={config().action}
|
||||||
options={soundOptions}
|
options={soundOptions}
|
||||||
current={props.channel.current()}
|
current={props.channel.current()}
|
||||||
@@ -283,8 +278,7 @@ const LanguageSetting = () => {
|
|||||||
title={language.t("settings.general.row.language.title")}
|
title={language.t("settings.general.row.language.title")}
|
||||||
description={language.t("settings.general.row.language.description")}
|
description={language.t("settings.general.row.language.description")}
|
||||||
>
|
>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-language"
|
data-action="settings-language"
|
||||||
options={options()}
|
options={options()}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
@@ -500,9 +494,9 @@ export const SettingsGeneral: Component<{
|
|||||||
title={language.t("settings.updates.row.check.title")}
|
title={language.t("settings.updates.row.check.title")}
|
||||||
description={language.t("settings.updates.row.check.description")}
|
description={language.t("settings.updates.row.check.description")}
|
||||||
>
|
>
|
||||||
<ButtonV2 size="normal" variant="neutral" disabled={!updater.action().run} onClick={() => updater.run()}>
|
<Button size="normal" variant="neutral" disabled={!updater.action().run} onClick={() => updater.run()}>
|
||||||
{language.t(updater.action().label)}
|
{language.t(updater.action().label)}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
</SettingsListV2>
|
</SettingsListV2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { type Component, For, Show } from "solid-js"
|
import { type Component, For, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
@@ -62,7 +62,7 @@ export const SettingsModelsV2: Component = () => {
|
|||||||
<InlineServerSelect />
|
<InlineServerSelect />
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-v2-tab-search">
|
<div class="settings-v2-tab-search">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
value={list.filter()}
|
value={list.filter()}
|
||||||
@@ -75,12 +75,12 @@ export const SettingsModelsV2: Component = () => {
|
|||||||
aria-label={language.t("dialog.model.search.placeholder")}
|
aria-label={language.t("dialog.model.search.placeholder")}
|
||||||
/>
|
/>
|
||||||
<Show when={list.filter()}>
|
<Show when={list.filter()}>
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
class="settings-v2-tab-search-clear"
|
class="settings-v2-tab-search-clear"
|
||||||
icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
|
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||||
onClick={() => list.clear()}
|
onClick={() => list.clear()}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component } from "solid-js"
|
import { Component } from "solid-js"
|
||||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { SettingsListV2 } from "./parts/list"
|
import { SettingsListV2 } from "./parts/list"
|
||||||
@@ -34,8 +34,7 @@ const SoundSetting: Component<{
|
|||||||
const config = () => soundSettings[props.kind]
|
const config = () => soundSettings[props.kind]
|
||||||
return (
|
return (
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action={config().action}
|
data-action={config().action}
|
||||||
options={soundOptions}
|
options={soundOptions}
|
||||||
current={props.channel.current()}
|
current={props.channel.current()}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Show, createMemo, type Component } from "solid-js"
|
import { Show, createMemo, type Component } from "solid-js"
|
||||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { useGlobal } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
import { ServerConnection, serverName } from "@/context/servers"
|
import { ServerConnection, serverName } from "@/context/servers"
|
||||||
|
|
||||||
@@ -20,8 +20,7 @@ export const InlineServerSelect: Component<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={options().length > 1}>
|
<Show when={options().length > 1}>
|
||||||
<SelectV2
|
<Select
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-server-select"
|
data-action="settings-server-select"
|
||||||
options={options()}
|
options={options()}
|
||||||
current={current()}
|
current={current()}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, For, Show, createMemo, createSignal } from "solid-js"
|
import { Component, For, Show, createMemo, createSignal } from "solid-js"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
|
import { ProjectAvatar } from "@opencode-ai/ui/project-avatar"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useGlobal } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
@@ -51,11 +51,11 @@ export const SettingsProjectsV2: Component = () => {
|
|||||||
<span class="text-13-medium text-v2-text-text-base truncate">{name()}</span>
|
<span class="text-13-medium text-v2-text-text-base truncate">{name()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
<div class="flex items-center gap-2 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<IconV2 name="settings-gear" size="small" class="text-v2-icon-icon-muted" />}
|
icon={<Icon name="settings-gear" size="small" class="text-v2-icon-icon-muted" />}
|
||||||
onClick={(event: MouseEvent) => {
|
onClick={(event: MouseEvent) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
openProjectSettings(props.project, props.server)
|
openProjectSettings(props.project, props.server)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
import { Badge } from "@opencode-ai/ui/badge"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
@@ -160,7 +160,7 @@ export const SettingsProvidersV2: Component<{
|
|||||||
/>
|
/>
|
||||||
<div class="settings-v2-provider-main">
|
<div class="settings-v2-provider-main">
|
||||||
<span class="settings-v2-provider-name truncate">{item.name}</span>
|
<span class="settings-v2-provider-name truncate">{item.name}</span>
|
||||||
<Tag>{type(item)}</Tag>
|
<Badge>{type(item)}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show
|
<Show
|
||||||
@@ -171,9 +171,9 @@ export const SettingsProvidersV2: Component<{
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ButtonV2 size="normal" variant="ghost-muted" onClick={() => void disconnect(item.id, item.name)}>
|
<Button size="normal" variant="ghost-muted" onClick={() => void disconnect(item.id, item.name)}>
|
||||||
{language.t("common.disconnect")}
|
{language.t("common.disconnect")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -199,7 +199,7 @@ export const SettingsProvidersV2: Component<{
|
|||||||
<div class="settings-v2-provider-main">
|
<div class="settings-v2-provider-main">
|
||||||
<span class="settings-v2-provider-name">{item.name}</span>
|
<span class="settings-v2-provider-name">{item.name}</span>
|
||||||
<Show when={item.id === "opencode" || item.id === "opencode-go"}>
|
<Show when={item.id === "opencode" || item.id === "opencode-go"}>
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
<Badge>{language.t("dialog.provider.tag.recommended")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={note(item.id)}>
|
<Show when={note(item.id)}>
|
||||||
@@ -207,9 +207,9 @@ export const SettingsProvidersV2: Component<{
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ButtonV2 size="normal" variant="neutral" icon="plus" onClick={() => connect(item.id)}>
|
<Button size="normal" variant="neutral" icon="plus" onClick={() => connect(item.id)}>
|
||||||
{language.t("common.connect")}
|
{language.t("common.connect")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -226,14 +226,14 @@ export const SettingsProvidersV2: Component<{
|
|||||||
<div class="settings-v2-provider-copy">
|
<div class="settings-v2-provider-copy">
|
||||||
<div class="settings-v2-provider-main">
|
<div class="settings-v2-provider-main">
|
||||||
<span class="settings-v2-provider-name">{language.t("provider.custom.title")}</span>
|
<span class="settings-v2-provider-name">{language.t("provider.custom.title")}</span>
|
||||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
<Badge>{language.t("settings.providers.tag.custom")}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p class="settings-v2-provider-description">
|
<p class="settings-v2-provider-description">
|
||||||
{language.t("settings.providers.custom.description")}
|
{language.t("settings.providers.custom.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ButtonV2
|
<Button
|
||||||
size="normal"
|
size="normal"
|
||||||
variant="neutral"
|
variant="neutral"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
@@ -246,7 +246,7 @@ export const SettingsProvidersV2: Component<{
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{language.t("common.connect")}
|
{language.t("common.connect")}
|
||||||
</ButtonV2>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</SettingsListV2>
|
</SettingsListV2>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
import { Badge } from "@opencode-ai/ui/badge"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInput } from "@opencode-ai/ui/text-input"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { type Component, For, Show, createMemo } from "solid-js"
|
import { type Component, For, Show, createMemo } from "solid-js"
|
||||||
@@ -61,7 +61,7 @@ export const SettingsServersV2: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={showSearch()}>
|
<Show when={showSearch()}>
|
||||||
<div class="settings-v2-tab-search">
|
<div class="settings-v2-tab-search">
|
||||||
<TextInputV2
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
appearance="base"
|
appearance="base"
|
||||||
value={store.filter}
|
value={store.filter}
|
||||||
@@ -74,12 +74,12 @@ export const SettingsServersV2: Component = () => {
|
|||||||
aria-label={language.t("dialog.server.search.placeholder")}
|
aria-label={language.t("dialog.server.search.placeholder")}
|
||||||
/>
|
/>
|
||||||
<Show when={store.filter}>
|
<Show when={store.filter}>
|
||||||
<IconButtonV2
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
size="small"
|
size="small"
|
||||||
class="settings-v2-tab-search-clear"
|
class="settings-v2-tab-search-clear"
|
||||||
icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
|
icon={<Icon name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||||
onClick={() => setStore("filter", "")}
|
onClick={() => setStore("filter", "")}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -126,7 +126,7 @@ export const SettingsServersV2: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-v2-servers-actions">
|
<div class="settings-v2-servers-actions">
|
||||||
<Show when={controller.defaults.available() && isDefault()}>
|
<Show when={controller.defaults.available() && isDefault()}>
|
||||||
<Tag>{language.t("dialog.server.status.default")}</Tag>
|
<Badge>{language.t("dialog.server.status.default")}</Badge>
|
||||||
</Show>
|
</Show>
|
||||||
<ServerRowMenu server={item} domain={controller} onEdit={openEdit} />
|
<ServerRowMenu server={item} domain={controller} onEdit={openEdit} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "@opencode-ai/ui/v2/text-input-v2.css";
|
@import "@opencode-ai/ui/text-input.css";
|
||||||
@import "@opencode-ai/ui/v2/button-v2.css";
|
@import "@opencode-ai/ui/button.css";
|
||||||
|
|
||||||
[data-component="tabs-v2"][data-variant="settings"] {
|
[data-component="tabs-v2"][data-variant="settings"] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user