mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-16 09:28:27 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ff4da5639 | |||
| b6e94fd2a8 | |||
| 3d5c041129 | |||
| 3af557512b | |||
| b53a95fd81 | |||
| 8e11a46fe0 | |||
| 8ab4d84057 | |||
| 4066247988 | |||
| b9de3ad370 |
+1
-5
@@ -10,9 +10,6 @@
|
|||||||
adamdotdevin
|
adamdotdevin
|
||||||
-agusbasari29 AI PR slop
|
-agusbasari29 AI PR slop
|
||||||
ariane-emory
|
ariane-emory
|
||||||
-atharvau AI review spamming literally every PR
|
|
||||||
-danieljoshuanazareth
|
|
||||||
-danieljoshuanazareth
|
|
||||||
edemaine
|
edemaine
|
||||||
-florianleibert
|
-florianleibert
|
||||||
fwang
|
fwang
|
||||||
@@ -20,9 +17,8 @@ iamdavidhill
|
|||||||
jayair
|
jayair
|
||||||
kitlangton
|
kitlangton
|
||||||
kommander
|
kommander
|
||||||
-opencode2026
|
|
||||||
r44vc0rp
|
r44vc0rp
|
||||||
rekram1-node
|
rekram1-node
|
||||||
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
||||||
thdxr
|
thdxr
|
||||||
-OpenCodeEngineer bot that spams issues
|
-OpenCode2026
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
name: close-issues
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 2 * * *" # Daily at 2:00 AM
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
close:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: Close stale issues
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: bun script/github/close-issues.ts
|
|
||||||
@@ -11,7 +11,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true
|
nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true
|
||||||
|
|
||||||
# Extract hash from build log with portability
|
# Extract hash from build log with portability
|
||||||
HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)"
|
HASH="$(grep -oE 'sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)"
|
||||||
|
|
||||||
if [ -z "$HASH" ]; then
|
if [ -z "$HASH" ]; then
|
||||||
echo "::error::Failed to compute hash for ${SYSTEM}"
|
echo "::error::Failed to compute hash for ${SYSTEM}"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: stale-issues
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *" # Daily at 1:30 AM
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
DAYS_BEFORE_STALE: 90
|
||||||
|
DAYS_BEFORE_CLOSE: 7
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v10
|
||||||
|
with:
|
||||||
|
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
|
||||||
|
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
close-issue-message: |
|
||||||
|
[automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity.
|
||||||
|
|
||||||
|
Feel free to reopen if you still need this!
|
||||||
|
stale-issue-message: |
|
||||||
|
[automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days.
|
||||||
|
|
||||||
|
It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity.
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
exempt-issue-labels: "pinned,security,feature-request,on-hold"
|
||||||
|
start-date: "2025-12-27"
|
||||||
@@ -33,6 +33,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
issue-id: ${{ github.event.issue.number }}
|
issue-id: ${{ github.event.issue.number }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
roles: admin,maintain,write
|
roles: admin,maintain
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
model: opencode/kimi-k2.5
|
|
||||||
---
|
|
||||||
|
|
||||||
create UPCOMING_CHANGELOG.md
|
|
||||||
|
|
||||||
it should have sections
|
|
||||||
|
|
||||||
```
|
|
||||||
## TUI
|
|
||||||
|
|
||||||
## Desktop
|
|
||||||
|
|
||||||
## Core
|
|
||||||
|
|
||||||
## Misc
|
|
||||||
```
|
|
||||||
|
|
||||||
fetch the latest github release for this repository to determine the last release version.
|
|
||||||
|
|
||||||
find each PR that was merged since the last release
|
|
||||||
|
|
||||||
for each PR spawn a subagent to summarize what the PR was about. focus on user facing changes. if it was entirely internal or code related you can ignore it. also skip docs updates. each subagent should append its summary to UPCOMING_CHANGELOG.md into the appropriate section.
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://opencode.ai/theme.json",
|
|
||||||
"defs": {
|
|
||||||
"nord0": "#2E3440",
|
|
||||||
"nord1": "#3B4252",
|
|
||||||
"nord2": "#434C5E",
|
|
||||||
"nord3": "#4C566A",
|
|
||||||
"nord4": "#D8DEE9",
|
|
||||||
"nord5": "#E5E9F0",
|
|
||||||
"nord6": "#ECEFF4",
|
|
||||||
"nord7": "#8FBCBB",
|
|
||||||
"nord8": "#88C0D0",
|
|
||||||
"nord9": "#81A1C1",
|
|
||||||
"nord10": "#5E81AC",
|
|
||||||
"nord11": "#BF616A",
|
|
||||||
"nord12": "#D08770",
|
|
||||||
"nord13": "#EBCB8B",
|
|
||||||
"nord14": "#A3BE8C",
|
|
||||||
"nord15": "#B48EAD"
|
|
||||||
},
|
|
||||||
"theme": {
|
|
||||||
"primary": {
|
|
||||||
"dark": "nord10",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"secondary": {
|
|
||||||
"dark": "nord9",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"accent": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"dark": "nord11",
|
|
||||||
"light": "nord11"
|
|
||||||
},
|
|
||||||
"warning": {
|
|
||||||
"dark": "nord12",
|
|
||||||
"light": "nord12"
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"dark": "nord14",
|
|
||||||
"light": "nord14"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"dark": "nord8",
|
|
||||||
"light": "nord10"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"dark": "nord6",
|
|
||||||
"light": "nord0"
|
|
||||||
},
|
|
||||||
"textMuted": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord1"
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"dark": "nord0",
|
|
||||||
"light": "nord6"
|
|
||||||
},
|
|
||||||
"backgroundPanel": {
|
|
||||||
"dark": "nord1",
|
|
||||||
"light": "nord5"
|
|
||||||
},
|
|
||||||
"backgroundElement": {
|
|
||||||
"dark": "nord2",
|
|
||||||
"light": "nord4"
|
|
||||||
},
|
|
||||||
"border": {
|
|
||||||
"dark": "nord2",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"borderActive": {
|
|
||||||
"dark": "nord3",
|
|
||||||
"light": "nord2"
|
|
||||||
},
|
|
||||||
"borderSubtle": {
|
|
||||||
"dark": "nord2",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"diffAdded": {
|
|
||||||
"dark": "nord14",
|
|
||||||
"light": "nord14"
|
|
||||||
},
|
|
||||||
"diffRemoved": {
|
|
||||||
"dark": "nord11",
|
|
||||||
"light": "nord11"
|
|
||||||
},
|
|
||||||
"diffContext": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"diffHunkHeader": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"diffHighlightAdded": {
|
|
||||||
"dark": "nord14",
|
|
||||||
"light": "nord14"
|
|
||||||
},
|
|
||||||
"diffHighlightRemoved": {
|
|
||||||
"dark": "nord11",
|
|
||||||
"light": "nord11"
|
|
||||||
},
|
|
||||||
"diffAddedBg": {
|
|
||||||
"dark": "#36413C",
|
|
||||||
"light": "#E6EBE7"
|
|
||||||
},
|
|
||||||
"diffRemovedBg": {
|
|
||||||
"dark": "#43393D",
|
|
||||||
"light": "#ECE6E8"
|
|
||||||
},
|
|
||||||
"diffContextBg": {
|
|
||||||
"dark": "nord1",
|
|
||||||
"light": "nord5"
|
|
||||||
},
|
|
||||||
"diffLineNumber": {
|
|
||||||
"dark": "nord2",
|
|
||||||
"light": "nord4"
|
|
||||||
},
|
|
||||||
"diffAddedLineNumberBg": {
|
|
||||||
"dark": "#303A35",
|
|
||||||
"light": "#DDE4DF"
|
|
||||||
},
|
|
||||||
"diffRemovedLineNumberBg": {
|
|
||||||
"dark": "#3C3336",
|
|
||||||
"light": "#E4DDE0"
|
|
||||||
},
|
|
||||||
"markdownText": {
|
|
||||||
"dark": "nord4",
|
|
||||||
"light": "nord0"
|
|
||||||
},
|
|
||||||
"markdownHeading": {
|
|
||||||
"dark": "nord8",
|
|
||||||
"light": "nord10"
|
|
||||||
},
|
|
||||||
"markdownLink": {
|
|
||||||
"dark": "nord9",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"markdownLinkText": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"markdownCode": {
|
|
||||||
"dark": "nord14",
|
|
||||||
"light": "nord14"
|
|
||||||
},
|
|
||||||
"markdownBlockQuote": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"markdownEmph": {
|
|
||||||
"dark": "nord12",
|
|
||||||
"light": "nord12"
|
|
||||||
},
|
|
||||||
"markdownStrong": {
|
|
||||||
"dark": "nord13",
|
|
||||||
"light": "nord13"
|
|
||||||
},
|
|
||||||
"markdownHorizontalRule": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"markdownListItem": {
|
|
||||||
"dark": "nord8",
|
|
||||||
"light": "nord10"
|
|
||||||
},
|
|
||||||
"markdownListEnumeration": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"markdownImage": {
|
|
||||||
"dark": "nord9",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"markdownImageText": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"markdownCodeBlock": {
|
|
||||||
"dark": "nord4",
|
|
||||||
"light": "nord0"
|
|
||||||
},
|
|
||||||
"syntaxComment": {
|
|
||||||
"dark": "#8B95A7",
|
|
||||||
"light": "nord3"
|
|
||||||
},
|
|
||||||
"syntaxKeyword": {
|
|
||||||
"dark": "nord9",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"syntaxFunction": {
|
|
||||||
"dark": "nord8",
|
|
||||||
"light": "nord8"
|
|
||||||
},
|
|
||||||
"syntaxVariable": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"syntaxString": {
|
|
||||||
"dark": "nord14",
|
|
||||||
"light": "nord14"
|
|
||||||
},
|
|
||||||
"syntaxNumber": {
|
|
||||||
"dark": "nord15",
|
|
||||||
"light": "nord15"
|
|
||||||
},
|
|
||||||
"syntaxType": {
|
|
||||||
"dark": "nord7",
|
|
||||||
"light": "nord7"
|
|
||||||
},
|
|
||||||
"syntaxOperator": {
|
|
||||||
"dark": "nord9",
|
|
||||||
"light": "nord9"
|
|
||||||
},
|
|
||||||
"syntaxPunctuation": {
|
|
||||||
"dark": "nord4",
|
|
||||||
"light": "nord0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,852 +0,0 @@
|
|||||||
/** @jsxImportSource @opentui/solid */
|
|
||||||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
|
||||||
import { RGBA, VignetteEffect } from "@opentui/core"
|
|
||||||
import type { TuiKeybindSet, TuiPluginApi, TuiPluginMeta, TuiSlotPlugin } from "@opencode-ai/plugin/tui"
|
|
||||||
|
|
||||||
const tabs = ["overview", "counter", "help"]
|
|
||||||
const bind = {
|
|
||||||
modal: "ctrl+shift+m",
|
|
||||||
screen: "ctrl+shift+o",
|
|
||||||
home: "escape,ctrl+h",
|
|
||||||
left: "left,h",
|
|
||||||
right: "right,l",
|
|
||||||
up: "up,k",
|
|
||||||
down: "down,j",
|
|
||||||
alert: "a",
|
|
||||||
confirm: "c",
|
|
||||||
prompt: "p",
|
|
||||||
select: "s",
|
|
||||||
modal_accept: "enter,return",
|
|
||||||
modal_close: "escape",
|
|
||||||
dialog_close: "escape",
|
|
||||||
local: "x",
|
|
||||||
local_push: "enter,return",
|
|
||||||
local_close: "q,backspace",
|
|
||||||
host: "z",
|
|
||||||
}
|
|
||||||
|
|
||||||
const pick = (value: unknown, fallback: string) => {
|
|
||||||
if (typeof value !== "string") return fallback
|
|
||||||
if (!value.trim()) return fallback
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const num = (value: unknown, fallback: number) => {
|
|
||||||
if (typeof value !== "number") return fallback
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const rec = (value: unknown) => {
|
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return
|
|
||||||
return Object.fromEntries(Object.entries(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cfg = {
|
|
||||||
label: string
|
|
||||||
route: string
|
|
||||||
vignette: number
|
|
||||||
keybinds: Record<string, unknown> | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
type Route = {
|
|
||||||
modal: string
|
|
||||||
screen: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
tab: number
|
|
||||||
count: number
|
|
||||||
source: string
|
|
||||||
note: string
|
|
||||||
selected: string
|
|
||||||
local: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const cfg = (options: Record<string, unknown> | undefined) => {
|
|
||||||
return {
|
|
||||||
label: pick(options?.label, "smoke"),
|
|
||||||
route: pick(options?.route, "workspace-smoke"),
|
|
||||||
vignette: Math.max(0, num(options?.vignette, 0.35)),
|
|
||||||
keybinds: rec(options?.keybinds),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const names = (input: Cfg) => {
|
|
||||||
return {
|
|
||||||
modal: `${input.route}.modal`,
|
|
||||||
screen: `${input.route}.screen`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Keys = TuiKeybindSet
|
|
||||||
const ui = {
|
|
||||||
panel: "#1d1d1d",
|
|
||||||
border: "#4a4a4a",
|
|
||||||
text: "#f0f0f0",
|
|
||||||
muted: "#a5a5a5",
|
|
||||||
accent: "#5f87ff",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Color = RGBA | string
|
|
||||||
|
|
||||||
const ink = (map: Record<string, unknown>, name: string, fallback: string): Color => {
|
|
||||||
const value = map[name]
|
|
||||||
if (typeof value === "string") return value
|
|
||||||
if (value instanceof RGBA) return value
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
const look = (map: Record<string, unknown>) => {
|
|
||||||
return {
|
|
||||||
panel: ink(map, "backgroundPanel", ui.panel),
|
|
||||||
border: ink(map, "border", ui.border),
|
|
||||||
text: ink(map, "text", ui.text),
|
|
||||||
muted: ink(map, "textMuted", ui.muted),
|
|
||||||
accent: ink(map, "primary", ui.accent),
|
|
||||||
selected: ink(map, "selectedListItemText", ui.text),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tone = (api: TuiPluginApi) => {
|
|
||||||
return look(api.theme.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Skin = {
|
|
||||||
panel: Color
|
|
||||||
border: Color
|
|
||||||
text: Color
|
|
||||||
muted: Color
|
|
||||||
accent: Color
|
|
||||||
selected: Color
|
|
||||||
}
|
|
||||||
|
|
||||||
const Btn = (props: { txt: string; run: () => void; skin: Skin; on?: boolean }) => {
|
|
||||||
return (
|
|
||||||
<box
|
|
||||||
onMouseUp={() => {
|
|
||||||
props.run()
|
|
||||||
}}
|
|
||||||
backgroundColor={props.on ? props.skin.accent : props.skin.border}
|
|
||||||
paddingLeft={1}
|
|
||||||
paddingRight={1}
|
|
||||||
>
|
|
||||||
<text fg={props.on ? props.skin.selected : props.skin.text}>{props.txt}</text>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse = (params: Record<string, unknown> | undefined) => {
|
|
||||||
const tab = typeof params?.tab === "number" ? params.tab : 0
|
|
||||||
const count = typeof params?.count === "number" ? params.count : 0
|
|
||||||
const source = typeof params?.source === "string" ? params.source : "unknown"
|
|
||||||
const note = typeof params?.note === "string" ? params.note : ""
|
|
||||||
const selected = typeof params?.selected === "string" ? params.selected : ""
|
|
||||||
const local = typeof params?.local === "number" ? params.local : 0
|
|
||||||
return {
|
|
||||||
tab: Math.max(0, Math.min(tab, tabs.length - 1)),
|
|
||||||
count,
|
|
||||||
source,
|
|
||||||
note,
|
|
||||||
selected,
|
|
||||||
local: Math.max(0, local),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = (api: TuiPluginApi, route: Route) => {
|
|
||||||
const value = api.route.current
|
|
||||||
const ok = Object.values(route).includes(value.name)
|
|
||||||
if (!ok) return parse(undefined)
|
|
||||||
if (!("params" in value)) return parse(undefined)
|
|
||||||
return parse(value.params)
|
|
||||||
}
|
|
||||||
|
|
||||||
const opts = [
|
|
||||||
{
|
|
||||||
title: "Overview",
|
|
||||||
value: 0,
|
|
||||||
description: "Switch to overview tab",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Counter",
|
|
||||||
value: 1,
|
|
||||||
description: "Switch to counter tab",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Help",
|
|
||||||
value: 2,
|
|
||||||
description: "Switch to help tab",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const host = (api: TuiPluginApi, input: Cfg, skin: Skin) => {
|
|
||||||
api.ui.dialog.setSize("medium")
|
|
||||||
api.ui.dialog.replace(() => (
|
|
||||||
<box paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1} flexDirection="column">
|
|
||||||
<text fg={skin.text}>
|
|
||||||
<b>{input.label} host overlay</b>
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>Using api.ui.dialog stack with built-in backdrop</text>
|
|
||||||
<text fg={skin.muted}>esc closes · depth {api.ui.dialog.depth}</text>
|
|
||||||
<box flexDirection="row" gap={1}>
|
|
||||||
<Btn txt="close" run={() => api.ui.dialog.clear()} skin={skin} on />
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const warn = (api: TuiPluginApi, route: Route, value: State) => {
|
|
||||||
const DialogAlert = api.ui.DialogAlert
|
|
||||||
api.ui.dialog.setSize("medium")
|
|
||||||
api.ui.dialog.replace(() => (
|
|
||||||
<DialogAlert
|
|
||||||
title="Smoke alert"
|
|
||||||
message="Testing built-in alert dialog"
|
|
||||||
onConfirm={() => api.route.navigate(route.screen, { ...value, source: "alert" })}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const check = (api: TuiPluginApi, route: Route, value: State) => {
|
|
||||||
const DialogConfirm = api.ui.DialogConfirm
|
|
||||||
api.ui.dialog.setSize("medium")
|
|
||||||
api.ui.dialog.replace(() => (
|
|
||||||
<DialogConfirm
|
|
||||||
title="Smoke confirm"
|
|
||||||
message="Apply +1 to counter?"
|
|
||||||
onConfirm={() => api.route.navigate(route.screen, { ...value, count: value.count + 1, source: "confirm" })}
|
|
||||||
onCancel={() => api.route.navigate(route.screen, { ...value, source: "confirm-cancel" })}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = (api: TuiPluginApi, route: Route, value: State) => {
|
|
||||||
const DialogPrompt = api.ui.DialogPrompt
|
|
||||||
api.ui.dialog.setSize("medium")
|
|
||||||
api.ui.dialog.replace(() => (
|
|
||||||
<DialogPrompt
|
|
||||||
title="Smoke prompt"
|
|
||||||
value={value.note}
|
|
||||||
onConfirm={(note) => {
|
|
||||||
api.ui.dialog.clear()
|
|
||||||
api.route.navigate(route.screen, { ...value, note, source: "prompt" })
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
api.ui.dialog.clear()
|
|
||||||
api.route.navigate(route.screen, value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const picker = (api: TuiPluginApi, route: Route, value: State) => {
|
|
||||||
const DialogSelect = api.ui.DialogSelect
|
|
||||||
api.ui.dialog.setSize("medium")
|
|
||||||
api.ui.dialog.replace(() => (
|
|
||||||
<DialogSelect
|
|
||||||
title="Smoke select"
|
|
||||||
options={opts}
|
|
||||||
current={value.tab}
|
|
||||||
onSelect={(item) => {
|
|
||||||
api.ui.dialog.clear()
|
|
||||||
api.route.navigate(route.screen, {
|
|
||||||
...value,
|
|
||||||
tab: typeof item.value === "number" ? item.value : value.tab,
|
|
||||||
selected: item.title,
|
|
||||||
source: "select",
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const Screen = (props: {
|
|
||||||
api: TuiPluginApi
|
|
||||||
input: Cfg
|
|
||||||
route: Route
|
|
||||||
keys: Keys
|
|
||||||
meta: TuiPluginMeta
|
|
||||||
params?: Record<string, unknown>
|
|
||||||
}) => {
|
|
||||||
const dim = useTerminalDimensions()
|
|
||||||
const value = parse(props.params)
|
|
||||||
const skin = tone(props.api)
|
|
||||||
const set = (local: number, base?: State) => {
|
|
||||||
const next = base ?? current(props.api, props.route)
|
|
||||||
props.api.route.navigate(props.route.screen, { ...next, local: Math.max(0, local), source: "local" })
|
|
||||||
}
|
|
||||||
const push = (base?: State) => {
|
|
||||||
const next = base ?? current(props.api, props.route)
|
|
||||||
set(next.local + 1, next)
|
|
||||||
}
|
|
||||||
const open = () => {
|
|
||||||
const next = current(props.api, props.route)
|
|
||||||
if (next.local > 0) return
|
|
||||||
set(1, next)
|
|
||||||
}
|
|
||||||
const pop = (base?: State) => {
|
|
||||||
const next = base ?? current(props.api, props.route)
|
|
||||||
const local = Math.max(0, next.local - 1)
|
|
||||||
set(local, next)
|
|
||||||
}
|
|
||||||
const show = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
open()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
useKeyboard((evt) => {
|
|
||||||
if (props.api.route.current.name !== props.route.screen) return
|
|
||||||
const next = current(props.api, props.route)
|
|
||||||
if (props.api.ui.dialog.open) {
|
|
||||||
if (props.keys.match("dialog_close", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.ui.dialog.clear()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.local > 0) {
|
|
||||||
if (evt.name === "escape" || props.keys.match("local_close", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
pop(next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("local_push", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
push(next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("home", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate("home")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("left", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("right", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("up", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("down", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("modal", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.modal, next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("local", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
open()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("host", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
host(props.api, props.input, skin)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("alert", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
warn(props.api, props.route, next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("confirm", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
check(props.api, props.route, next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("prompt", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
entry(props.api, props.route, next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("select", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
picker(props.api, props.route, next)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box width={dim().width} height={dim().height} backgroundColor={skin.panel} position="relative">
|
|
||||||
<box
|
|
||||||
flexDirection="column"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
paddingTop={1}
|
|
||||||
paddingBottom={1}
|
|
||||||
paddingLeft={2}
|
|
||||||
paddingRight={2}
|
|
||||||
>
|
|
||||||
<box flexDirection="row" justifyContent="space-between" paddingBottom={1}>
|
|
||||||
<text fg={skin.text}>
|
|
||||||
<b>{props.input.label} screen</b>
|
|
||||||
<span style={{ fg: skin.muted }}> plugin route</span>
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>{props.keys.print("home")} home</text>
|
|
||||||
</box>
|
|
||||||
|
|
||||||
<box flexDirection="row" gap={1} paddingBottom={1}>
|
|
||||||
{tabs.map((item, i) => {
|
|
||||||
const on = value.tab === i
|
|
||||||
return (
|
|
||||||
<Btn
|
|
||||||
txt={item}
|
|
||||||
run={() => props.api.route.navigate(props.route.screen, { ...value, tab: i })}
|
|
||||||
skin={skin}
|
|
||||||
on={on}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</box>
|
|
||||||
|
|
||||||
<box
|
|
||||||
border
|
|
||||||
borderColor={skin.border}
|
|
||||||
paddingTop={1}
|
|
||||||
paddingBottom={1}
|
|
||||||
paddingLeft={2}
|
|
||||||
paddingRight={2}
|
|
||||||
flexGrow={1}
|
|
||||||
>
|
|
||||||
{value.tab === 0 ? (
|
|
||||||
<box flexDirection="column" gap={1}>
|
|
||||||
<text fg={skin.text}>Route: {props.route.screen}</text>
|
|
||||||
<text fg={skin.muted}>plugin state: {props.meta.state}</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
first: {props.meta.state === "first" ? "yes" : "no"} · updated:{" "}
|
|
||||||
{props.meta.state === "updated" ? "yes" : "no"} · loads: {props.meta.load_count}
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>plugin source: {props.meta.source}</text>
|
|
||||||
<text fg={skin.muted}>source: {value.source}</text>
|
|
||||||
<text fg={skin.muted}>note: {value.note || "(none)"}</text>
|
|
||||||
<text fg={skin.muted}>selected: {value.selected || "(none)"}</text>
|
|
||||||
<text fg={skin.muted}>local stack depth: {value.local}</text>
|
|
||||||
<text fg={skin.muted}>host stack open: {props.api.ui.dialog.open ? "yes" : "no"}</text>
|
|
||||||
</box>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{value.tab === 1 ? (
|
|
||||||
<box flexDirection="column" gap={1}>
|
|
||||||
<text fg={skin.text}>Counter: {value.count}</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{props.keys.print("up")} / {props.keys.print("down")} change value
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{value.tab === 2 ? (
|
|
||||||
<box flexDirection="column" gap={1}>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{props.keys.print("modal")} modal | {props.keys.print("alert")} alert | {props.keys.print("confirm")}{" "}
|
|
||||||
confirm | {props.keys.print("prompt")} prompt | {props.keys.print("select")} select
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{props.keys.print("local")} local stack | {props.keys.print("host")} host stack
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
local open: {props.keys.print("local_push")} push nested · esc or {props.keys.print("local_close")}{" "}
|
|
||||||
close
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>{props.keys.print("home")} returns home</text>
|
|
||||||
</box>
|
|
||||||
) : null}
|
|
||||||
</box>
|
|
||||||
|
|
||||||
<box flexDirection="row" gap={1} paddingTop={1}>
|
|
||||||
<Btn txt="go home" run={() => props.api.route.navigate("home")} skin={skin} />
|
|
||||||
<Btn txt="modal" run={() => props.api.route.navigate(props.route.modal, value)} skin={skin} on />
|
|
||||||
<Btn txt="local overlay" run={show} skin={skin} />
|
|
||||||
<Btn txt="host overlay" run={() => host(props.api, props.input, skin)} skin={skin} />
|
|
||||||
<Btn txt="alert" run={() => warn(props.api, props.route, value)} skin={skin} />
|
|
||||||
<Btn txt="confirm" run={() => check(props.api, props.route, value)} skin={skin} />
|
|
||||||
<Btn txt="prompt" run={() => entry(props.api, props.route, value)} skin={skin} />
|
|
||||||
<Btn txt="select" run={() => picker(props.api, props.route, value)} skin={skin} />
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
|
|
||||||
<box
|
|
||||||
visible={value.local > 0}
|
|
||||||
width={dim().width}
|
|
||||||
height={dim().height}
|
|
||||||
alignItems="center"
|
|
||||||
position="absolute"
|
|
||||||
zIndex={3000}
|
|
||||||
paddingTop={dim().height / 4}
|
|
||||||
left={0}
|
|
||||||
top={0}
|
|
||||||
backgroundColor={RGBA.fromInts(0, 0, 0, 160)}
|
|
||||||
onMouseUp={() => {
|
|
||||||
pop()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<box
|
|
||||||
onMouseUp={(evt) => {
|
|
||||||
evt.stopPropagation()
|
|
||||||
}}
|
|
||||||
width={60}
|
|
||||||
maxWidth={dim().width - 2}
|
|
||||||
backgroundColor={skin.panel}
|
|
||||||
border
|
|
||||||
borderColor={skin.border}
|
|
||||||
paddingTop={1}
|
|
||||||
paddingBottom={1}
|
|
||||||
paddingLeft={2}
|
|
||||||
paddingRight={2}
|
|
||||||
gap={1}
|
|
||||||
flexDirection="column"
|
|
||||||
>
|
|
||||||
<text fg={skin.text}>
|
|
||||||
<b>{props.input.label} local overlay</b>
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>Plugin-owned stack depth: {value.local}</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{props.keys.print("local_push")} push nested · {props.keys.print("local_close")} pop/close
|
|
||||||
</text>
|
|
||||||
<box flexDirection="row" gap={1}>
|
|
||||||
<Btn txt="push" run={push} skin={skin} on />
|
|
||||||
<Btn txt="pop" run={pop} skin={skin} />
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Modal = (props: {
|
|
||||||
api: TuiPluginApi
|
|
||||||
input: Cfg
|
|
||||||
route: Route
|
|
||||||
keys: Keys
|
|
||||||
params?: Record<string, unknown>
|
|
||||||
}) => {
|
|
||||||
const Dialog = props.api.ui.Dialog
|
|
||||||
const value = parse(props.params)
|
|
||||||
const skin = tone(props.api)
|
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
|
||||||
if (props.api.route.current.name !== props.route.modal) return
|
|
||||||
|
|
||||||
if (props.keys.match("modal_accept", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate(props.route.screen, { ...value, source: "modal" })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("modal_close", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate("home")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box width="100%" height="100%" backgroundColor={skin.panel}>
|
|
||||||
<Dialog onClose={() => props.api.route.navigate("home")}>
|
|
||||||
<box paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1} flexDirection="column">
|
|
||||||
<text fg={skin.text}>
|
|
||||||
<b>{props.input.label} modal</b>
|
|
||||||
</text>
|
|
||||||
<text fg={skin.muted}>{props.keys.print("modal")} modal command</text>
|
|
||||||
<text fg={skin.muted}>{props.keys.print("screen")} screen command</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{props.keys.print("modal_accept")} opens screen · {props.keys.print("modal_close")} closes
|
|
||||||
</text>
|
|
||||||
<box flexDirection="row" gap={1}>
|
|
||||||
<Btn
|
|
||||||
txt="open screen"
|
|
||||||
run={() => props.api.route.navigate(props.route.screen, { ...value, source: "modal" })}
|
|
||||||
skin={skin}
|
|
||||||
on
|
|
||||||
/>
|
|
||||||
<Btn txt="cancel" run={() => props.api.route.navigate("home")} skin={skin} />
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
</Dialog>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const home = (input: Cfg): TuiSlotPlugin => ({
|
|
||||||
slots: {
|
|
||||||
home_logo(ctx) {
|
|
||||||
const map = ctx.theme.current
|
|
||||||
const skin = look(map)
|
|
||||||
const art = [
|
|
||||||
" $$\\",
|
|
||||||
" $$ |",
|
|
||||||
" $$$$$$$\\ $$$$$$\\$$$$\\ $$$$$$\\ $$ | $$\\ $$$$$$\\",
|
|
||||||
"$$ _____|$$ _$$ _$$\\ $$ __$$\\ $$ | $$ |$$ __$$\\",
|
|
||||||
"\\$$$$$$\\ $$ / $$ / $$ |$$ / $$ |$$$$$$ / $$$$$$$$ |",
|
|
||||||
" \\____$$\\ $$ | $$ | $$ |$$ | $$ |$$ _$$< $$ ____|",
|
|
||||||
"$$$$$$$ |$$ | $$ | $$ |\\$$$$$$ |$$ | \\$$\\ \\$$$$$$$\\",
|
|
||||||
"\\_______/ \\__| \\__| \\__| \\______/ \\__| \\__| \\_______|",
|
|
||||||
]
|
|
||||||
const fill = [
|
|
||||||
skin.accent,
|
|
||||||
skin.muted,
|
|
||||||
ink(map, "info", ui.accent),
|
|
||||||
skin.text,
|
|
||||||
ink(map, "success", ui.accent),
|
|
||||||
ink(map, "warning", ui.accent),
|
|
||||||
ink(map, "secondary", ui.accent),
|
|
||||||
ink(map, "error", ui.accent),
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box flexDirection="column">
|
|
||||||
{art.map((line, i) => (
|
|
||||||
<text fg={fill[i]}>{line}</text>
|
|
||||||
))}
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
home_bottom(ctx) {
|
|
||||||
const skin = look(ctx.theme.current)
|
|
||||||
const text = "extra content in the unified home bottom slot"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box width="100%" maxWidth={75} alignItems="center" paddingTop={1} flexShrink={0} gap={1}>
|
|
||||||
<box
|
|
||||||
border
|
|
||||||
borderColor={skin.border}
|
|
||||||
backgroundColor={skin.panel}
|
|
||||||
paddingTop={1}
|
|
||||||
paddingBottom={1}
|
|
||||||
paddingLeft={2}
|
|
||||||
paddingRight={2}
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
<span style={{ fg: skin.accent }}>{input.label}</span> {text}
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const block = (input: Cfg, order: number, title: string, text: string): TuiSlotPlugin => ({
|
|
||||||
order,
|
|
||||||
slots: {
|
|
||||||
sidebar_content(ctx, value) {
|
|
||||||
const skin = look(ctx.theme.current)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box
|
|
||||||
border
|
|
||||||
borderColor={skin.border}
|
|
||||||
backgroundColor={skin.panel}
|
|
||||||
paddingTop={1}
|
|
||||||
paddingBottom={1}
|
|
||||||
paddingLeft={2}
|
|
||||||
paddingRight={2}
|
|
||||||
flexDirection="column"
|
|
||||||
gap={1}
|
|
||||||
>
|
|
||||||
<text fg={skin.accent}>
|
|
||||||
<b>{title}</b>
|
|
||||||
</text>
|
|
||||||
<text fg={skin.text}>{text}</text>
|
|
||||||
<text fg={skin.muted}>
|
|
||||||
{input.label} order {order} · session {value.session_id.slice(0, 8)}
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const slot = (input: Cfg): TuiSlotPlugin[] => [
|
|
||||||
home(input),
|
|
||||||
block(input, 50, "Smoke above", "renders above internal sidebar blocks"),
|
|
||||||
block(input, 250, "Smoke between", "renders between internal sidebar blocks"),
|
|
||||||
block(input, 650, "Smoke below", "renders below internal sidebar blocks"),
|
|
||||||
]
|
|
||||||
|
|
||||||
const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
|
|
||||||
const route = names(input)
|
|
||||||
api.command.register(() => [
|
|
||||||
{
|
|
||||||
title: `${input.label} modal`,
|
|
||||||
value: "plugin.smoke.modal",
|
|
||||||
keybind: keys.get("modal"),
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
api.route.navigate(route.modal, { source: "command" })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} screen`,
|
|
||||||
value: "plugin.smoke.screen",
|
|
||||||
keybind: keys.get("screen"),
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-screen",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} alert dialog`,
|
|
||||||
value: "plugin.smoke.alert",
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-alert",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
warn(api, route, current(api, route))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} confirm dialog`,
|
|
||||||
value: "plugin.smoke.confirm",
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-confirm",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
check(api, route, current(api, route))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} prompt dialog`,
|
|
||||||
value: "plugin.smoke.prompt",
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-prompt",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
entry(api, route, current(api, route))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} select dialog`,
|
|
||||||
value: "plugin.smoke.select",
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-select",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
picker(api, route, current(api, route))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} host overlay`,
|
|
||||||
value: "plugin.smoke.host",
|
|
||||||
category: "Plugin",
|
|
||||||
slash: {
|
|
||||||
name: "smoke-host",
|
|
||||||
},
|
|
||||||
onSelect: () => {
|
|
||||||
host(api, input, tone(api))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} go home`,
|
|
||||||
value: "plugin.smoke.home",
|
|
||||||
category: "Plugin",
|
|
||||||
enabled: api.route.current.name !== "home",
|
|
||||||
onSelect: () => {
|
|
||||||
api.route.navigate("home")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: `${input.label} toast`,
|
|
||||||
value: "plugin.smoke.toast",
|
|
||||||
category: "Plugin",
|
|
||||||
onSelect: () => {
|
|
||||||
api.ui.toast({
|
|
||||||
variant: "info",
|
|
||||||
title: "Smoke",
|
|
||||||
message: "Plugin toast works",
|
|
||||||
duration: 2000,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
const tui = async (api: TuiPluginApi, options: Record<string, unknown> | null, meta: TuiPluginMeta) => {
|
|
||||||
if (options?.enabled === false) return
|
|
||||||
|
|
||||||
await api.theme.install("./smoke-theme.json")
|
|
||||||
api.theme.set("smoke-theme")
|
|
||||||
|
|
||||||
const value = cfg(options ?? undefined)
|
|
||||||
const route = names(value)
|
|
||||||
const keys = api.keybind.create(bind, value.keybinds)
|
|
||||||
const fx = new VignetteEffect(value.vignette)
|
|
||||||
const post = fx.apply.bind(fx)
|
|
||||||
api.renderer.addPostProcessFn(post)
|
|
||||||
api.lifecycle.onDispose(() => {
|
|
||||||
api.renderer.removePostProcessFn(post)
|
|
||||||
})
|
|
||||||
|
|
||||||
api.route.register([
|
|
||||||
{
|
|
||||||
name: route.screen,
|
|
||||||
render: ({ params }) => <Screen api={api} input={value} route={route} keys={keys} meta={meta} params={params} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: route.modal,
|
|
||||||
render: ({ params }) => <Modal api={api} input={value} route={route} keys={keys} params={params} />,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
reg(api, value, keys)
|
|
||||||
for (const item of slot(value)) {
|
|
||||||
api.slots.register(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
id: "tui-smoke",
|
|
||||||
tui,
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
smoke-theme.json
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://opencode.ai/tui.json",
|
|
||||||
"plugin": [
|
|
||||||
[
|
|
||||||
"./plugins/tui-smoke.tsx",
|
|
||||||
{
|
|
||||||
"enabled": false,
|
|
||||||
"label": "workspace",
|
|
||||||
"keybinds": {
|
|
||||||
"modal": "ctrl+alt+m",
|
|
||||||
"screen": "ctrl+alt+o",
|
|
||||||
"home": "escape,ctrl+shift+h",
|
|
||||||
"dialog_close": "escape,q"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+1
-1
@@ -137,4 +137,4 @@ OpenCode 内置两种 Agent,可用 `Tab` 键快速切换:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=738j8655-cd59-4633-a30a-1124e0096789&qr_code=true) | [X.com](https://x.com/opencode)
|
**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode)
|
||||||
|
|||||||
+1
-1
@@ -137,4 +137,4 @@ OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=738j8655-cd59-4633-a30a-1124e0096789&qr_code=true) | [X.com](https://x.com/opencode)
|
**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -41,11 +41,9 @@
|
|||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solid-primitives/scroll": "2.1.3",
|
"@solid-primitives/scroll": "2.1.3",
|
||||||
"@solid-primitives/storage": "catalog:",
|
"@solid-primitives/storage": "catalog:",
|
||||||
"@solid-primitives/timer": "1.4.4",
|
|
||||||
"@solid-primitives/websocket": "1.3.1",
|
"@solid-primitives/websocket": "1.3.1",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@tanstack/solid-query": "5.91.4",
|
|
||||||
"@thisbeyond/solid-dnd": "0.7.5",
|
"@thisbeyond/solid-dnd": "0.7.5",
|
||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -79,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -113,7 +111,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -130,7 +128,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "catalog:",
|
"@cloudflare/workers-types": "catalog:",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "1.3.0",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"drizzle-kit": "catalog:",
|
"drizzle-kit": "catalog:",
|
||||||
@@ -140,11 +138,11 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.64",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
"@ai-sdk/openai-compatible": "2.0.37",
|
"@ai-sdk/openai-compatible": "1.0.1",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
@@ -164,7 +162,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -188,7 +186,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -221,7 +219,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop-electron": {
|
"packages/desktop-electron": {
|
||||||
"name": "@opencode-ai/desktop-electron",
|
"name": "@opencode-ai/desktop-electron",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -252,7 +250,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -281,7 +279,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -297,7 +295,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -305,31 +303,31 @@
|
|||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.14.1",
|
"@agentclientprotocol/sdk": "0.14.1",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.83",
|
"@ai-sdk/amazon-bedrock": "3.0.82",
|
||||||
"@ai-sdk/anthropic": "3.0.64",
|
"@ai-sdk/anthropic": "2.0.65",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "2.0.91",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "1.0.36",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "2.0.22",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "1.0.36",
|
||||||
"@ai-sdk/gateway": "3.0.80",
|
"@ai-sdk/gateway": "2.0.30",
|
||||||
"@ai-sdk/google": "3.0.53",
|
"@ai-sdk/google": "2.0.54",
|
||||||
"@ai-sdk/google-vertex": "4.0.95",
|
"@ai-sdk/google-vertex": "3.0.106",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "2.0.34",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "2.0.27",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "2.0.89",
|
||||||
"@ai-sdk/openai-compatible": "2.0.37",
|
"@ai-sdk/openai-compatible": "1.0.32",
|
||||||
"@ai-sdk/perplexity": "3.0.26",
|
"@ai-sdk/perplexity": "2.0.23",
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "2.0.1",
|
||||||
"@ai-sdk/provider-utils": "4.0.21",
|
"@ai-sdk/provider-utils": "3.0.21",
|
||||||
"@ai-sdk/togetherai": "2.0.41",
|
"@ai-sdk/togetherai": "1.0.34",
|
||||||
"@ai-sdk/vercel": "2.0.39",
|
"@ai-sdk/vercel": "1.0.33",
|
||||||
"@ai-sdk/xai": "3.0.74",
|
"@ai-sdk/xai": "2.0.51",
|
||||||
"@aws-sdk/credential-providers": "3.993.0",
|
"@aws-sdk/credential-providers": "3.993.0",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
"@modelcontextprotocol/sdk": "1.27.1",
|
"@modelcontextprotocol/sdk": "1.25.2",
|
||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
@@ -337,9 +335,9 @@
|
|||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "2.3.3",
|
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||||
"@opentui/core": "0.1.90",
|
"@opentui/core": "0.1.87",
|
||||||
"@opentui/solid": "0.1.90",
|
"@opentui/solid": "0.1.87",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
@@ -347,7 +345,7 @@
|
|||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
"ai-gateway-provider": "3.1.2",
|
"ai-gateway-provider": "2.3.1",
|
||||||
"bonjour-service": "1.3.0",
|
"bonjour-service": "1.3.0",
|
||||||
"bun-pty": "0.4.8",
|
"bun-pty": "0.4.8",
|
||||||
"chokidar": "4.0.3",
|
"chokidar": "4.0.3",
|
||||||
@@ -358,7 +356,7 @@
|
|||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"gitlab-ai-provider": "6.0.0",
|
"gitlab-ai-provider": "5.2.2",
|
||||||
"glob": "13.0.5",
|
"glob": "13.0.5",
|
||||||
"google-auth-library": "10.5.0",
|
"google-auth-library": "10.5.0",
|
||||||
"gray-matter": "4.0.3",
|
"gray-matter": "4.0.3",
|
||||||
@@ -370,7 +368,6 @@
|
|||||||
"minimatch": "10.0.3",
|
"minimatch": "10.0.3",
|
||||||
"open": "10.1.2",
|
"open": "10.1.2",
|
||||||
"opencode-gitlab-auth": "2.0.0",
|
"opencode-gitlab-auth": "2.0.0",
|
||||||
"opencode-poe-auth": "0.0.1",
|
|
||||||
"opentui-spinner": "0.0.6",
|
"opentui-spinner": "0.0.6",
|
||||||
"partial-json": "0.1.7",
|
"partial-json": "0.1.7",
|
||||||
"remeda": "catalog:",
|
"remeda": "catalog:",
|
||||||
@@ -422,27 +419,17 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opentui/core": "0.1.90",
|
|
||||||
"@opentui/solid": "0.1.90",
|
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"@opentui/core": ">=0.1.90",
|
|
||||||
"@opentui/solid": ">=0.1.90",
|
|
||||||
},
|
|
||||||
"optionalPeers": [
|
|
||||||
"@opentui/core",
|
|
||||||
"@opentui/solid",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"packages/script": {
|
"packages/script": {
|
||||||
"name": "@opencode-ai/script",
|
"name": "@opencode-ai/script",
|
||||||
@@ -456,7 +443,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.90.10",
|
"@hey-api/openapi-ts": "0.90.10",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -467,7 +454,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -502,7 +489,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -526,7 +513,6 @@
|
|||||||
"motion-dom": "12.34.3",
|
"motion-dom": "12.34.3",
|
||||||
"motion-utils": "12.29.2",
|
"motion-utils": "12.29.2",
|
||||||
"remeda": "catalog:",
|
"remeda": "catalog:",
|
||||||
"remend": "catalog:",
|
|
||||||
"shiki": "catalog:",
|
"shiki": "catalog:",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"solid-list": "catalog:",
|
"solid-list": "catalog:",
|
||||||
@@ -549,7 +535,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -560,7 +546,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -599,10 +585,10 @@
|
|||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
],
|
],
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
|
||||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||||
|
"@ai-sdk/xai@2.0.51": "patches/@ai-sdk%2Fxai@2.0.51.patch",
|
||||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||||
"@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch",
|
|
||||||
"@ai-sdk/provider-utils@4.0.21": "patches/@ai-sdk%2Fprovider-utils@4.0.21.patch",
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
@@ -610,7 +596,7 @@
|
|||||||
},
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@effect/platform-node": "4.0.0-beta.37",
|
"@effect/platform-node": "4.0.0-beta.35",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
@@ -624,17 +610,17 @@
|
|||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "4.1.11",
|
||||||
"@tsconfig/bun": "1.0.9",
|
"@tsconfig/bun": "1.0.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@types/bun": "1.3.11",
|
"@types/bun": "1.3.9",
|
||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@types/semver": "7.7.1",
|
"@types/semver": "7.7.1",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
"ai": "6.0.138",
|
"ai": "5.0.124",
|
||||||
"diff": "8.0.2",
|
"diff": "8.0.2",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||||
"effect": "4.0.0-beta.37",
|
"effect": "4.0.0-beta.35",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
@@ -642,7 +628,6 @@
|
|||||||
"marked": "17.0.1",
|
"marked": "17.0.1",
|
||||||
"marked-shiki": "1.2.1",
|
"marked-shiki": "1.2.1",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
"remend": "1.3.0",
|
|
||||||
"shiki": "3.20.0",
|
"shiki": "3.20.0",
|
||||||
"solid-js": "1.9.10",
|
"solid-js": "1.9.10",
|
||||||
"solid-list": "0.3.0",
|
"solid-list": "0.3.0",
|
||||||
@@ -673,51 +658,51 @@
|
|||||||
|
|
||||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.14.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w=="],
|
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.14.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.83", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DoRpvIWGU/r83UeJAM9L93Lca8Kf/yP5fIhfEOltMPGP/PXrGe0BZaz0maLSRn8djJ6+HzWIsgu5ZI6bZqXEXg=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.82", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.65", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yb1EkRCMWex0tnpHPLGQxoJEiJvMGOizuxzlXFOpuGFiYgE679NsWE/F8pHwtoAWsqLlylgGAJvJDIJ8us8LEw=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
|
|
||||||
"@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="],
|
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.91", "", { "dependencies": { "@ai-sdk/openai": "2.0.89", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9tznVSs6LGQNKKxb8pKd7CkBV9yk+a/ENpFicHCj2CmBUKefxzwJ9JbUqrlK3VF6dGZw3LXq0dWxt7/Yekaj1w=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="],
|
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zoJYL33+ieyd86FSP0Whm86D79d1lKPR7wUzh1SZ1oTxwYmsGyvIrmMf2Ll0JA9Ds2Es6qik4VaFCrjwGYRTIQ=="],
|
||||||
|
|
||||||
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="],
|
"@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="],
|
||||||
|
|
||||||
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.24", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-htT1Y7vBN0cRu/1pGnhx6DNH3xaNr0o0MjDkmii48X2+6S/WkOzVNtMjn7V3vLWEQIWNio5vw1hG/F43K8WLHA=="],
|
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.24", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-E+wzGPSa/XHmajO3WtX8mtq0ewy04tsHSpU6/SGwqbiykwWba/emi7ayZ4ir89s5OzbAen2g7T9zZiEchMfkHQ=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="],
|
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LndvRktEgY2IFu4peDJMEXcjhHEEFtM0upLx/J64kCpFHCifalXpK4PPSX3PVndnn0bJzvamO5+fc0z2ooqBZw=="],
|
||||||
|
|
||||||
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.24", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4vOEekW4TAYVHN0qgiwoUOQZhguGwZBiEw8LDeUmpWBm07QkLRAtxYCaSoMiA4hZZojao5mj6NRGEBW1CnDPtg=="],
|
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.35", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Qvh2yxL5zJS9RO/Bf12pyYBIDmn+9GR1hT6e28IYWQWnt2Xq0h9XGps6XagLAv3VYYFg8c/ozkWVd4kXLZ25HA=="],
|
||||||
|
|
||||||
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.24", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K+1YprVMO8R6vTcNhqTqUWhOzX5V/hEY0pFx9KQL0/+MJjOgRi6DcOLoNBd7ONcjxYTyiFLRfk/0a/pHTtSgFA=="],
|
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.24", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ee2At5jgV+SqC6nrtPq20iH7N/aN+O36LrA4gkzVM4cmhM7bvQKVkOXhC1XxG+wsYG6UZi3Nekoi8MEjNWuRrw=="],
|
||||||
|
|
||||||
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.40", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.35", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ARjygiBQtVSgNBp3Sag+Bkwn68ub+cZPC05UpRGG+VY8/Q896K2yU1j4I0+S1eU0BQW/9DKbRG04d9Ayi2DUmA=="],
|
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.35", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.34", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-inUq29XvSVDer6JIeOkwAmCFxOtHPU0OZEhwaWoe3PI59naHIW4RIFA9wppLLV5fJI9WQcAfDKy0ZHW9nV3UJw=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.80", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uM7kpZB5l977lW7+2X1+klBUxIZQ78+1a9jHlaHFEzcOcmmslTl3sdP0QqfuuBcO0YBM2gwOiqVdp8i4TRQYcw=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5Nrkj8B4MzkkOfjjA+Cs5pamkbkK4lI11bx80QV7TFcen/hWA8wEC+UVzwuM5H2zpekoNMjvl6GonHnR62XIZw=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw=="],
|
"@ai-sdk/google": ["@ai-sdk/google@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VKguP0x/PUYpdQyuA/uy5pDGJy6reL0X/yDKxHfL207aCUXpFIBmyMhVs4US39dkEVhtmIFSwXauY0Pt170JRw=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.95", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/google": "3.0.53", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-xL44fHlTtDM7RLkMTgyqMfkfthA38JS91bbMaHItObIhte1PAIY936ZV1PLl/Z9A/oBAXjHWbXo5xDoHzB7LEg=="],
|
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.106", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.65", "@ai-sdk/google": "2.0.54", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-f9sA66bmhgJoTwa+pHWFSdYxPa0lgdQ/MgYNxZptzVyGptoziTf1a9EIXEL3jiCD0qIBAg+IhDAaYalbvZaDqQ=="],
|
||||||
|
|
||||||
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wfCYkVgmVjxNA32T57KbLabVnv9aFUflJ4urJ7eWgTwbnmGQHElCTu+rJ3ydxkXSqxOkXPwMOttDm7XNrvPjmg=="],
|
||||||
|
|
||||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZXe7nZQgliDdjz5ufH5RKpHWxbN72AzmzzKGbF/z+0K9GN5tUCnftrQRvTRFHA5jAzTapcm2BEevmGLVbMkW+A=="],
|
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gaptHgaXjMw3+eA0Q4FABcsj5nQNP6EpFaGUR+Pj5WJy7Kn6mApl975/x57224MfeJIShNpt8wFKK3tvh5ewKg=="],
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="],
|
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-luHVcU+yKzwv3ekKgbP3v+elUVxb2Rt+8c6w9qi7g2NYG2/pEL21oIrnaEnc6UtTZLLZX9EFBcpq2N1FQKDIMw=="],
|
||||||
|
|
||||||
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dXzrVsLR5f6tr+U04jq4AXoRroGFBTvODnLgss0SWbzNjGGQg3XqtQ9j7rCLo6o8qbYGuAHvqUrIpUCuiscuFg=="],
|
"@ai-sdk/perplexity": ["@ai-sdk/perplexity@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aiaRvnc6mhQZKhTTSXPCjPH8Iqr5D/PfCN1hgVP/3RGTBbJtsd9HemIBSABeSdAKbsMH/PwJxgnqH75HEamcBA=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
|
||||||
|
|
||||||
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-k3p9e3k0/gpDDyTtvafsK4HYR4D/aUQW/kzCwWo1+CzdBU84i4L14gWISC/mv6tgSicMXHcEUd521fPufQwNlg=="],
|
"@ai-sdk/togetherai": ["@ai-sdk/togetherai@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jjJmJms6kdEc4nC3MDGFJfhV8F1ifY4nolV2dbnT7BM4ab+Wkskc0GwCsJ7G7WdRMk7xDbFh4he3DPL8KJ/cyA=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="],
|
"@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Qwjm+HdwKasu7L9bDUryBMGKDMscIEzMUkjw/33uGdJpktzyNW13YaNIObOZ2HkskqDMIQJSd4Ao2BBT8fEYLw=="],
|
||||||
|
|
||||||
"@ai-sdk/xai": ["@ai-sdk/xai@3.0.74", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HDDLsT+QrzE3c2QZLRV/HKAwMtXDb0PMDdk1PYUXLJ3r9Qv76zGKGyvJLX7Pu6c8TOHD1mwLrOVYrsTpC/eTMw=="],
|
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="],
|
||||||
|
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
@@ -993,9 +978,9 @@
|
|||||||
|
|
||||||
"@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="],
|
"@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="],
|
||||||
|
|
||||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.37", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.37", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.37", "ioredis": "^5.7.0" } }, "sha512-dCfTNYGAT+1K+nu/0jw3FL/0DJXcobZCJs9SD5XJbj1DewWPhR9/AptP6zLGj8vdP8hXem6Aa53nze3HSujW3w=="],
|
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.35", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.35", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.35", "ioredis": "^5.7.0" } }, "sha512-HPc2xZASl9F9y/xJ01bQgFD6Jf9XP4Fcv/BlVTvG0Yr/uN63lwKZYr/VXor5K5krHfBDeCBD8y7/SICPYZoq3A=="],
|
||||||
|
|
||||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.40", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.40" } }, "sha512-WMRVG7T8ZDALKCOacsx2ZZj3Ccaoq8YGeD9q7ZL4q8RwQv8Nmrl+4+KZl95/zHCqXzgK9oUJOlBfQ7CZr6PQOQ=="],
|
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.35", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.35" } }, "sha512-9bPqNV988itKJ7MQoJuzmR014DB9EZRDOnhJt/+iJlb8qLoR9HnCzNJb9gfBdYhFmVYc8DMsQxG81rdJzpv9tg=="],
|
||||||
|
|
||||||
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
||||||
|
|
||||||
@@ -1337,7 +1322,7 @@
|
|||||||
|
|
||||||
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
|
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
|
||||||
|
|
||||||
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
||||||
|
|
||||||
@@ -1455,25 +1440,27 @@
|
|||||||
|
|
||||||
"@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"],
|
"@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"],
|
||||||
|
|
||||||
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.3.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-4fVteGkVedc7fGoA9+qJs4tpYwALezMq14m2Sjub3KmyRlksCbK+WJf67NPdGem8+NZrV2tAN42A1NU3+SiV3w=="],
|
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.5.4", "", { "dependencies": { "@openrouter/sdk": "^0.1.27" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw=="],
|
||||||
|
|
||||||
|
"@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="],
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.90", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.90", "@opentui/core-darwin-x64": "0.1.90", "@opentui/core-linux-arm64": "0.1.90", "@opentui/core-linux-x64": "0.1.90", "@opentui/core-win32-arm64": "0.1.90", "@opentui/core-win32-x64": "0.1.90", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-Os2dviqWVETU3kaK36lbSvdcI93GAWhw0xb9ng/d0DWYuM9scRmAhLHiOayp61saWv/BR8OJXeuQYHvrp5rd6A=="],
|
"@opentui/core": ["@opentui/core@0.1.87", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.87", "@opentui/core-darwin-x64": "0.1.87", "@opentui/core-linux-arm64": "0.1.87", "@opentui/core-linux-x64": "0.1.87", "@opentui/core-win32-arm64": "0.1.87", "@opentui/core-win32-x64": "0.1.87", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dhsmMv0IqKftwG7J/pBrLBj2armsYIg5R3LBvciRQI/6X89GufP4l1u0+QTACAx6iR4SYJJNVNQ2tdX8LM9rMw=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.90", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XFrm2zCg1SlHPQ5A2HX/I4dCrmTjYaCJIIpo3QuPIvZBGH3aBMdWDJh2tXw7AB5Mmh8X1K4hDkP5nlK9x0Ewow=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.87", "", { "os": "darwin", "cpu": "arm64" }, "sha512-G8oq85diOfkU6n0T1CxCle7oDmpKxwhcdhZ9khBMU5IrfLx9ZDuCM3F6MsiRQWdvPPCq2oomNbd64bYkPamYgw=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.90", "", { "os": "darwin", "cpu": "x64" }, "sha512-vbDpUsnlZ+0CeVKyBBXE+l2+X1XoVncMxMOhXTiMtud2/Cwu+Vfs/g3LC/6Zv08yaytA+9g7Z8sdf0QCqFyQ4w=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.87", "", { "os": "darwin", "cpu": "x64" }, "sha512-MYTFQfOHm6qO7YaY4GHK9u/oJlXY6djaaxl5I+k4p2mk3vvuFIl/AP1ypITwBFjyV5gyp7PRWFp4nGfY9oN8bw=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.90", "", { "os": "linux", "cpu": "arm64" }, "sha512-OTbvBTP5mVQ4uwKyuz6b59ElG+D0i1Ln+q6cVhNkLgeRLySIn1uXEzUFQGlnVgb8lFDANsn3yQmdv+R+Cpw0og=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.87", "", { "os": "linux", "cpu": "arm64" }, "sha512-he8o1h5M6oskRJ7wE+xKJgmWnv5ZwN6gB3M/Z+SeHtOMPa5cZmi3TefTjG54llEgFfx0F9RcqHof7TJ/GNxRkw=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.90", "", { "os": "linux", "cpu": "x64" }, "sha512-2PJi/LLlO7tGk9Ful/n+6iBdg1RFrA9ibU7wVneE6Z1P0LCYeu7bpwMzea1TXL0eAQWPHsjTs9aPlqPxln0EJw=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.87", "", { "os": "linux", "cpu": "x64" }, "sha512-aiUwjPlH4yDcB8/6YDKSmMkaoGAAltL0Xo0AzXyAtJXWK5tkCSaYjEVwzJ/rYRkr4Magnad+Mjth4AQUWdR2AA=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.90", "", { "os": "win32", "cpu": "arm64" }, "sha512-+sTRaOb7gCMZ6iLuuG4y9kzyweJzBDcIJN0Xh49ikFWTwVECDXEVtXahNGlw57avm2yYUoNzmpBjK/LV7zBj9A=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.87", "", { "os": "win32", "cpu": "arm64" }, "sha512-cmP0pOyREjWGniHqbDmaMY7U+1AyagrD8VseJbU0cGpNgVpG2/gbrJUGdfdLB0SNb+mzLdx6SOjdxtrElwRCQA=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.90", "", { "os": "win32", "cpu": "x64" }, "sha512-aVFyErckWp4oW9NJ/ZDKBUAlTlfVUiRXGP63JXFOoeqI7EYaM8uBt6rgZAJuUdFWCN2Q66WRS8Y2mk+0BJwVBg=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.87", "", { "os": "win32", "cpu": "x64" }, "sha512-N2GErAAP8iODf2RPp86pilPaVKiD6G4pkpZL5nLGbKsl0bndrVTpSqZcn8+/nQwFZDPD/AsiRTYNOfWOblhzOw=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.90", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.90", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-zEHDpJOTGS707ts5j4diqoWuFLSqV6yARKl1H0FJkwWOotu+rxCyksL+C0gX0jJUonAw2cjlZ2NNtZY8g78zkg=="],
|
"@opentui/solid": ["@opentui/solid@0.1.87", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.87", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-lRT9t30l8+FtgOjjWJcdb2MT6hP8/RKqwGgYwTI7fXrOqdhxxwdP2SM+rH2l3suHeASheiTdlvPAo230iUcsvg=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
@@ -1901,8 +1888,6 @@
|
|||||||
|
|
||||||
"@solid-primitives/storage": ["@solid-primitives/storage@4.3.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="],
|
"@solid-primitives/storage": ["@solid-primitives/storage@4.3.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="],
|
||||||
|
|
||||||
"@solid-primitives/timer": ["@solid-primitives/timer@1.4.4", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Ayjyb3+v1hyU92vuLUN0tVHq2mmTCPGxSDLGJMsDydRqx9ZfJIc9xj6cxK4XvdY3pif3ps2mIv52pjgToybEpQ=="],
|
|
||||||
|
|
||||||
"@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="],
|
"@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="],
|
||||||
|
|
||||||
"@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="],
|
"@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="],
|
||||||
@@ -1981,14 +1966,10 @@
|
|||||||
|
|
||||||
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.134.5", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/router-utils": "1.133.19", "babel-dead-code-elimination": "^1.0.10", "pathe": "^2.0.3", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "vite": ">=6.0.0 || >=7.0.0" } }, "sha512-J3oawV8uBRBbPoLgMdyHt+LxzTNuWRKNJJuCLWsm/yq6v0IQSvIVCgfD2+liIiSnDPxGZ8ExduPXy8IzS70eXw=="],
|
"@tanstack/directive-functions-plugin": ["@tanstack/directive-functions-plugin@1.134.5", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/router-utils": "1.133.19", "babel-dead-code-elimination": "^1.0.10", "pathe": "^2.0.3", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "vite": ">=6.0.0 || >=7.0.0" } }, "sha512-J3oawV8uBRBbPoLgMdyHt+LxzTNuWRKNJJuCLWsm/yq6v0IQSvIVCgfD2+liIiSnDPxGZ8ExduPXy8IzS70eXw=="],
|
||||||
|
|
||||||
"@tanstack/query-core": ["@tanstack/query-core@5.91.2", "", {}, "sha512-Uz2pTgPC1mhqrrSGg18RKCWT/pkduAYtxbcyIyKBhw7dTWjXZIzqmpzO2lBkyWr4hlImQgpu1m1pei3UnkFRWw=="],
|
|
||||||
|
|
||||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.133.19", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-WEp5D2gPxvlLDRXwD/fV7RXjYtqaqJNXKB/L6OyZEbT+9BG/Ib2d7oG9GSUZNNMGPGYAlhBUOi3xutySsk6rxA=="],
|
"@tanstack/router-utils": ["@tanstack/router-utils@1.133.19", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-WEp5D2gPxvlLDRXwD/fV7RXjYtqaqJNXKB/L6OyZEbT+9BG/Ib2d7oG9GSUZNNMGPGYAlhBUOi3xutySsk6rxA=="],
|
||||||
|
|
||||||
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.134.5", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/directive-functions-plugin": "1.134.5", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-2sWxq70T+dOEUlE3sHlXjEPhaFZfdPYlWTSkHchWXrFGw2YOAa+hzD6L9wHMjGDQezYd03ue8tQlHG+9Jzbzgw=="],
|
"@tanstack/server-functions-plugin": ["@tanstack/server-functions-plugin@1.134.5", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/directive-functions-plugin": "1.134.5", "babel-dead-code-elimination": "^1.0.9", "tiny-invariant": "^1.3.3" } }, "sha512-2sWxq70T+dOEUlE3sHlXjEPhaFZfdPYlWTSkHchWXrFGw2YOAa+hzD6L9wHMjGDQezYd03ue8tQlHG+9Jzbzgw=="],
|
||||||
|
|
||||||
"@tanstack/solid-query": ["@tanstack/solid-query@5.91.4", "", { "dependencies": { "@tanstack/query-core": "5.91.2" }, "peerDependencies": { "solid-js": "^1.6.0" } }, "sha512-oCEgn8iT7WnF/7ISd7usBpUK1C9EdvQfg8ZUpKNKZ4edVClICZrCX6f3/Bp8ZlwQnL21KLc2rp+CejEuehlRxg=="],
|
|
||||||
|
|
||||||
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="],
|
"@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="],
|
||||||
@@ -2071,7 +2052,7 @@
|
|||||||
|
|
||||||
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||||
|
|
||||||
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
||||||
|
|
||||||
@@ -2269,9 +2250,9 @@
|
|||||||
|
|
||||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||||
|
|
||||||
"ai": ["ai@6.0.138", "", { "dependencies": { "@ai-sdk/gateway": "3.0.80", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-49OfPe0f5uxJ6jUdA5BBXjIinP6+ZdYfAtpF2aEH64GA5wPcxH2rf/TBUQQ0bbamBz/D+TLMV18xilZqOC+zaA=="],
|
"ai": ["ai@5.0.124", "", { "dependencies": { "@ai-sdk/gateway": "2.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Li6Jw9F9qsvFJXZPBfxj38ddP2iURCnMs96f9Q3OeQzrDVcl1hvtwSEAuxA/qmfh6SDV2ERqFUOFzigvr0697g=="],
|
||||||
|
|
||||||
"ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
|
"ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="],
|
||||||
|
|
||||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||||
|
|
||||||
@@ -2467,7 +2448,7 @@
|
|||||||
|
|
||||||
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||||
|
|
||||||
"bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
"bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
||||||
|
|
||||||
@@ -2769,7 +2750,7 @@
|
|||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
"effect": ["effect@4.0.0-beta.37", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-AVMXXtb6n62W4uvo1EvT7FJ41HfDvQRX8IY2FGPvfP361dtBArKK2JtE5vmFXTsxkW90WUdvJZYpVATGIzr/BA=="],
|
"effect": ["effect@4.0.0-beta.35", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-64j8dgJmoEMeq6Y3WLYcZIRqPZ5E/lqnULCf6QW5te3hQ/sa13UodWLGwBEviEqBoq72U8lArhVX+T7ntzhJGQ=="],
|
||||||
|
|
||||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||||
|
|
||||||
@@ -2899,7 +2880,7 @@
|
|||||||
|
|
||||||
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
|
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
|
||||||
|
|
||||||
"express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="],
|
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
|
||||||
|
|
||||||
"expressive-code": ["expressive-code@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "@expressive-code/plugin-frames": "^0.41.7", "@expressive-code/plugin-shiki": "^0.41.7", "@expressive-code/plugin-text-markers": "^0.41.7" } }, "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA=="],
|
"expressive-code": ["expressive-code@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "@expressive-code/plugin-frames": "^0.41.7", "@expressive-code/plugin-shiki": "^0.41.7", "@expressive-code/plugin-text-markers": "^0.41.7" } }, "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA=="],
|
||||||
|
|
||||||
@@ -3047,7 +3028,7 @@
|
|||||||
|
|
||||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||||
|
|
||||||
"gitlab-ai-provider": ["gitlab-ai-provider@6.0.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-683GcJdrer/GhnljkbVcGsndCEhvGB8f9fUdCxQBlkuyt8rzf0G9DpSh+iMBYp9HpcSvYmYG0Qv5ks9dLrNxwQ=="],
|
"gitlab-ai-provider": ["gitlab-ai-provider@5.2.2", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-ejwnie62rimfVHbjYZ2tsnqwLjF9YLgXD3OQA458gHz8hUvw7vEnhuyuMv5PmWQtyS3ISAghiX7r5SBhUWeCTA=="],
|
||||||
|
|
||||||
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
|
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
|
||||||
|
|
||||||
@@ -3803,8 +3784,6 @@
|
|||||||
|
|
||||||
"opencode-gitlab-auth": ["opencode-gitlab-auth@2.0.0", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-jmZOOvYIurRScQCtdBqIW5HbP1JbmIiq7UtI7NGgn2vjke46g9d4NVPBg5/ZmFFVIBwZcgyFgJ7b8kGEOR9ujA=="],
|
"opencode-gitlab-auth": ["opencode-gitlab-auth@2.0.0", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-jmZOOvYIurRScQCtdBqIW5HbP1JbmIiq7UtI7NGgn2vjke46g9d4NVPBg5/ZmFFVIBwZcgyFgJ7b8kGEOR9ujA=="],
|
||||||
|
|
||||||
"opencode-poe-auth": ["opencode-poe-auth@0.0.1", "", { "dependencies": { "open": "^10.0.0", "poe-oauth": "*" }, "peerDependencies": { "@opencode-ai/plugin": "*" } }, "sha512-cXqTlS6AXHzo1oBdosnxbT47ZJEZ9WXn050X8Re6wZ1vaNnTpB/l2fMQt90evT7RBK0fB8UjXQUDMKyd7bbiqg=="],
|
|
||||||
|
|
||||||
"opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="],
|
"opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="],
|
||||||
|
|
||||||
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
||||||
@@ -3845,7 +3824,7 @@
|
|||||||
|
|
||||||
"pagefind": ["pagefind@1.4.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="],
|
"pagefind": ["pagefind@1.4.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="],
|
||||||
|
|
||||||
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||||
|
|
||||||
"param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
|
"param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
|
||||||
|
|
||||||
@@ -3937,8 +3916,6 @@
|
|||||||
|
|
||||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||||
|
|
||||||
"poe-oauth": ["poe-oauth@0.0.3", "", {}, "sha512-KgxDylcuq/mov8URSplrBGjrIjkQwjN/Ml8BhqaGsAvHzYN3yhuROdv1sDRfwqncg7TT8XzJvMeJAWmv/4NDLw=="],
|
|
||||||
|
|
||||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||||
@@ -4117,8 +4094,6 @@
|
|||||||
|
|
||||||
"remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="],
|
"remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="],
|
||||||
|
|
||||||
"remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="],
|
|
||||||
|
|
||||||
"request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="],
|
"request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="],
|
||||||
|
|
||||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
@@ -4797,21 +4772,63 @@
|
|||||||
|
|
||||||
"@actions/http-client/undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
|
"@actions/http-client/undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
"@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
||||||
|
|
||||||
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
"@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-g3wA57IAQFb+3j4YuFndgkUdXyRETZVvbfAWM+UX7bZSxA3xjes0v3XKgIdKdekPtDGsh4ZX2byHD0gJIMPfiA=="],
|
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
|
||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
|
"@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AnGoxVNZ/E3EU4lW12rrufI6riqL2cEv4jk3OrjJ/i54XwR0CJU1V26jXAwxb+Pc+uZmYG++HM+gzXxPQZkMNQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="],
|
||||||
|
|
||||||
|
"@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
"@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
"@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||||
|
|
||||||
@@ -5099,8 +5116,6 @@
|
|||||||
|
|
||||||
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="],
|
"@modelcontextprotocol/sdk/jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||||
@@ -5183,6 +5198,8 @@
|
|||||||
|
|
||||||
"@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
"@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||||
|
|
||||||
|
"@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.9", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.8" }, "optionalPeers": ["solid-js"] }, "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw=="],
|
||||||
|
|
||||||
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
|
||||||
|
|
||||||
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
||||||
@@ -5285,6 +5302,16 @@
|
|||||||
|
|
||||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AnGoxVNZ/E3EU4lW12rrufI6riqL2cEv4jk3OrjJ/i54XwR0CJU1V26jXAwxb+Pc+uZmYG++HM+gzXxPQZkMNQ=="],
|
||||||
|
|
||||||
"ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
"ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||||
|
|
||||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
@@ -5503,9 +5530,13 @@
|
|||||||
|
|
||||||
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||||
|
|
||||||
"opencode-gitlab-auth/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=="],
|
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="],
|
||||||
|
|
||||||
"opencode-poe-auth/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=="],
|
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="],
|
||||||
|
|
||||||
|
"opencode-gitlab-auth/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=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||||
|
|
||||||
@@ -5627,12 +5658,12 @@
|
|||||||
|
|
||||||
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
|
||||||
|
|
||||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
|
|
||||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
"vitest/@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
|
"vitest/@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
|
||||||
@@ -5679,6 +5710,16 @@
|
|||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
@@ -5687,6 +5728,28 @@
|
|||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/gateway/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/openai/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||||
|
|
||||||
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
@@ -6119,6 +6182,20 @@
|
|||||||
|
|
||||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.46", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="],
|
||||||
|
|
||||||
|
"ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||||
|
|
||||||
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
@@ -6213,12 +6290,12 @@
|
|||||||
|
|
||||||
"opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
"opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||||
|
|
||||||
"opencode-poe-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
|
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
"opencontrol/@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||||
@@ -6471,6 +6548,12 @@
|
|||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
||||||
@@ -6523,6 +6606,10 @@
|
|||||||
|
|
||||||
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773909469,
|
"lastModified": 1772091128,
|
||||||
"narHash": "sha256-vglVrLfHjFIzIdV9A27Ugul6rh3I1qHbbitGW7dk420=",
|
"narHash": "sha256-TnrYykX8Mf/Ugtkix6V+PjW7miU2yClA6uqWl/v6KWM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "7149c06513f335be57f26fcbbbe34afda923882b",
|
"rev": "3f0336406035444b4a24b942788334af5f906259",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ async function subscribeSessionEvents() {
|
|||||||
|
|
||||||
const TOOL: Record<string, [string, string]> = {
|
const TOOL: Record<string, [string, string]> = {
|
||||||
todowrite: ["Todo", "\x1b[33m\x1b[1m"],
|
todowrite: ["Todo", "\x1b[33m\x1b[1m"],
|
||||||
|
todoread: ["Todo", "\x1b[33m\x1b[1m"],
|
||||||
bash: ["Bash", "\x1b[31m\x1b[1m"],
|
bash: ["Bash", "\x1b[31m\x1b[1m"],
|
||||||
edit: ["Edit", "\x1b[32m\x1b[1m"],
|
edit: ["Edit", "\x1b[32m\x1b[1m"],
|
||||||
glob: ["Glob", "\x1b[34m\x1b[1m"],
|
glob: ["Glob", "\x1b[34m\x1b[1m"],
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-4XhUHjgqinKxOeT8K5hGAjpFA2vzOp8QpEg0uYCZwvg=",
|
"x86_64-linux": "sha256-P0RJfQF8APTYVGP6hLJRrOkRSl5nVDNxdcGcZECPPJE=",
|
||||||
"aarch64-linux": "sha256-X2YTNOpJocIkWkkfS8RnuDW+tvj4riHs7CXM+cS9iv0=",
|
"aarch64-linux": "sha256-ZtMjTcd35X3JhJIdn3DilFsp7i/IZIcNaKZFnSzW/nk=",
|
||||||
"aarch64-darwin": "sha256-pN0rY+cpdW+6gNWeegVprdmhc2H72OZ9WxKDIs1fvJM=",
|
"aarch64-darwin": "sha256-Uw/okFDRxxKQMfEsj8MXuHyhpugxZGgIKtu89Getlz8=",
|
||||||
"x86_64-darwin": "sha256-l8+Yz/6UfSPJrdgfcqy/L2SvxN2i9Apv2R0B61rpEmw="
|
"x86_64-darwin": "sha256-ZySIgT1HbWZWnaQ0W0eURKC43BTupRmmply92JDFPWA="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -4,7 +4,7 @@
|
|||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.11",
|
"packageManager": "bun@1.3.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"dev:desktop": "bun --cwd packages/desktop tauri dev",
|
"dev:desktop": "bun --cwd packages/desktop tauri dev",
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@effect/platform-node": "4.0.0-beta.37",
|
"@effect/platform-node": "4.0.0-beta.35",
|
||||||
"@types/bun": "1.3.11",
|
"@types/bun": "1.3.9",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
@@ -45,15 +45,14 @@
|
|||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||||
"effect": "4.0.0-beta.37",
|
"effect": "4.0.0-beta.35",
|
||||||
"ai": "6.0.138",
|
"ai": "5.0.124",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
"marked": "17.0.1",
|
"marked": "17.0.1",
|
||||||
"marked-shiki": "1.2.1",
|
"marked-shiki": "1.2.1",
|
||||||
"remend": "1.3.0",
|
|
||||||
"@playwright/test": "1.51.0",
|
"@playwright/test": "1.51.0",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
@@ -113,8 +112,8 @@
|
|||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
|
||||||
"@ai-sdk/provider-utils@4.0.21": "patches/@ai-sdk%2Fprovider-utils@4.0.21.patch",
|
"@ai-sdk/xai@2.0.51": "patches/@ai-sdk%2Fxai@2.0.51.patch",
|
||||||
"@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch"
|
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,9 +175,9 @@ export async function runTerminal(page: Page, input: { cmd: string; token: strin
|
|||||||
await expect.poll(() => terminalHas(page, { term, token: input.token }), { timeout }).toBe(true)
|
await expect.poll(() => terminalHas(page, { term, token: input.token }), { timeout }).toBe(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openPalette(page: Page, key = "K") {
|
export async function openPalette(page: Page) {
|
||||||
await defocus(page)
|
await defocus(page)
|
||||||
await page.keyboard.press(`${modKey}+${key}`)
|
await page.keyboard.press(`${modKey}+P`)
|
||||||
|
|
||||||
const dialog = page.getByRole("dialog")
|
const dialog = page.getByRole("dialog")
|
||||||
await expect(dialog).toBeVisible()
|
await expect(dialog).toBeVisible()
|
||||||
@@ -465,13 +465,10 @@ export async function waitSession(page: Page, input: { directory: string; sessio
|
|||||||
if (!slug) return false
|
if (!slug) return false
|
||||||
const resolved = await resolveSlug(slug).catch(() => undefined)
|
const resolved = await resolveSlug(slug).catch(() => undefined)
|
||||||
if (!resolved || resolved.directory !== target) return false
|
if (!resolved || resolved.directory !== target) return false
|
||||||
const current = sessionIDFromUrl(page.url())
|
if (input.sessionID && sessionIDFromUrl(page.url()) !== input.sessionID) return false
|
||||||
if (input.sessionID && current !== input.sessionID) return false
|
|
||||||
if (!input.sessionID && current) return false
|
|
||||||
|
|
||||||
const state = await probeSession(page)
|
const state = await probeSession(page)
|
||||||
if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false
|
if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false
|
||||||
if (!input.sessionID && state?.sessionID) return false
|
|
||||||
if (state?.dir) {
|
if (state?.dir) {
|
||||||
const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "")
|
const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "")
|
||||||
if (dir !== target) return false
|
if (dir !== target) return false
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { closeDialog, openPalette } from "../actions"
|
import { openPalette } from "../actions"
|
||||||
|
|
||||||
test("search palette opens and closes", async ({ page, gotoSession }) => {
|
test("search palette opens and closes", async ({ page, gotoSession }) => {
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
@@ -9,12 +9,3 @@ test("search palette opens and closes", async ({ page, gotoSession }) => {
|
|||||||
await page.keyboard.press("Escape")
|
await page.keyboard.press("Escape")
|
||||||
await expect(dialog).toHaveCount(0)
|
await expect(dialog).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("search palette also opens with cmd+p", async ({ page, gotoSession }) => {
|
|
||||||
await gotoSession()
|
|
||||||
|
|
||||||
const dialog = await openPalette(page, "P")
|
|
||||||
|
|
||||||
await closeDialog(page, dialog)
|
|
||||||
await expect(dialog).toHaveCount(0)
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -108,10 +108,7 @@ test("prompt history restores unsent draft with arrow navigation", async ({ page
|
|||||||
await page.keyboard.type(draft)
|
await page.keyboard.type(draft)
|
||||||
await wait(page, draft)
|
await wait(page, draft)
|
||||||
|
|
||||||
// Clear the draft before navigating history (ArrowUp only works when prompt is empty)
|
await edge(page, "start")
|
||||||
await prompt.fill("")
|
|
||||||
await wait(page, "")
|
|
||||||
|
|
||||||
await page.keyboard.press("ArrowUp")
|
await page.keyboard.press("ArrowUp")
|
||||||
await wait(page, second)
|
await wait(page, second)
|
||||||
|
|
||||||
@@ -122,7 +119,7 @@ test("prompt history restores unsent draft with arrow navigation", async ({ page
|
|||||||
await wait(page, second)
|
await wait(page, second)
|
||||||
|
|
||||||
await page.keyboard.press("ArrowDown")
|
await page.keyboard.press("ArrowDown")
|
||||||
await wait(page, "")
|
await wait(page, draft)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export const promptVariantSelector = '[data-component="prompt-variant-control"]'
|
|||||||
export const settingsLanguageSelectSelector = '[data-action="settings-language"]'
|
export const settingsLanguageSelectSelector = '[data-action="settings-language"]'
|
||||||
export const settingsColorSchemeSelector = '[data-action="settings-color-scheme"]'
|
export const settingsColorSchemeSelector = '[data-action="settings-color-scheme"]'
|
||||||
export const settingsThemeSelector = '[data-action="settings-theme"]'
|
export const settingsThemeSelector = '[data-action="settings-theme"]'
|
||||||
export const settingsCodeFontSelector = '[data-action="settings-code-font"]'
|
export const settingsFontSelector = '[data-action="settings-font"]'
|
||||||
export const settingsUIFontSelector = '[data-action="settings-ui-font"]'
|
|
||||||
export const settingsNotificationsAgentSelector = '[data-action="settings-notifications-agent"]'
|
export const settingsNotificationsAgentSelector = '[data-action="settings-notifications-agent"]'
|
||||||
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
|
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
|
||||||
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
|
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ async function todoDock(page: any, sessionID: string) {
|
|||||||
|
|
||||||
const write = async (driver: ComposerDriverState | undefined) => {
|
const write = async (driver: ComposerDriverState | undefined) => {
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
(input: { event: string; sessionID: string; driver: ComposerDriverState | undefined }) => {
|
(input) => {
|
||||||
const win = window as ComposerWindow
|
const win = window as ComposerWindow
|
||||||
const composer = win.__opencode_e2e?.composer
|
const composer = win.__opencode_e2e?.composer
|
||||||
if (!composer?.enabled) throw new Error("Composer e2e driver is not enabled")
|
if (!composer?.enabled) throw new Error("Composer e2e driver is not enabled")
|
||||||
@@ -118,7 +118,7 @@ async function todoDock(page: any, sessionID: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const read = () =>
|
const read = () =>
|
||||||
page.evaluate((sessionID: string) => {
|
page.evaluate((sessionID) => {
|
||||||
const win = window as ComposerWindow
|
const win = window as ComposerWindow
|
||||||
return win.__opencode_e2e?.composer?.sessions?.[sessionID]?.probe ?? null
|
return win.__opencode_e2e?.composer?.sessions?.[sessionID]?.probe ?? null
|
||||||
}, sessionID) as Promise<ComposerProbeState | null>
|
}, sessionID) as Promise<ComposerProbeState | null>
|
||||||
@@ -186,8 +186,6 @@ async function withMockPermission<T>(
|
|||||||
opts: { child?: any } | undefined,
|
opts: { child?: any } | undefined,
|
||||||
fn: (state: { resolved: () => Promise<void> }) => Promise<T>,
|
fn: (state: { resolved: () => Promise<void> }) => Promise<T>,
|
||||||
) {
|
) {
|
||||||
const listUrl = /\/permission(?:\?.*)?$/
|
|
||||||
const replyUrls = [/\/session\/[^/]+\/permissions\/[^/?]+(?:\?.*)?$/, /\/permission\/[^/]+\/reply(?:\?.*)?$/]
|
|
||||||
let pending = [
|
let pending = [
|
||||||
{
|
{
|
||||||
...request,
|
...request,
|
||||||
@@ -206,8 +204,7 @@ async function withMockPermission<T>(
|
|||||||
|
|
||||||
const reply = async (route: any) => {
|
const reply = async (route: any) => {
|
||||||
const url = new URL(route.request().url())
|
const url = new URL(route.request().url())
|
||||||
const parts = url.pathname.split("/").filter(Boolean)
|
const id = url.pathname.split("/").pop()
|
||||||
const id = parts.at(-1) === "reply" ? parts.at(-2) : parts.at(-1)
|
|
||||||
pending = pending.filter((item) => item.id !== id)
|
pending = pending.filter((item) => item.id !== id)
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -216,10 +213,8 @@ async function withMockPermission<T>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.route(listUrl, list)
|
await page.route("**/permission", list)
|
||||||
for (const item of replyUrls) {
|
await page.route("**/session/*/permissions/*", reply)
|
||||||
await page.route(item, reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionList = opts?.child
|
const sessionList = opts?.child
|
||||||
? async (route: any) => {
|
? async (route: any) => {
|
||||||
@@ -247,10 +242,8 @@ async function withMockPermission<T>(
|
|||||||
try {
|
try {
|
||||||
return await fn(state)
|
return await fn(state)
|
||||||
} finally {
|
} finally {
|
||||||
await page.unroute(listUrl, list)
|
await page.unroute("**/permission", list)
|
||||||
for (const item of replyUrls) {
|
await page.unroute("**/session/*/permissions/*", reply)
|
||||||
await page.unroute(item, reply)
|
|
||||||
}
|
|
||||||
if (sessionList) await page.unroute("**/session?*", sessionList)
|
if (sessionList) await page.unroute("**/session?*", sessionList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,17 +28,7 @@ type Footer = {
|
|||||||
type Probe = {
|
type Probe = {
|
||||||
dir?: string
|
dir?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
agent?: string
|
|
||||||
model?: { providerID: string; modelID: string; name?: string }
|
|
||||||
variant?: string | null
|
|
||||||
pick?: {
|
|
||||||
agent?: string
|
|
||||||
model?: { providerID: string; modelID: string }
|
model?: { providerID: string; modelID: string }
|
||||||
variant?: string | null
|
|
||||||
}
|
|
||||||
variants?: string[]
|
|
||||||
models?: Array<{ providerID: string; modelID: string; name: string }>
|
|
||||||
agents?: Array<{ name: string }>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||||
@@ -60,86 +50,6 @@ async function probe(page: Page): Promise<Probe | null> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function currentModel(page: Page) {
|
|
||||||
await expect.poll(() => probe(page).then(modelKey), { timeout: 30_000 }).not.toBe(null)
|
|
||||||
const value = await probe(page).then(modelKey)
|
|
||||||
if (!value) throw new Error("Failed to resolve current model key")
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
async function waitControl(page: Page, key: "setAgent" | "setModel" | "setVariant") {
|
|
||||||
await expect
|
|
||||||
.poll(
|
|
||||||
() =>
|
|
||||||
page.evaluate((key) => {
|
|
||||||
const win = window as Window & {
|
|
||||||
__opencode_e2e?: {
|
|
||||||
model?: {
|
|
||||||
controls?: Record<string, unknown>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !!win.__opencode_e2e?.model?.controls?.[key]
|
|
||||||
}, key),
|
|
||||||
{ timeout: 30_000 },
|
|
||||||
)
|
|
||||||
.toBe(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pickAgent(page: Page, value: string) {
|
|
||||||
await waitControl(page, "setAgent")
|
|
||||||
await page.evaluate((value) => {
|
|
||||||
const win = window as Window & {
|
|
||||||
__opencode_e2e?: {
|
|
||||||
model?: {
|
|
||||||
controls?: {
|
|
||||||
setAgent?: (value: string | undefined) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fn = win.__opencode_e2e?.model?.controls?.setAgent
|
|
||||||
if (!fn) throw new Error("Model e2e agent control is not enabled")
|
|
||||||
fn(value)
|
|
||||||
}, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pickModel(page: Page, value: { providerID: string; modelID: string }) {
|
|
||||||
await waitControl(page, "setModel")
|
|
||||||
await page.evaluate((value) => {
|
|
||||||
const win = window as Window & {
|
|
||||||
__opencode_e2e?: {
|
|
||||||
model?: {
|
|
||||||
controls?: {
|
|
||||||
setModel?: (value: { providerID: string; modelID: string } | undefined) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fn = win.__opencode_e2e?.model?.controls?.setModel
|
|
||||||
if (!fn) throw new Error("Model e2e model control is not enabled")
|
|
||||||
fn(value)
|
|
||||||
}, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pickVariant(page: Page, value: string) {
|
|
||||||
await waitControl(page, "setVariant")
|
|
||||||
await page.evaluate((value) => {
|
|
||||||
const win = window as Window & {
|
|
||||||
__opencode_e2e?: {
|
|
||||||
model?: {
|
|
||||||
controls?: {
|
|
||||||
setVariant?: (value: string | undefined) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fn = win.__opencode_e2e?.model?.controls?.setVariant
|
|
||||||
if (!fn) throw new Error("Model e2e variant control is not enabled")
|
|
||||||
fn(value)
|
|
||||||
}, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function read(page: Page): Promise<Footer> {
|
async function read(page: Page): Promise<Footer> {
|
||||||
return {
|
return {
|
||||||
agent: await text(page.locator(`${promptAgentSelector} [data-slot="select-select-trigger-value"]`).first()),
|
agent: await text(page.locator(`${promptAgentSelector} [data-slot="select-select-trigger-value"]`).first()),
|
||||||
@@ -172,15 +82,31 @@ async function waitModel(page: Page, value: string) {
|
|||||||
async function choose(page: Page, root: string, value: string) {
|
async function choose(page: Page, root: string, value: string) {
|
||||||
const select = page.locator(root)
|
const select = page.locator(root)
|
||||||
await expect(select).toBeVisible()
|
await expect(select).toBeVisible()
|
||||||
await pickAgent(page, value)
|
await select.locator('[data-action], [data-slot="select-select-trigger"]').first().click()
|
||||||
|
const item = page
|
||||||
|
.locator('[data-slot="select-select-item"]')
|
||||||
|
.filter({ hasText: new RegExp(`^\\s*${escape(value)}\\s*$`) })
|
||||||
|
.first()
|
||||||
|
await expect(item).toBeVisible()
|
||||||
|
await item.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function variantCount(page: Page) {
|
async function variantCount(page: Page) {
|
||||||
return (await probe(page))?.variants?.length ?? 0
|
const select = page.locator(promptVariantSelector)
|
||||||
|
await expect(select).toBeVisible()
|
||||||
|
await select.locator('[data-slot="select-select-trigger"]').click()
|
||||||
|
const count = await page.locator('[data-slot="select-select-item"]').count()
|
||||||
|
await page.keyboard.press("Escape")
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
async function agents(page: Page) {
|
async function agents(page: Page) {
|
||||||
return ((await probe(page))?.agents ?? []).map((item) => item.name).filter(Boolean)
|
const select = page.locator(promptAgentSelector)
|
||||||
|
await expect(select).toBeVisible()
|
||||||
|
await select.locator('[data-action], [data-slot="select-select-trigger"]').first().click()
|
||||||
|
const labels = await page.locator('[data-slot="select-select-item-label"]').allTextContents()
|
||||||
|
await page.keyboard.press("Escape")
|
||||||
|
return labels.map((item) => item.trim()).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureVariant(page: Page, directory: string): Promise<Footer> {
|
async function ensureVariant(page: Page, directory: string): Promise<Footer> {
|
||||||
@@ -206,25 +132,50 @@ async function ensureVariant(page: Page, directory: string): Promise<Footer> {
|
|||||||
|
|
||||||
async function chooseDifferentVariant(page: Page): Promise<Footer> {
|
async function chooseDifferentVariant(page: Page): Promise<Footer> {
|
||||||
const current = await read(page)
|
const current = await read(page)
|
||||||
const next = (await probe(page))?.variants?.find((item) => item !== current.variant)
|
const select = page.locator(promptVariantSelector)
|
||||||
if (!next) throw new Error("Current model has no alternate variant to select")
|
await expect(select).toBeVisible()
|
||||||
|
await select.locator('[data-slot="select-select-trigger"]').click()
|
||||||
|
|
||||||
await pickVariant(page, next)
|
const items = page.locator('[data-slot="select-select-item"]')
|
||||||
|
const count = await items.count()
|
||||||
|
if (count < 2) throw new Error("Current model has no alternate variant to select")
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const item = items.nth(i)
|
||||||
|
const next = await text(item.locator('[data-slot="select-select-item-label"]').first())
|
||||||
|
if (!next || next === current.variant) continue
|
||||||
|
await item.click()
|
||||||
return waitFooter(page, { agent: current.agent, model: current.model, variant: next })
|
return waitFooter(page, { agent: current.agent, model: current.model, variant: next })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chooseOtherModel(page: Page, skip: string[] = []): Promise<Footer> {
|
throw new Error("Failed to choose a different variant")
|
||||||
const current = await currentModel(page)
|
}
|
||||||
const next = (await probe(page))?.models?.find((item) => {
|
|
||||||
const key = `${item.providerID}:${item.modelID}`
|
async function chooseOtherModel(page: Page): Promise<Footer> {
|
||||||
return key !== current && !skip.includes(key)
|
const current = await read(page)
|
||||||
})
|
const button = page.locator(`${promptModelSelector} [data-action="prompt-model"]`)
|
||||||
if (!next) throw new Error("Failed to choose a different model")
|
await expect(button).toBeVisible()
|
||||||
await pickModel(page, { providerID: next.providerID, modelID: next.modelID })
|
await button.click()
|
||||||
await expect.poll(async () => (await read(page)).model, { timeout: 30_000 }).toBe(next.name)
|
|
||||||
|
const dialog = page.getByRole("dialog")
|
||||||
|
await expect(dialog).toBeVisible()
|
||||||
|
const items = dialog.locator('[data-slot="list-item"]')
|
||||||
|
const count = await items.count()
|
||||||
|
expect(count).toBeGreaterThan(1)
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const item = items.nth(i)
|
||||||
|
const selected = (await item.getAttribute("data-selected")) === "true"
|
||||||
|
if (selected) continue
|
||||||
|
await item.click()
|
||||||
|
await expect(dialog).toHaveCount(0)
|
||||||
|
await expect.poll(async () => (await read(page)).model !== current.model, { timeout: 30_000 }).toBe(true)
|
||||||
return read(page)
|
return read(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to choose a different model")
|
||||||
|
}
|
||||||
|
|
||||||
async function goto(page: Page, directory: string, sessionID?: string) {
|
async function goto(page: Page, directory: string, sessionID?: string) {
|
||||||
await page.goto(sessionPath(directory, sessionID))
|
await page.goto(sessionPath(directory, sessionID))
|
||||||
await waitSession(page, { directory, sessionID })
|
await waitSession(page, { directory, sessionID })
|
||||||
@@ -298,14 +249,17 @@ async function newWorkspaceSession(page: Page, slug: string) {
|
|||||||
return waitSession(page, { directory: next.directory }).then((item) => item.directory)
|
return waitSession(page, { directory: next.directory }).then((item) => item.directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("session model restore per session without leaking into new sessions", async ({ page, withProject }) => {
|
test("session model and variant restore per session without leaking into new sessions", async ({
|
||||||
|
page,
|
||||||
|
withProject,
|
||||||
|
}) => {
|
||||||
await page.setViewportSize({ width: 1440, height: 900 })
|
await page.setViewportSize({ width: 1440, height: 900 })
|
||||||
|
|
||||||
await withProject(async ({ directory, gotoSession, trackSession }) => {
|
await withProject(async ({ directory, gotoSession, trackSession }) => {
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
|
|
||||||
const firstState = await chooseOtherModel(page)
|
await ensureVariant(page, directory)
|
||||||
const firstKey = await currentModel(page)
|
const firstState = await chooseDifferentVariant(page)
|
||||||
const first = await submit(page, `session variant ${Date.now()}`)
|
const first = await submit(page, `session variant ${Date.now()}`)
|
||||||
trackSession(first)
|
trackSession(first)
|
||||||
await waitUser(directory, first)
|
await waitUser(directory, first)
|
||||||
@@ -315,10 +269,10 @@ test("session model restore per session without leaking into new sessions", asyn
|
|||||||
await waitFooter(page, firstState)
|
await waitFooter(page, firstState)
|
||||||
|
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
const fresh = await read(page)
|
const fresh = await ensureVariant(page, directory)
|
||||||
expect(fresh.model).not.toBe(firstState.model)
|
expect(fresh.variant).not.toBe(firstState.variant)
|
||||||
|
|
||||||
const secondState = await chooseOtherModel(page, [firstKey])
|
const secondState = await chooseOtherModel(page)
|
||||||
const second = await submit(page, `session model ${Date.now()}`)
|
const second = await submit(page, `session model ${Date.now()}`)
|
||||||
trackSession(second)
|
trackSession(second)
|
||||||
await waitUser(directory, second)
|
await waitUser(directory, second)
|
||||||
@@ -340,8 +294,8 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
|
|||||||
await withProject(async ({ directory: root, slug, gotoSession, trackDirectory, trackSession }) => {
|
await withProject(async ({ directory: root, slug, gotoSession, trackDirectory, trackSession }) => {
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
|
|
||||||
const firstState = await chooseOtherModel(page)
|
await ensureVariant(page, root)
|
||||||
const firstKey = await currentModel(page)
|
const firstState = await chooseDifferentVariant(page)
|
||||||
const first = await submit(page, `root session ${Date.now()}`)
|
const first = await submit(page, `root session ${Date.now()}`)
|
||||||
trackSession(first, root)
|
trackSession(first, root)
|
||||||
await waitUser(root, first)
|
await waitUser(root, first)
|
||||||
@@ -353,8 +307,7 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
|
|||||||
const oneDir = await newWorkspaceSession(page, one.slug)
|
const oneDir = await newWorkspaceSession(page, one.slug)
|
||||||
trackDirectory(oneDir)
|
trackDirectory(oneDir)
|
||||||
|
|
||||||
const secondState = await chooseOtherModel(page, [firstKey])
|
const secondState = await chooseOtherModel(page)
|
||||||
const secondKey = await currentModel(page)
|
|
||||||
const second = await submit(page, `workspace one ${Date.now()}`)
|
const second = await submit(page, `workspace one ${Date.now()}`)
|
||||||
trackSession(second, oneDir)
|
trackSession(second, oneDir)
|
||||||
await waitUser(oneDir, second)
|
await waitUser(oneDir, second)
|
||||||
@@ -363,7 +316,8 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
|
|||||||
const twoDir = await newWorkspaceSession(page, two.slug)
|
const twoDir = await newWorkspaceSession(page, two.slug)
|
||||||
trackDirectory(twoDir)
|
trackDirectory(twoDir)
|
||||||
|
|
||||||
const thirdState = await chooseOtherModel(page, [firstKey, secondKey])
|
await ensureVariant(page, twoDir)
|
||||||
|
const thirdState = await chooseDifferentVariant(page)
|
||||||
const third = await submit(page, `workspace two ${Date.now()}`)
|
const third = await submit(page, `workspace two ${Date.now()}`)
|
||||||
trackSession(third, twoDir)
|
trackSession(third, twoDir)
|
||||||
await waitUser(twoDir, third)
|
await waitUser(twoDir, third)
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ test("changing file open keybind works", async ({ page, gotoSession }) => {
|
|||||||
await expect(keybindButton).toBeVisible()
|
await expect(keybindButton).toBeVisible()
|
||||||
|
|
||||||
const initialKeybind = await keybindButton.textContent()
|
const initialKeybind = await keybindButton.textContent()
|
||||||
expect(initialKeybind).toContain("K")
|
expect(initialKeybind).toContain("P")
|
||||||
|
|
||||||
await keybindButton.click()
|
await keybindButton.click()
|
||||||
await expect(keybindButton).toHaveText(/press/i)
|
await expect(keybindButton).toHaveText(/press/i)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { test, expect, settingsKey } from "../fixtures"
|
|||||||
import { closeDialog, openSettings } from "../actions"
|
import { closeDialog, openSettings } from "../actions"
|
||||||
import {
|
import {
|
||||||
settingsColorSchemeSelector,
|
settingsColorSchemeSelector,
|
||||||
settingsCodeFontSelector,
|
settingsFontSelector,
|
||||||
settingsLanguageSelectSelector,
|
settingsLanguageSelectSelector,
|
||||||
settingsNotificationsAgentSelector,
|
settingsNotificationsAgentSelector,
|
||||||
settingsNotificationsErrorsSelector,
|
settingsNotificationsErrorsSelector,
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
settingsSoundsErrorsSelector,
|
settingsSoundsErrorsSelector,
|
||||||
settingsSoundsPermissionsSelector,
|
settingsSoundsPermissionsSelector,
|
||||||
settingsThemeSelector,
|
settingsThemeSelector,
|
||||||
settingsUIFontSelector,
|
|
||||||
settingsUpdatesStartupSelector,
|
settingsUpdatesStartupSelector,
|
||||||
} from "../selectors"
|
} from "../selectors"
|
||||||
|
|
||||||
@@ -153,199 +152,39 @@ test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => {
|
|||||||
.toBeNull()
|
.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("typing a code font with spaces persists and updates CSS variable", async ({ page, gotoSession }) => {
|
test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => {
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
|
|
||||||
const dialog = await openSettings(page)
|
const dialog = await openSettings(page)
|
||||||
const input = dialog.locator(settingsCodeFontSelector)
|
const select = dialog.locator(settingsFontSelector)
|
||||||
await expect(input).toBeVisible()
|
await expect(select).toBeVisible()
|
||||||
await expect(input).toHaveAttribute("placeholder", "IBM Plex Mono")
|
|
||||||
|
|
||||||
const initialFontFamily = await page.evaluate(() =>
|
const initialFontFamily = await page.evaluate(() => {
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono")
|
||||||
)
|
})
|
||||||
const initialUIFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
expect(initialFontFamily).toContain("IBM Plex Mono")
|
expect(initialFontFamily).toContain("IBM Plex Mono")
|
||||||
|
|
||||||
const next = "Test Mono"
|
await select.locator('[data-slot="select-select-trigger"]').click()
|
||||||
|
|
||||||
await input.click()
|
const items = page.locator('[data-slot="select-select-item"]')
|
||||||
await input.clear()
|
await items.nth(2).click()
|
||||||
await input.pressSequentially(next)
|
|
||||||
await expect(input).toHaveValue(next)
|
|
||||||
|
|
||||||
await expect
|
await page.waitForTimeout(100)
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
const stored = await page.evaluate((key) => {
|
||||||
const raw = localStorage.getItem(key)
|
const raw = localStorage.getItem(key)
|
||||||
return raw ? JSON.parse(raw) : null
|
return raw ? JSON.parse(raw) : null
|
||||||
}, settingsKey)
|
}, settingsKey)
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
font: next,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const newFontFamily = await page.evaluate(() =>
|
expect(stored?.appearance?.font).not.toBe("ibm-plex-mono")
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
|
||||||
)
|
const newFontFamily = await page.evaluate(() => {
|
||||||
const newUIFamily = await page.evaluate(() =>
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono")
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
})
|
||||||
)
|
|
||||||
expect(newFontFamily).toContain(next)
|
|
||||||
expect(newFontFamily).not.toBe(initialFontFamily)
|
expect(newFontFamily).not.toBe(initialFontFamily)
|
||||||
expect(newUIFamily).toBe(initialUIFamily)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("typing a UI font with spaces persists and updates CSS variable", async ({ page, gotoSession }) => {
|
test("color scheme and font rehydrate after reload", async ({ page, gotoSession }) => {
|
||||||
await gotoSession()
|
|
||||||
|
|
||||||
const dialog = await openSettings(page)
|
|
||||||
const input = dialog.locator(settingsUIFontSelector)
|
|
||||||
await expect(input).toBeVisible()
|
|
||||||
await expect(input).toHaveAttribute("placeholder", "Inter")
|
|
||||||
|
|
||||||
const initialFontFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
const initialCodeFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
|
||||||
)
|
|
||||||
expect(initialFontFamily).toContain("Inter")
|
|
||||||
|
|
||||||
const next = "Test Sans"
|
|
||||||
|
|
||||||
await input.click()
|
|
||||||
await input.clear()
|
|
||||||
await input.pressSequentially(next)
|
|
||||||
await expect(input).toHaveValue(next)
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
|
||||||
const raw = localStorage.getItem(key)
|
|
||||||
return raw ? JSON.parse(raw) : null
|
|
||||||
}, settingsKey)
|
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
uiFont: next,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const newFontFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
const newCodeFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
|
||||||
)
|
|
||||||
expect(newFontFamily).toContain(next)
|
|
||||||
expect(newFontFamily).not.toBe(initialFontFamily)
|
|
||||||
expect(newCodeFamily).toBe(initialCodeFamily)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("clearing the code font field restores the default placeholder and stack", async ({ page, gotoSession }) => {
|
|
||||||
await gotoSession()
|
|
||||||
|
|
||||||
const dialog = await openSettings(page)
|
|
||||||
const input = dialog.locator(settingsCodeFontSelector)
|
|
||||||
await expect(input).toBeVisible()
|
|
||||||
|
|
||||||
await input.click()
|
|
||||||
await input.clear()
|
|
||||||
await input.pressSequentially("Reset Mono")
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
|
||||||
const raw = localStorage.getItem(key)
|
|
||||||
return raw ? JSON.parse(raw) : null
|
|
||||||
}, settingsKey)
|
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
font: "Reset Mono",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await input.clear()
|
|
||||||
await input.press("Space")
|
|
||||||
await expect(input).toHaveValue("")
|
|
||||||
await expect(input).toHaveAttribute("placeholder", "IBM Plex Mono")
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
|
||||||
const raw = localStorage.getItem(key)
|
|
||||||
return raw ? JSON.parse(raw) : null
|
|
||||||
}, settingsKey)
|
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
font: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
|
||||||
)
|
|
||||||
expect(fontFamily).toContain("IBM Plex Mono")
|
|
||||||
expect(fontFamily).not.toContain("Reset Mono")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("clearing the UI font field restores the default placeholder and stack", async ({ page, gotoSession }) => {
|
|
||||||
await gotoSession()
|
|
||||||
|
|
||||||
const dialog = await openSettings(page)
|
|
||||||
const input = dialog.locator(settingsUIFontSelector)
|
|
||||||
await expect(input).toBeVisible()
|
|
||||||
|
|
||||||
await input.click()
|
|
||||||
await input.clear()
|
|
||||||
await input.pressSequentially("Reset Sans")
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
|
||||||
const raw = localStorage.getItem(key)
|
|
||||||
return raw ? JSON.parse(raw) : null
|
|
||||||
}, settingsKey)
|
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
uiFont: "Reset Sans",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await input.clear()
|
|
||||||
await input.press("Space")
|
|
||||||
await expect(input).toHaveValue("")
|
|
||||||
await expect(input).toHaveAttribute("placeholder", "Inter")
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate((key) => {
|
|
||||||
const raw = localStorage.getItem(key)
|
|
||||||
return raw ? JSON.parse(raw) : null
|
|
||||||
}, settingsKey)
|
|
||||||
})
|
|
||||||
.toMatchObject({
|
|
||||||
appearance: {
|
|
||||||
uiFont: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const fontFamily = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
expect(fontFamily).toContain("Inter")
|
|
||||||
expect(fontFamily).not.toContain("Reset Sans")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("color scheme, code font, and UI font rehydrate after reload", async ({ page, gotoSession }) => {
|
|
||||||
await gotoSession()
|
await gotoSession()
|
||||||
|
|
||||||
const dialog = await openSettings(page)
|
const dialog = await openSettings(page)
|
||||||
@@ -356,35 +195,31 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag
|
|||||||
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
||||||
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
||||||
|
|
||||||
const code = dialog.locator(settingsCodeFontSelector)
|
const fontSelect = dialog.locator(settingsFontSelector)
|
||||||
const ui = dialog.locator(settingsUIFontSelector)
|
await expect(fontSelect).toBeVisible()
|
||||||
await expect(code).toBeVisible()
|
|
||||||
await expect(ui).toBeVisible()
|
|
||||||
|
|
||||||
const initialMono = await page.evaluate(() =>
|
const initialFontFamily = await page.evaluate(() => {
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||||
)
|
})
|
||||||
const initialSans = await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
|
|
||||||
const initialSettings = await page.evaluate((key) => {
|
const initialSettings = await page.evaluate((key) => {
|
||||||
const raw = localStorage.getItem(key)
|
const raw = localStorage.getItem(key)
|
||||||
return raw ? JSON.parse(raw) : null
|
return raw ? JSON.parse(raw) : null
|
||||||
}, settingsKey)
|
}, settingsKey)
|
||||||
|
|
||||||
const mono = initialSettings?.appearance?.font === "Reload Mono" ? "Reload Mono 2" : "Reload Mono"
|
const currentFont =
|
||||||
const sans = initialSettings?.appearance?.uiFont === "Reload Sans" ? "Reload Sans 2" : "Reload Sans"
|
(await fontSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||||
|
await fontSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||||
|
|
||||||
await code.click()
|
const fontItems = page.locator('[data-slot="select-select-item"]')
|
||||||
await code.clear()
|
expect(await fontItems.count()).toBeGreaterThan(1)
|
||||||
await code.pressSequentially(mono)
|
|
||||||
await expect(code).toHaveValue(mono)
|
|
||||||
|
|
||||||
await ui.click()
|
if (currentFont) {
|
||||||
await ui.clear()
|
await fontItems.filter({ hasNotText: currentFont }).first().click()
|
||||||
await ui.pressSequentially(sans)
|
}
|
||||||
await expect(ui).toHaveValue(sans)
|
if (!currentFont) {
|
||||||
|
await fontItems.nth(1).click()
|
||||||
|
}
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => {
|
.poll(async () => {
|
||||||
@@ -395,8 +230,7 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag
|
|||||||
})
|
})
|
||||||
.toMatchObject({
|
.toMatchObject({
|
||||||
appearance: {
|
appearance: {
|
||||||
font: mono,
|
font: expect.any(String),
|
||||||
uiFont: sans,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -405,18 +239,11 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag
|
|||||||
return raw ? JSON.parse(raw) : null
|
return raw ? JSON.parse(raw) : null
|
||||||
}, settingsKey)
|
}, settingsKey)
|
||||||
|
|
||||||
const updatedMono = await page.evaluate(() =>
|
const updatedFontFamily = await page.evaluate(() => {
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||||
)
|
})
|
||||||
const updatedSans = await page.evaluate(() =>
|
expect(updatedFontFamily).not.toBe(initialFontFamily)
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
expect(updatedSettings?.appearance?.font).not.toBe(initialSettings?.appearance?.font)
|
||||||
)
|
|
||||||
expect(updatedMono).toContain(mono)
|
|
||||||
expect(updatedMono).not.toBe(initialMono)
|
|
||||||
expect(updatedSans).toContain(sans)
|
|
||||||
expect(updatedSans).not.toBe(initialSans)
|
|
||||||
expect(updatedSettings?.appearance?.font).toBe(mono)
|
|
||||||
expect(updatedSettings?.appearance?.uiFont).toBe(sans)
|
|
||||||
|
|
||||||
await closeDialog(page, dialog)
|
await closeDialog(page, dialog)
|
||||||
await page.reload()
|
await page.reload()
|
||||||
@@ -432,8 +259,7 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag
|
|||||||
})
|
})
|
||||||
.toMatchObject({
|
.toMatchObject({
|
||||||
appearance: {
|
appearance: {
|
||||||
font: mono,
|
font: updatedSettings?.appearance?.font,
|
||||||
uiFont: sans,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -444,32 +270,17 @@ test("color scheme, code font, and UI font rehydrate after reload", async ({ pag
|
|||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => {
|
.poll(async () => {
|
||||||
return await page.evaluate(() =>
|
return await page.evaluate(() => {
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.toContain(mono)
|
|
||||||
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
return await page.evaluate(() =>
|
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.toContain(sans)
|
.not.toBe(initialFontFamily)
|
||||||
|
|
||||||
const rehydratedMono = await page.evaluate(() =>
|
const rehydratedFontFamily = await page.evaluate(() => {
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
|
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||||
)
|
})
|
||||||
const rehydratedSans = await page.evaluate(() =>
|
expect(rehydratedFontFamily).not.toBe(initialFontFamily)
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
|
expect(rehydratedSettings?.appearance?.font).toBe(updatedSettings?.appearance?.font)
|
||||||
)
|
|
||||||
expect(rehydratedMono).toContain(mono)
|
|
||||||
expect(rehydratedMono).not.toBe(initialMono)
|
|
||||||
expect(rehydratedSans).toContain(sans)
|
|
||||||
expect(rehydratedSans).not.toBe(initialSans)
|
|
||||||
expect(rehydratedSettings?.appearance?.font).toBe(mono)
|
|
||||||
expect(rehydratedSettings?.appearance?.uiFont).toBe(sans)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
|
test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import {
|
import { cleanupSession, closeSidebar, hoverSessionItem } from "../actions"
|
||||||
defocus,
|
|
||||||
cleanupSession,
|
|
||||||
cleanupTestProject,
|
|
||||||
closeSidebar,
|
|
||||||
createTestProject,
|
|
||||||
hoverSessionItem,
|
|
||||||
openSidebar,
|
|
||||||
waitSession,
|
|
||||||
} from "../actions"
|
|
||||||
import { projectSwitchSelector } from "../selectors"
|
import { projectSwitchSelector } from "../selectors"
|
||||||
import { dirSlug } from "../utils"
|
|
||||||
|
|
||||||
test("collapsed sidebar popover stays open when archiving a session", async ({ page, slug, sdk, gotoSession }) => {
|
test("collapsed sidebar popover stays open when archiving a session", async ({ page, slug, sdk, gotoSession }) => {
|
||||||
const stamp = Date.now()
|
const stamp = Date.now()
|
||||||
@@ -47,72 +37,3 @@ test("collapsed sidebar popover stays open when archiving a session", async ({ p
|
|||||||
await cleanupSession({ sdk, sessionID: two.id })
|
await cleanupSession({ sdk, sessionID: two.id })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("open sidebar project popover stays closed after clicking avatar", async ({ page, withProject }) => {
|
|
||||||
await page.setViewportSize({ width: 1400, height: 800 })
|
|
||||||
|
|
||||||
const other = await createTestProject()
|
|
||||||
const slug = dirSlug(other)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await withProject(
|
|
||||||
async () => {
|
|
||||||
await openSidebar(page)
|
|
||||||
|
|
||||||
const project = page.locator(projectSwitchSelector(slug)).first()
|
|
||||||
const card = page.locator('[data-component="hover-card-content"]')
|
|
||||||
|
|
||||||
await expect(project).toBeVisible()
|
|
||||||
await project.hover()
|
|
||||||
await expect(card.getByText(/recent sessions/i)).toBeVisible()
|
|
||||||
|
|
||||||
await page.mouse.down()
|
|
||||||
await expect(card).toHaveCount(0)
|
|
||||||
await page.mouse.up()
|
|
||||||
|
|
||||||
await waitSession(page, { directory: other })
|
|
||||||
await expect(card).toHaveCount(0)
|
|
||||||
},
|
|
||||||
{ extra: [other] },
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
await cleanupTestProject(other)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test("open sidebar project switch activates on first tabbed enter", async ({ page, withProject }) => {
|
|
||||||
await page.setViewportSize({ width: 1400, height: 800 })
|
|
||||||
|
|
||||||
const other = await createTestProject()
|
|
||||||
const slug = dirSlug(other)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await withProject(
|
|
||||||
async () => {
|
|
||||||
await openSidebar(page)
|
|
||||||
await defocus(page)
|
|
||||||
|
|
||||||
const project = page.locator(projectSwitchSelector(slug)).first()
|
|
||||||
|
|
||||||
await expect(project).toBeVisible()
|
|
||||||
|
|
||||||
let hit = false
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
hit = await project.evaluate((el) => {
|
|
||||||
return el.matches(":focus") || !!el.parentElement?.matches(":focus")
|
|
||||||
})
|
|
||||||
if (hit) break
|
|
||||||
await page.keyboard.press("Tab")
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(hit).toBe(true)
|
|
||||||
|
|
||||||
await page.keyboard.press("Enter")
|
|
||||||
await waitSession(page, { directory: other })
|
|
||||||
},
|
|
||||||
{ extra: [other] },
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
await cleanupTestProject(other)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Page } from "@playwright/test"
|
import type { Page } from "@playwright/test"
|
||||||
import { runTerminal, waitTerminalReady } from "../actions"
|
import { runTerminal, waitTerminalReady } from "../actions"
|
||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { dropdownMenuContentSelector, terminalSelector } from "../selectors"
|
import { terminalSelector } from "../selectors"
|
||||||
import { terminalToggleKey, workspacePersistKey } from "../utils"
|
import { terminalToggleKey, workspacePersistKey } from "../utils"
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -130,39 +130,3 @@ test("closing the active terminal tab falls back to the previous tab", async ({
|
|||||||
.toEqual({ count: 1, first: true })
|
.toEqual({ count: 1, first: true })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("terminal tab can be renamed from the context menu", async ({ page, withProject }) => {
|
|
||||||
await withProject(async ({ directory, gotoSession }) => {
|
|
||||||
const key = workspacePersistKey(directory, "terminal")
|
|
||||||
const rename = `E2E term ${Date.now()}`
|
|
||||||
const tab = page.locator('#terminal-panel [data-slot="tabs-trigger"]').first()
|
|
||||||
|
|
||||||
await gotoSession()
|
|
||||||
await open(page)
|
|
||||||
|
|
||||||
await expect(tab).toContainText(/Terminal 1/)
|
|
||||||
await tab.click({ button: "right" })
|
|
||||||
|
|
||||||
const menu = page.locator(dropdownMenuContentSelector).first()
|
|
||||||
await expect(menu).toBeVisible()
|
|
||||||
await menu.getByRole("menuitem", { name: /^Rename$/i }).click()
|
|
||||||
await expect(menu).toHaveCount(0)
|
|
||||||
|
|
||||||
const input = page.locator('#terminal-panel input[type="text"]').first()
|
|
||||||
await expect(input).toBeVisible()
|
|
||||||
await input.fill(rename)
|
|
||||||
await input.press("Enter")
|
|
||||||
|
|
||||||
await expect(input).toHaveCount(0)
|
|
||||||
await expect(tab).toContainText(rename)
|
|
||||||
await expect
|
|
||||||
.poll(
|
|
||||||
async () => {
|
|
||||||
const state = await store(page, key)
|
|
||||||
return state?.all[0]?.title
|
|
||||||
},
|
|
||||||
{ timeout: 5_000 },
|
|
||||||
)
|
|
||||||
.toBe(rename)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en" style="background-color: var(--background-base)">
|
<html lang="en" style="background-color: var(--background-base)">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>OpenCode</title>
|
<title>OpenCode</title>
|
||||||
<link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
|
<link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.3.3",
|
"version": "1.2.27",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -51,11 +51,9 @@
|
|||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solid-primitives/scroll": "2.1.3",
|
"@solid-primitives/scroll": "2.1.3",
|
||||||
"@solid-primitives/storage": "catalog:",
|
"@solid-primitives/storage": "catalog:",
|
||||||
"@solid-primitives/timer": "1.4.4",
|
|
||||||
"@solid-primitives/websocket": "1.3.1",
|
"@solid-primitives/websocket": "1.3.1",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@tanstack/solid-query": "5.91.4",
|
|
||||||
"@thisbeyond/solid-dnd": "0.7.5",
|
"@thisbeyond/solid-dnd": "0.7.5",
|
||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
|||||||
+10
-12
@@ -6,10 +6,9 @@ import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
|||||||
import { File } from "@opencode-ai/ui/file"
|
import { File } from "@opencode-ai/ui/file"
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { Splash } from "@opencode-ai/ui/logo"
|
import { Splash } from "@opencode-ai/ui/logo"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
import { ThemeProvider } from "@opencode-ai/ui/theme"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
|
import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
|
||||||
import { type Duration, Effect } from "effect"
|
import { type Duration, Effect } from "effect"
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
@@ -32,11 +31,12 @@ import { FileProvider } from "@/context/file"
|
|||||||
import { GlobalSDKProvider } from "@/context/global-sdk"
|
import { GlobalSDKProvider } from "@/context/global-sdk"
|
||||||
import { GlobalSyncProvider } from "@/context/global-sync"
|
import { GlobalSyncProvider } from "@/context/global-sync"
|
||||||
import { HighlightsProvider } from "@/context/highlights"
|
import { HighlightsProvider } from "@/context/highlights"
|
||||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
import { LanguageProvider, useLanguage } from "@/context/language"
|
||||||
import { LayoutProvider } from "@/context/layout"
|
import { LayoutProvider } from "@/context/layout"
|
||||||
import { ModelsProvider } from "@/context/models"
|
import { ModelsProvider } from "@/context/models"
|
||||||
import { NotificationProvider } from "@/context/notification"
|
import { NotificationProvider } from "@/context/notification"
|
||||||
import { PermissionProvider } from "@/context/permission"
|
import { PermissionProvider } from "@/context/permission"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
import { PromptProvider } from "@/context/prompt"
|
import { PromptProvider } from "@/context/prompt"
|
||||||
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
||||||
import { SettingsProvider } from "@/context/settings"
|
import { SettingsProvider } from "@/context/settings"
|
||||||
@@ -76,9 +76,9 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function QueryProvider(props: ParentProps) {
|
function MarkedProviderWithNativeParser(props: ParentProps) {
|
||||||
const client = new QueryClient()
|
const platform = usePlatform()
|
||||||
return <QueryClientProvider client={client}>{props.children}</QueryClientProvider>
|
return <MarkedProvider nativeParser={platform.parseMarkdown}>{props.children}</MarkedProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppShellProviders(props: ParentProps) {
|
function AppShellProviders(props: ParentProps) {
|
||||||
@@ -124,7 +124,7 @@ function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
export function AppBaseProviders(props: ParentProps) {
|
||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Font />
|
<Font />
|
||||||
@@ -133,16 +133,14 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
|||||||
void window.api?.setTitlebar?.({ mode })
|
void window.api?.setTitlebar?.({ mode })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LanguageProvider locale={props.locale}>
|
<LanguageProvider>
|
||||||
<UiI18nBridge>
|
<UiI18nBridge>
|
||||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||||
<QueryProvider>
|
|
||||||
<DialogProvider>
|
<DialogProvider>
|
||||||
<MarkedProvider>
|
<MarkedProviderWithNativeParser>
|
||||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||||
</MarkedProvider>
|
</MarkedProviderWithNativeParser>
|
||||||
</DialogProvider>
|
</DialogProvider>
|
||||||
</QueryProvider>
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</UiI18nBridge>
|
</UiI18nBridge>
|
||||||
</LanguageProvider>
|
</LanguageProvider>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string;
|
|||||||
<Tooltip value={props.tip} placement="top">
|
<Tooltip value={props.tip} placement="top">
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"flex min-h-[42px] w-full min-w-0 flex-col items-center justify-center rounded-[8px] px-0.5 py-1 text-center": true,
|
"flex min-h-[42px] w-full min-w-0 flex-col items-center justify-center rounded-[8px] bg-white/5 px-0.5 py-1 text-center": true,
|
||||||
"col-span-2": !!props.wide,
|
"col-span-2": !!props.wide,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -363,7 +363,11 @@ export function DebugBar() {
|
|||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
aria-label={language.t("debugBar.ariaLabel")}
|
aria-label={language.t("debugBar.ariaLabel")}
|
||||||
class="pointer-events-auto fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl border border-border-base bg-surface-raised-stronger-non-alpha p-0.5 text-text-strong shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]"
|
class="pointer-events-auto fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl border p-0.5 text-text-on-interactive-base shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]"
|
||||||
|
style={{
|
||||||
|
"background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)",
|
||||||
|
"border-color": "color-mix(in srgb, white 14%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-5 gap-px font-mono">
|
<div class="grid grid-cols-5 gap-px font-mono">
|
||||||
<Cell
|
<Cell
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk/v2/client"
|
import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client"
|
||||||
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 } from "@opencode-ai/ui/dialog"
|
||||||
@@ -9,26 +9,20 @@ 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 { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { createEffect, createMemo, createResource, Match, onCleanup, onMount, Switch } from "solid-js"
|
import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { Link } from "@/components/link"
|
import { Link } from "@/components/link"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { DialogSelectModel } from "./dialog-select-model"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
|
|
||||||
export function DialogConnectProvider(props: { provider: string }) {
|
export function DialogConnectProvider(props: { provider: string }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const providers = useProviders()
|
|
||||||
|
|
||||||
const all = () => {
|
|
||||||
void import("./dialog-select-provider").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectProvider />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const alive = { value: true }
|
const alive = { value: true }
|
||||||
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||||
@@ -40,30 +34,16 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
timer.current = undefined
|
timer.current = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const provider = createMemo(
|
const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
|
||||||
|
const methods = createMemo(
|
||||||
() =>
|
() =>
|
||||||
providers.all().find((x) => x.id === props.provider) ??
|
globalSync.data.provider_auth[props.provider] ?? [
|
||||||
globalSync.data.provider.all.find((x) => x.id === props.provider)!,
|
|
||||||
)
|
|
||||||
const fallback = createMemo<ProviderAuthMethod[]>(() => [
|
|
||||||
{
|
{
|
||||||
type: "api" as const,
|
type: "api",
|
||||||
label: language.t("provider.connect.method.apiKey"),
|
label: language.t("provider.connect.method.apiKey"),
|
||||||
},
|
},
|
||||||
])
|
],
|
||||||
const [auth] = createResource(
|
|
||||||
() => props.provider,
|
|
||||||
async () => {
|
|
||||||
const cached = globalSync.data.provider_auth[props.provider]
|
|
||||||
if (cached) return cached
|
|
||||||
const res = await globalSDK.client.provider.auth()
|
|
||||||
if (!alive.value) return fallback()
|
|
||||||
globalSync.set("provider_auth", res.data ?? {})
|
|
||||||
return res.data?.[props.provider] ?? fallback()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
const loading = createMemo(() => auth.loading && !globalSync.data.provider_auth[props.provider])
|
|
||||||
const methods = createMemo(() => auth.latest ?? globalSync.data.provider_auth[props.provider] ?? fallback())
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
methodIndex: undefined as undefined | number,
|
methodIndex: undefined as undefined | number,
|
||||||
authorization: undefined as undefined | ProviderAuthAuthorization,
|
authorization: undefined as undefined | ProviderAuthAuthorization,
|
||||||
@@ -198,11 +178,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
index: 0,
|
index: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const prompts = createMemo<NonNullable<ProviderAuthMethod["prompts"]>>(() => {
|
const prompts = createMemo(() => method()?.prompts ?? [])
|
||||||
const value = method()
|
|
||||||
if (value?.type !== "oauth") return []
|
|
||||||
return value.prompts ?? []
|
|
||||||
})
|
|
||||||
const matches = (prompt: NonNullable<ReturnType<typeof prompts>[number]>, value: Record<string, string>) => {
|
const matches = (prompt: NonNullable<ReturnType<typeof prompts>[number]>, value: Record<string, string>) => {
|
||||||
if (!prompt.when) return true
|
if (!prompt.when) return true
|
||||||
const actual = value[prompt.when.key]
|
const actual = value[prompt.when.key]
|
||||||
@@ -321,12 +297,8 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
listRef?.onKeyDown(e)
|
listRef?.onKeyDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
let auto = false
|
onMount(() => {
|
||||||
createEffect(() => {
|
|
||||||
if (auto) return
|
|
||||||
if (loading()) return
|
|
||||||
if (methods().length === 1) {
|
if (methods().length === 1) {
|
||||||
auto = true
|
|
||||||
selectMethod(0)
|
selectMethod(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -344,7 +316,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
if (methods().length === 1) {
|
if (methods().length === 1) {
|
||||||
all()
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (store.authorization) {
|
if (store.authorization) {
|
||||||
@@ -355,7 +327,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
dispatch({ type: "method.reset" })
|
dispatch({ type: "method.reset" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
all()
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MethodSelection() {
|
function MethodSelection() {
|
||||||
@@ -602,14 +574,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
<div class="px-2.5 pb-10 flex flex-col gap-6">
|
<div class="px-2.5 pb-10 flex flex-col gap-6">
|
||||||
<div onKeyDown={handleKey} tabIndex={0} autofocus={store.methodIndex === undefined ? true : undefined}>
|
<div onKeyDown={handleKey} tabIndex={0} autofocus={store.methodIndex === undefined ? true : undefined}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={loading()}>
|
|
||||||
<div class="text-14-regular text-text-base">
|
|
||||||
<div class="flex items-center gap-x-2">
|
|
||||||
<Spinner />
|
|
||||||
<span>{language.t("provider.connect.status.inProgress")}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={store.methodIndex === undefined}>
|
<Match when={store.methodIndex === undefined}>
|
||||||
<MethodSelection />
|
<MethodSelection />
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type FormState = {
|
|||||||
apiKey: string
|
apiKey: string
|
||||||
models: ModelRow[]
|
models: ModelRow[]
|
||||||
headers: HeaderRow[]
|
headers: HeaderRow[]
|
||||||
|
saving: boolean
|
||||||
err: {
|
err: {
|
||||||
providerID?: string
|
providerID?: string
|
||||||
name?: string
|
name?: string
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe("validateCustomProvider", () => {
|
|||||||
{ row: "h0", key: " X-Test ", value: " enabled ", err: {} },
|
{ row: "h0", key: " X-Test ", value: " enabled ", err: {} },
|
||||||
{ row: "h1", key: "", value: "", err: {} },
|
{ row: "h1", key: "", value: "", err: {} },
|
||||||
],
|
],
|
||||||
|
saving: false,
|
||||||
err: {},
|
err: {},
|
||||||
},
|
},
|
||||||
t,
|
t,
|
||||||
@@ -59,6 +60,7 @@ describe("validateCustomProvider", () => {
|
|||||||
{ row: "h0", key: "Authorization", value: "one", err: {} },
|
{ row: "h0", key: "Authorization", value: "one", err: {} },
|
||||||
{ row: "h1", key: "authorization", value: "two", err: {} },
|
{ row: "h1", key: "authorization", value: "two", err: {} },
|
||||||
],
|
],
|
||||||
|
saving: false,
|
||||||
err: {},
|
err: {},
|
||||||
},
|
},
|
||||||
t,
|
t,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
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 { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { batch, For } from "solid-js"
|
import { batch, For } from "solid-js"
|
||||||
@@ -32,6 +31,7 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
apiKey: "",
|
apiKey: "",
|
||||||
models: [modelRow()],
|
models: [modelRow()],
|
||||||
headers: [headerRow()],
|
headers: [headerRow()],
|
||||||
|
saving: false,
|
||||||
err: {},
|
err: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -116,28 +116,33 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
return output.result
|
return output.result
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveMutation = useMutation(() => ({
|
const save = async (e: SubmitEvent) => {
|
||||||
mutationFn: async (result: NonNullable<ReturnType<typeof validate>>) => {
|
e.preventDefault()
|
||||||
|
if (form.saving) return
|
||||||
|
|
||||||
|
const result = validate()
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
setForm("saving", true)
|
||||||
|
|
||||||
const disabledProviders = globalSync.data.config.disabled_providers ?? []
|
const disabledProviders = globalSync.data.config.disabled_providers ?? []
|
||||||
const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
|
const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
|
||||||
|
|
||||||
if (result.key) {
|
const auth = result.key
|
||||||
await globalSDK.client.auth.set({
|
? globalSDK.client.auth.set({
|
||||||
providerID: result.providerID,
|
providerID: result.providerID,
|
||||||
auth: {
|
auth: {
|
||||||
type: "api",
|
type: "api",
|
||||||
key: result.key,
|
key: result.key,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
: Promise.resolve()
|
||||||
|
|
||||||
await globalSync.updateConfig({
|
auth
|
||||||
provider: { [result.providerID]: result.config },
|
.then(() =>
|
||||||
disabled_providers: nextDisabled,
|
globalSync.updateConfig({ provider: { [result.providerID]: result.config }, disabled_providers: nextDisabled }),
|
||||||
})
|
)
|
||||||
return result
|
.then(() => {
|
||||||
},
|
|
||||||
onSuccess: (result) => {
|
|
||||||
dialog.close()
|
dialog.close()
|
||||||
showToast({
|
showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -145,20 +150,14 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
|
title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
|
||||||
description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
|
description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
onError: (err) => {
|
.catch((err: unknown) => {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
},
|
})
|
||||||
}))
|
.finally(() => {
|
||||||
|
setForm("saving", false)
|
||||||
const save = (e: SubmitEvent) => {
|
})
|
||||||
e.preventDefault()
|
|
||||||
if (saveMutation.isPending) return
|
|
||||||
|
|
||||||
const result = validate()
|
|
||||||
if (!result) return
|
|
||||||
saveMutation.mutate(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -313,14 +312,8 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button class="w-auto self-start" type="submit" size="large" variant="primary" disabled={form.saving}>
|
||||||
class="w-auto self-start"
|
{form.saving ? language.t("common.saving") : language.t("common.submit")}
|
||||||
type="submit"
|
|
||||||
size="large"
|
|
||||||
variant="primary"
|
|
||||||
disabled={saveMutation.isPending}
|
|
||||||
>
|
|
||||||
{saveMutation.isPending ? language.t("common.saving") : language.t("common.submit")}
|
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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 } from "@opencode-ai/ui/dialog"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { createMemo, For, Show } from "solid-js"
|
import { createMemo, For, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
@@ -29,6 +28,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||||||
color: props.project.icon?.color || "pink",
|
color: props.project.icon?.color || "pink",
|
||||||
iconUrl: props.project.icon?.override || "",
|
iconUrl: props.project.icon?.override || "",
|
||||||
startup: props.project.commands?.start ?? "",
|
startup: props.project.commands?.start ?? "",
|
||||||
|
saving: false,
|
||||||
dragOver: false,
|
dragOver: false,
|
||||||
iconHover: false,
|
iconHover: false,
|
||||||
})
|
})
|
||||||
@@ -71,8 +71,12 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||||||
setStore("iconUrl", "")
|
setStore("iconUrl", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveMutation = useMutation(() => ({
|
async function handleSubmit(e: SubmitEvent) {
|
||||||
mutationFn: async () => {
|
e.preventDefault()
|
||||||
|
|
||||||
|
await Promise.resolve()
|
||||||
|
.then(async () => {
|
||||||
|
setStore("saving", true)
|
||||||
const name = store.name.trim() === folderName() ? "" : store.name.trim()
|
const name = store.name.trim() === folderName() ? "" : store.name.trim()
|
||||||
const start = store.startup.trim()
|
const start = store.startup.trim()
|
||||||
|
|
||||||
@@ -95,13 +99,10 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||||||
commands: { start: start || undefined },
|
commands: { start: start || undefined },
|
||||||
})
|
})
|
||||||
dialog.close()
|
dialog.close()
|
||||||
},
|
})
|
||||||
}))
|
.finally(() => {
|
||||||
|
setStore("saving", false)
|
||||||
function handleSubmit(e: SubmitEvent) {
|
})
|
||||||
e.preventDefault()
|
|
||||||
if (saveMutation.isPending) return
|
|
||||||
saveMutation.mutate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -245,8 +246,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||||||
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
|
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
|
||||||
{language.t("common.cancel")}
|
{language.t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" variant="primary" size="large" disabled={saveMutation.isPending}>
|
<Button type="submit" variant="primary" size="large" disabled={store.saving}>
|
||||||
{saveMutation.isPending ? language.t("common.saving") : language.t("common.save")}
|
{store.saving ? language.t("common.saving") : language.t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { useMutation } from "@tanstack/solid-query"
|
import { Component, createMemo, createSignal, Show } from "solid-js"
|
||||||
import { Component, createEffect, createMemo, on, Show } from "solid-js"
|
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } 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 { showToast } from "@opencode-ai/ui/toast"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
const statusLabels = {
|
const statusLabels = {
|
||||||
@@ -20,48 +17,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const [state, setState] = createStore({
|
const [loading, setLoading] = createSignal<string | null>(null)
|
||||||
done: false,
|
|
||||||
loading: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => sync.data.mcp_ready,
|
|
||||||
(ready, prev) => {
|
|
||||||
if (!ready && prev) setState("done", false)
|
|
||||||
},
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (state.done || state.loading) return
|
|
||||||
if (sync.data.mcp_ready) {
|
|
||||||
setState("done", true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setState("loading", true)
|
|
||||||
void sdk.client.mcp
|
|
||||||
.status()
|
|
||||||
.then((result) => {
|
|
||||||
sync.set("mcp", result.data ?? {})
|
|
||||||
sync.set("mcp_ready", true)
|
|
||||||
setState("done", true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setState("done", true)
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: language.t("common.requestFailed"),
|
|
||||||
description: err instanceof Error ? err.message : String(err),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setState("loading", false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const items = createMemo(() =>
|
const items = createMemo(() =>
|
||||||
Object.entries(sync.data.mcp ?? {})
|
Object.entries(sync.data.mcp ?? {})
|
||||||
@@ -69,8 +25,10 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggle = useMutation(() => ({
|
const toggle = async (name: string) => {
|
||||||
mutationFn: async (name: string) => {
|
if (loading()) return
|
||||||
|
setLoading(name)
|
||||||
|
try {
|
||||||
const status = sync.data.mcp[name]
|
const status = sync.data.mcp[name]
|
||||||
if (status?.status === "connected") {
|
if (status?.status === "connected") {
|
||||||
await sdk.client.mcp.disconnect({ name })
|
await sdk.client.mcp.disconnect({ name })
|
||||||
@@ -80,8 +38,10 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
|
|
||||||
const result = await sdk.client.mcp.status()
|
const result = await sdk.client.mcp.status()
|
||||||
if (result.data) sync.set("mcp", result.data)
|
if (result.data) sync.set("mcp", result.data)
|
||||||
},
|
} finally {
|
||||||
}))
|
setLoading(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
||||||
const totalCount = createMemo(() => items().length)
|
const totalCount = createMemo(() => items().length)
|
||||||
@@ -99,8 +59,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
filterKeys={["name", "status"]}
|
filterKeys={["name", "status"]}
|
||||||
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
sortBy={(a, b) => a.name.localeCompare(b.name)}
|
||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (!x || toggle.isPending) return
|
if (x) toggle(x.name)
|
||||||
toggle.mutate(x.name)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => {
|
{(i) => {
|
||||||
@@ -124,7 +83,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
<Show when={statusLabel()}>
|
<Show when={statusLabel()}>
|
||||||
<span class="text-11-regular text-text-weaker">{statusLabel()}</span>
|
<span class="text-11-regular text-text-weaker">{statusLabel()}</span>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={toggle.isPending && toggle.variables === i.name}>
|
<Show when={loading() === i.name}>
|
||||||
<span class="text-11-regular text-text-weak">{language.t("common.loading.ellipsis")}</span>
|
<span class="text-11-regular text-text-weak">{language.t("common.loading.ellipsis")}</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,14 +92,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<Switch
|
<Switch checked={enabled()} disabled={loading() === i.name} onChange={() => toggle(i.name)} />
|
||||||
checked={enabled()}
|
|
||||||
disabled={toggle.isPending && toggle.variables === i.name}
|
|
||||||
onChange={() => {
|
|
||||||
if (toggle.isPending) return
|
|
||||||
toggle.mutate(i.name)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ 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"
|
||||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||||
|
import { DialogConnectProvider } from "./dialog-connect-provider"
|
||||||
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
import { ModelTooltip } from "./model-tooltip"
|
import { ModelTooltip } from "./model-tooltip"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
@@ -19,18 +21,6 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
|||||||
const providers = useProviders()
|
const providers = useProviders()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const connect = (provider: string) => {
|
|
||||||
void import("./dialog-connect-provider").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogConnectProvider provider={provider} />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const all = () => {
|
|
||||||
void import("./dialog-select-provider").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectProvider />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let listRef: ListRef | undefined
|
let listRef: ListRef | undefined
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape") return
|
if (e.key === "Escape") return
|
||||||
@@ -101,7 +91,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
|||||||
}}
|
}}
|
||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (!x) return
|
if (!x) return
|
||||||
connect(x.id)
|
dialog.show(() => <DialogConnectProvider provider={x.id} />)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
@@ -132,7 +122,9 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
|
||||||
icon="dot-grid"
|
icon="dot-grid"
|
||||||
onClick={all}
|
onClick={() => {
|
||||||
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{language.t("dialog.provider.viewAll")}
|
{language.t("dialog.provider.viewAll")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { Tag } from "@opencode-ai/ui/tag"
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
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 { DialogSelectProvider } from "./dialog-select-provider"
|
||||||
|
import { DialogManageModels } from "./dialog-manage-models"
|
||||||
import { ModelTooltip } from "./model-tooltip"
|
import { ModelTooltip } from "./model-tooltip"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
@@ -105,16 +107,12 @@ export function ModelSelectorPopover(props: {
|
|||||||
|
|
||||||
const handleManage = () => {
|
const handleManage = () => {
|
||||||
setStore("open", false)
|
setStore("open", false)
|
||||||
void import("./dialog-manage-models").then((x) => {
|
dialog.show(() => <DialogManageModels />)
|
||||||
dialog.show(() => <x.DialogManageModels />)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleConnectProvider = () => {
|
const handleConnectProvider = () => {
|
||||||
setStore("open", false)
|
setStore("open", false)
|
||||||
void import("./dialog-select-provider").then((x) => {
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
dialog.show(() => <x.DialogSelectProvider />)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
@@ -195,29 +193,26 @@ export const DialogSelectModel: Component<{ provider?: string; model?: ModelStat
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const provider = () => {
|
|
||||||
void import("./dialog-select-provider").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectProvider />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const manage = () => {
|
|
||||||
void import("./dialog-manage-models").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogManageModels />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
title={language.t("dialog.model.select.title")}
|
title={language.t("dialog.model.select.title")}
|
||||||
action={
|
action={
|
||||||
<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={() => dialog.show(() => <DialogSelectProvider />)}
|
||||||
|
>
|
||||||
{language.t("command.provider.connect")}
|
{language.t("command.provider.connect")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<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={() => dialog.show(() => <DialogManageModels />)}
|
||||||
|
>
|
||||||
{language.t("dialog.model.manage")}
|
{language.t("dialog.model.manage")}
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
||||||
@@ -187,6 +186,7 @@ export function DialogSelectServer() {
|
|||||||
name: "",
|
name: "",
|
||||||
username: DEFAULT_USERNAME,
|
username: DEFAULT_USERNAME,
|
||||||
password: "",
|
password: "",
|
||||||
|
adding: false,
|
||||||
error: "",
|
error: "",
|
||||||
showForm: false,
|
showForm: false,
|
||||||
status: undefined as boolean | undefined,
|
status: undefined as boolean | undefined,
|
||||||
@@ -198,6 +198,7 @@ export function DialogSelectServer() {
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
error: "",
|
error: "",
|
||||||
|
busy: false,
|
||||||
status: undefined as boolean | undefined,
|
status: undefined as boolean | undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -208,6 +209,7 @@ export function DialogSelectServer() {
|
|||||||
name: "",
|
name: "",
|
||||||
username: DEFAULT_USERNAME,
|
username: DEFAULT_USERNAME,
|
||||||
password: "",
|
password: "",
|
||||||
|
adding: false,
|
||||||
error: "",
|
error: "",
|
||||||
showForm: false,
|
showForm: false,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
@@ -222,78 +224,10 @@ export function DialogSelectServer() {
|
|||||||
password: "",
|
password: "",
|
||||||
error: "",
|
error: "",
|
||||||
status: undefined,
|
status: undefined,
|
||||||
|
busy: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMutation = useMutation(() => ({
|
|
||||||
mutationFn: async (value: string) => {
|
|
||||||
const normalized = normalizeServerUrl(value)
|
|
||||||
if (!normalized) {
|
|
||||||
resetAdd()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const conn: ServerConnection.Http = {
|
|
||||||
type: "http",
|
|
||||||
http: { url: normalized },
|
|
||||||
}
|
|
||||||
if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim()
|
|
||||||
if (store.addServer.password) conn.http.password = store.addServer.password
|
|
||||||
if (store.addServer.password && store.addServer.username) conn.http.username = store.addServer.username
|
|
||||||
const result = await checkServerHealth(conn.http)
|
|
||||||
if (!result.healthy) {
|
|
||||||
setStore("addServer", { error: language.t("dialog.server.add.error") })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resetAdd()
|
|
||||||
await select(conn, true)
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const editMutation = useMutation(() => ({
|
|
||||||
mutationFn: async (input: { original: ServerConnection.Any; value: string }) => {
|
|
||||||
if (input.original.type !== "http") return
|
|
||||||
const normalized = normalizeServerUrl(input.value)
|
|
||||||
if (!normalized) {
|
|
||||||
resetEdit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = store.editServer.name.trim() || undefined
|
|
||||||
const username = store.editServer.username || undefined
|
|
||||||
const password = store.editServer.password || undefined
|
|
||||||
const existingName = input.original.displayName
|
|
||||||
if (
|
|
||||||
normalized === input.original.http.url &&
|
|
||||||
name === existingName &&
|
|
||||||
username === input.original.http.username &&
|
|
||||||
password === input.original.http.password
|
|
||||||
) {
|
|
||||||
resetEdit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const conn: ServerConnection.Http = {
|
|
||||||
type: "http",
|
|
||||||
displayName: name,
|
|
||||||
http: { url: normalized, username, password },
|
|
||||||
}
|
|
||||||
const result = await checkServerHealth(conn.http)
|
|
||||||
if (!result.healthy) {
|
|
||||||
setStore("editServer", { error: language.t("dialog.server.add.error") })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (normalized === input.original.http.url) {
|
|
||||||
server.add(conn)
|
|
||||||
} else {
|
|
||||||
replaceServer(input.original, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
resetEdit()
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const replaceServer = (original: ServerConnection.Http, next: ServerConnection.Http) => {
|
const replaceServer = (original: ServerConnection.Http, next: ServerConnection.Http) => {
|
||||||
const active = server.key
|
const active = server.key
|
||||||
const newConn = server.add(next)
|
const newConn = server.add(next)
|
||||||
@@ -362,7 +296,7 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddChange = (value: string) => {
|
const handleAddChange = (value: string) => {
|
||||||
if (addMutation.isPending) return
|
if (store.addServer.adding) return
|
||||||
setStore("addServer", { url: value, error: "" })
|
setStore("addServer", { url: value, error: "" })
|
||||||
void previewStatus(value, store.addServer.username, store.addServer.password, (next) =>
|
void previewStatus(value, store.addServer.username, store.addServer.password, (next) =>
|
||||||
setStore("addServer", { status: next }),
|
setStore("addServer", { status: next }),
|
||||||
@@ -370,12 +304,12 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddNameChange = (value: string) => {
|
const handleAddNameChange = (value: string) => {
|
||||||
if (addMutation.isPending) return
|
if (store.addServer.adding) return
|
||||||
setStore("addServer", { name: value, error: "" })
|
setStore("addServer", { name: value, error: "" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddUsernameChange = (value: string) => {
|
const handleAddUsernameChange = (value: string) => {
|
||||||
if (addMutation.isPending) return
|
if (store.addServer.adding) return
|
||||||
setStore("addServer", { username: value, error: "" })
|
setStore("addServer", { username: value, error: "" })
|
||||||
void previewStatus(store.addServer.url, value, store.addServer.password, (next) =>
|
void previewStatus(store.addServer.url, value, store.addServer.password, (next) =>
|
||||||
setStore("addServer", { status: next }),
|
setStore("addServer", { status: next }),
|
||||||
@@ -383,7 +317,7 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddPasswordChange = (value: string) => {
|
const handleAddPasswordChange = (value: string) => {
|
||||||
if (addMutation.isPending) return
|
if (store.addServer.adding) return
|
||||||
setStore("addServer", { password: value, error: "" })
|
setStore("addServer", { password: value, error: "" })
|
||||||
void previewStatus(store.addServer.url, store.addServer.username, value, (next) =>
|
void previewStatus(store.addServer.url, store.addServer.username, value, (next) =>
|
||||||
setStore("addServer", { status: next }),
|
setStore("addServer", { status: next }),
|
||||||
@@ -391,7 +325,7 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleEditChange = (value: string) => {
|
const handleEditChange = (value: string) => {
|
||||||
if (editMutation.isPending) return
|
if (store.editServer.busy) return
|
||||||
setStore("editServer", { value, error: "" })
|
setStore("editServer", { value, error: "" })
|
||||||
void previewStatus(value, store.editServer.username, store.editServer.password, (next) =>
|
void previewStatus(value, store.editServer.username, store.editServer.password, (next) =>
|
||||||
setStore("editServer", { status: next }),
|
setStore("editServer", { status: next }),
|
||||||
@@ -399,12 +333,12 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleEditNameChange = (value: string) => {
|
const handleEditNameChange = (value: string) => {
|
||||||
if (editMutation.isPending) return
|
if (store.editServer.busy) return
|
||||||
setStore("editServer", { name: value, error: "" })
|
setStore("editServer", { name: value, error: "" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditUsernameChange = (value: string) => {
|
const handleEditUsernameChange = (value: string) => {
|
||||||
if (editMutation.isPending) return
|
if (store.editServer.busy) return
|
||||||
setStore("editServer", { username: value, error: "" })
|
setStore("editServer", { username: value, error: "" })
|
||||||
void previewStatus(store.editServer.value, value, store.editServer.password, (next) =>
|
void previewStatus(store.editServer.value, value, store.editServer.password, (next) =>
|
||||||
setStore("editServer", { status: next }),
|
setStore("editServer", { status: next }),
|
||||||
@@ -412,13 +346,85 @@ export function DialogSelectServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleEditPasswordChange = (value: string) => {
|
const handleEditPasswordChange = (value: string) => {
|
||||||
if (editMutation.isPending) return
|
if (store.editServer.busy) return
|
||||||
setStore("editServer", { password: value, error: "" })
|
setStore("editServer", { password: value, error: "" })
|
||||||
void previewStatus(store.editServer.value, store.editServer.username, value, (next) =>
|
void previewStatus(store.editServer.value, store.editServer.username, value, (next) =>
|
||||||
setStore("editServer", { status: next }),
|
setStore("editServer", { status: next }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAdd(value: string) {
|
||||||
|
if (store.addServer.adding) return
|
||||||
|
const normalized = normalizeServerUrl(value)
|
||||||
|
if (!normalized) {
|
||||||
|
resetAdd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore("addServer", { adding: true, error: "" })
|
||||||
|
|
||||||
|
const conn: ServerConnection.Http = {
|
||||||
|
type: "http",
|
||||||
|
http: { url: normalized },
|
||||||
|
}
|
||||||
|
if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim()
|
||||||
|
if (store.addServer.password) conn.http.password = store.addServer.password
|
||||||
|
if (store.addServer.password && store.addServer.username) conn.http.username = store.addServer.username
|
||||||
|
const result = await checkServerHealth(conn.http)
|
||||||
|
setStore("addServer", { adding: false })
|
||||||
|
if (!result.healthy) {
|
||||||
|
setStore("addServer", { error: language.t("dialog.server.add.error") })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAdd()
|
||||||
|
await select(conn, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEdit(original: ServerConnection.Any, value: string) {
|
||||||
|
if (store.editServer.busy || original.type !== "http") return
|
||||||
|
const normalized = normalizeServerUrl(value)
|
||||||
|
if (!normalized) {
|
||||||
|
resetEdit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = store.editServer.name.trim() || undefined
|
||||||
|
const username = store.editServer.username || undefined
|
||||||
|
const password = store.editServer.password || undefined
|
||||||
|
const existingName = original.displayName
|
||||||
|
if (
|
||||||
|
normalized === original.http.url &&
|
||||||
|
name === existingName &&
|
||||||
|
username === original.http.username &&
|
||||||
|
password === original.http.password
|
||||||
|
) {
|
||||||
|
resetEdit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore("editServer", { busy: true, error: "" })
|
||||||
|
|
||||||
|
const conn: ServerConnection.Http = {
|
||||||
|
type: "http",
|
||||||
|
displayName: name,
|
||||||
|
http: { url: normalized, username, password },
|
||||||
|
}
|
||||||
|
const result = await checkServerHealth(conn.http)
|
||||||
|
setStore("editServer", { busy: false })
|
||||||
|
if (!result.healthy) {
|
||||||
|
setStore("editServer", { error: language.t("dialog.server.add.error") })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (normalized === original.http.url) {
|
||||||
|
server.add(conn)
|
||||||
|
} else {
|
||||||
|
replaceServer(original, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetEdit()
|
||||||
|
}
|
||||||
|
|
||||||
const mode = createMemo<"list" | "add" | "edit">(() => {
|
const mode = createMemo<"list" | "add" | "edit">(() => {
|
||||||
if (store.editServer.id) return "edit"
|
if (store.editServer.id) return "edit"
|
||||||
if (store.addServer.showForm) return "add"
|
if (store.addServer.showForm) return "add"
|
||||||
@@ -458,26 +464,23 @@ export function DialogSelectServer() {
|
|||||||
password: conn.http.password ?? "",
|
password: conn.http.password ?? "",
|
||||||
error: "",
|
error: "",
|
||||||
status: store.status[ServerConnection.key(conn)]?.healthy,
|
status: store.status[ServerConnection.key(conn)]?.healthy,
|
||||||
|
busy: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitForm = () => {
|
const submitForm = () => {
|
||||||
if (mode() === "add") {
|
if (mode() === "add") {
|
||||||
if (addMutation.isPending) return
|
void handleAdd(store.addServer.url)
|
||||||
setStore("addServer", { error: "" })
|
|
||||||
addMutation.mutate(store.addServer.url)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const original = editing()
|
const original = editing()
|
||||||
if (!original) return
|
if (!original) return
|
||||||
if (editMutation.isPending) return
|
void handleEdit(original, store.editServer.value)
|
||||||
setStore("editServer", { error: "" })
|
|
||||||
editMutation.mutate({ original, value: store.editServer.value })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFormMode = createMemo(() => mode() !== "list")
|
const isFormMode = createMemo(() => mode() !== "list")
|
||||||
const isAddMode = createMemo(() => mode() === "add")
|
const isAddMode = createMemo(() => mode() === "add")
|
||||||
const formBusy = createMemo(() => (isAddMode() ? addMutation.isPending : editMutation.isPending))
|
const formBusy = createMemo(() => (isAddMode() ? store.addServer.adding : store.editServer.busy))
|
||||||
|
|
||||||
const formTitle = createMemo(() => {
|
const formTitle = createMemo(() => {
|
||||||
if (!isFormMode()) return language.t("dialog.server.title")
|
if (!isFormMode()) return language.t("dialog.server.title")
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||||
|
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
@@ -571,7 +572,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const open = recent()
|
const open = recent()
|
||||||
const seen = new Set(open)
|
const seen = new Set(open)
|
||||||
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||||
if (!query.trim()) return [...agents, ...pinned]
|
|
||||||
const paths = await files.searchFilesAndDirectories(query)
|
const paths = await files.searchFilesAndDirectories(query)
|
||||||
const fileOptions: AtOption[] = paths
|
const fileOptions: AtOption[] = paths
|
||||||
.filter((path) => !seen.has(path))
|
.filter((path) => !seen.has(path))
|
||||||
@@ -1043,7 +1043,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const { addAttachments, removeAttachment, handlePaste } = createPromptAttachments({
|
const { addAttachment, removeAttachment, handlePaste } = createPromptAttachments({
|
||||||
editor: () => editorRef,
|
editor: () => editorRef,
|
||||||
isDialogActive: () => !!dialog.active,
|
isDialogActive: () => !!dialog.active,
|
||||||
setDraggingType: (type) => setStore("draggingType", type),
|
setDraggingType: (type) => setStore("draggingType", type),
|
||||||
@@ -1388,7 +1388,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
class="hidden"
|
class="hidden"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const list = e.currentTarget.files
|
const list = e.currentTarget.files
|
||||||
if (list) void addAttachments(Array.from(list))
|
if (list) {
|
||||||
|
for (const file of Array.from(list)) {
|
||||||
|
void addAttachment(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
e.currentTarget.value = ""
|
e.currentTarget.value = ""
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -1493,15 +1497,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
size="normal"
|
size="normal"
|
||||||
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
|
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
|
||||||
style={control()}
|
style={control()}
|
||||||
onClick={() => {
|
onClick={() => dialog.show(() => <DialogSelectModelUnpaid model={local.model} />)}
|
||||||
void import("@/components/dialog-select-model-unpaid").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id={local.model.current()?.provider?.id ?? ""}
|
id={local.model.current()!.provider.id}
|
||||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
/>
|
/>
|
||||||
@@ -1533,7 +1533,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id={local.model.current()?.provider?.id ?? ""}
|
id={local.model.current()!.provider.id}
|
||||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -71,18 +71,6 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|||||||
|
|
||||||
const addAttachment = (file: File) => add(file)
|
const addAttachment = (file: File) => add(file)
|
||||||
|
|
||||||
const addAttachments = async (files: File[], toast = true) => {
|
|
||||||
let found = false
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const ok = await add(file, false)
|
|
||||||
if (ok) found = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found && files.length > 0 && toast) warn()
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeAttachment = (id: string) => {
|
const removeAttachment = (id: string) => {
|
||||||
const current = prompt.current()
|
const current = prompt.current()
|
||||||
const next = current.filter((part) => part.type !== "image" || part.id !== id)
|
const next = current.filter((part) => part.type !== "image" || part.id !== id)
|
||||||
@@ -96,14 +84,18 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const files = Array.from(clipboardData.items).flatMap((item) => {
|
const items = Array.from(clipboardData.items)
|
||||||
if (item.kind !== "file") return []
|
const fileItems = items.filter((item) => item.kind === "file")
|
||||||
const file = item.getAsFile()
|
|
||||||
return file ? [file] : []
|
|
||||||
})
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (fileItems.length > 0) {
|
||||||
await addAttachments(files)
|
let found = false
|
||||||
|
for (const item of fileItems) {
|
||||||
|
const file = item.getAsFile()
|
||||||
|
if (!file) continue
|
||||||
|
const ok = await add(file, false)
|
||||||
|
if (ok) found = true
|
||||||
|
}
|
||||||
|
if (!found) warn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +169,12 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|||||||
const dropped = event.dataTransfer?.files
|
const dropped = event.dataTransfer?.files
|
||||||
if (!dropped) return
|
if (!dropped) return
|
||||||
|
|
||||||
await addAttachments(Array.from(dropped))
|
let found = false
|
||||||
|
for (const file of Array.from(dropped)) {
|
||||||
|
const ok = await add(file, false)
|
||||||
|
if (ok) found = true
|
||||||
|
}
|
||||||
|
if (!found && dropped.length > 0) warn()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -194,7 +191,6 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
addAttachment,
|
addAttachment,
|
||||||
addAttachments,
|
|
||||||
removeAttachment,
|
removeAttachment,
|
||||||
handlePaste,
|
handlePaste,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,32 +49,6 @@ describe("buildRequestParts", () => {
|
|||||||
expect(result.optimisticParts.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_1")).toBe(true)
|
expect(result.optimisticParts.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_1")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("keeps multiple uploaded attachments in order", () => {
|
|
||||||
const result = buildRequestParts({
|
|
||||||
prompt: [{ type: "text", content: "check these", start: 0, end: 11 }],
|
|
||||||
context: [],
|
|
||||||
images: [
|
|
||||||
{ type: "image", id: "img_1", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" },
|
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
id: "img_2",
|
|
||||||
filename: "b.pdf",
|
|
||||||
mime: "application/pdf",
|
|
||||||
dataUrl: "data:application/pdf;base64,BBB",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
text: "check these",
|
|
||||||
messageID: "msg_multi",
|
|
||||||
sessionID: "ses_multi",
|
|
||||||
sessionDirectory: "/repo",
|
|
||||||
})
|
|
||||||
|
|
||||||
const files = result.requestParts.filter((part) => part.type === "file" && part.url.startsWith("data:"))
|
|
||||||
|
|
||||||
expect(files).toHaveLength(2)
|
|
||||||
expect(files.map((part) => (part.type === "file" ? part.filename : ""))).toEqual(["a.png", "b.pdf"])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("deduplicates context files when prompt already includes same path", () => {
|
test("deduplicates context files when prompt already includes same path", () => {
|
||||||
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ describe("prompt-input history", () => {
|
|||||||
test("canNavigateHistoryAtCursor only allows prompt boundaries", () => {
|
test("canNavigateHistoryAtCursor only allows prompt boundaries", () => {
|
||||||
const value = "a\nb\nc"
|
const value = "a\nb\nc"
|
||||||
|
|
||||||
expect(canNavigateHistoryAtCursor("up", value, 0)).toBe(false)
|
expect(canNavigateHistoryAtCursor("up", value, 0)).toBe(true)
|
||||||
expect(canNavigateHistoryAtCursor("down", value, 0)).toBe(false)
|
expect(canNavigateHistoryAtCursor("down", value, 0)).toBe(false)
|
||||||
|
|
||||||
expect(canNavigateHistoryAtCursor("up", value, 2)).toBe(false)
|
expect(canNavigateHistoryAtCursor("up", value, 2)).toBe(false)
|
||||||
@@ -135,14 +135,11 @@ describe("prompt-input history", () => {
|
|||||||
expect(canNavigateHistoryAtCursor("up", value, 5)).toBe(false)
|
expect(canNavigateHistoryAtCursor("up", value, 5)).toBe(false)
|
||||||
expect(canNavigateHistoryAtCursor("down", value, 5)).toBe(true)
|
expect(canNavigateHistoryAtCursor("down", value, 5)).toBe(true)
|
||||||
|
|
||||||
expect(canNavigateHistoryAtCursor("up", "abc", 0)).toBe(false)
|
expect(canNavigateHistoryAtCursor("up", "abc", 0)).toBe(true)
|
||||||
expect(canNavigateHistoryAtCursor("down", "abc", 3)).toBe(true)
|
expect(canNavigateHistoryAtCursor("down", "abc", 3)).toBe(true)
|
||||||
expect(canNavigateHistoryAtCursor("up", "abc", 1)).toBe(false)
|
expect(canNavigateHistoryAtCursor("up", "abc", 1)).toBe(false)
|
||||||
expect(canNavigateHistoryAtCursor("down", "abc", 1)).toBe(false)
|
expect(canNavigateHistoryAtCursor("down", "abc", 1)).toBe(false)
|
||||||
|
|
||||||
expect(canNavigateHistoryAtCursor("up", "", 0)).toBe(true)
|
|
||||||
expect(canNavigateHistoryAtCursor("down", "", 0)).toBe(true)
|
|
||||||
|
|
||||||
expect(canNavigateHistoryAtCursor("up", "abc", 0, true)).toBe(true)
|
expect(canNavigateHistoryAtCursor("up", "abc", 0, true)).toBe(true)
|
||||||
expect(canNavigateHistoryAtCursor("up", "abc", 3, true)).toBe(true)
|
expect(canNavigateHistoryAtCursor("up", "abc", 3, true)).toBe(true)
|
||||||
expect(canNavigateHistoryAtCursor("down", "abc", 0, true)).toBe(true)
|
expect(canNavigateHistoryAtCursor("down", "abc", 0, true)).toBe(true)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function canNavigateHistoryAtCursor(direction: "up" | "down", text: strin
|
|||||||
const atStart = position === 0
|
const atStart = position === 0
|
||||||
const atEnd = position === text.length
|
const atEnd = position === text.length
|
||||||
if (inHistory) return atStart || atEnd
|
if (inHistory) return atStart || atEnd
|
||||||
if (direction === "up") return position === 0 && text.length === 0
|
if (direction === "up") return position === 0
|
||||||
return position === text.length
|
return position === text.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useFile } from "@/context/file"
|
|||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
|
||||||
import { getSessionContextMetrics } from "@/components/session/session-context-metrics"
|
import { getSessionContextMetrics } from "@/components/session/session-context-metrics"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
@@ -33,7 +32,6 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
const file = useFile()
|
const file = useFile()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const providers = useProviders()
|
|
||||||
const { params, tabs, view } = useSessionLayout()
|
const { params, tabs, view } = useSessionLayout()
|
||||||
|
|
||||||
const variant = createMemo(() => props.variant ?? "button")
|
const variant = createMemo(() => props.variant ?? "button")
|
||||||
@@ -52,7 +50,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), providers.all()))
|
const metrics = createMemo(() => getSessionContextMetrics(messages(), sync.data.provider.all))
|
||||||
const context = createMemo(() => metrics().context)
|
const context = createMemo(() => metrics().context)
|
||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
return usd().format(metrics().totalCost)
|
return usd().format(metrics().totalCost)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Markdown } from "@opencode-ai/ui/markdown"
|
|||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { getSessionContextMetrics } from "./session-context-metrics"
|
import { getSessionContextMetrics } from "./session-context-metrics"
|
||||||
import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown"
|
import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown"
|
||||||
@@ -93,7 +92,6 @@ const emptyUserMessages: UserMessage[] = []
|
|||||||
export function SessionContextTab() {
|
export function SessionContextTab() {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const providers = useProviders()
|
|
||||||
const { params, view } = useSessionLayout()
|
const { params, view } = useSessionLayout()
|
||||||
|
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
||||||
@@ -132,7 +130,7 @@ export function SessionContextTab() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), providers.all()))
|
const metrics = createMemo(() => getSessionContextMetrics(messages(), sync.data.provider.all))
|
||||||
const ctx = createMemo(() => metrics().context)
|
const ctx = createMemo(() => metrics().context)
|
||||||
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
||||||
|
|
||||||
@@ -269,14 +267,14 @@ export function SessionContextTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
class="@container h-full"
|
class="@container h-full pb-10"
|
||||||
viewportRef={(el) => {
|
viewportRef={(el) => {
|
||||||
scroll = el
|
scroll = el
|
||||||
restoreScroll()
|
restoreScroll()
|
||||||
}}
|
}}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div class="px-6 pt-4 pb-10 flex flex-col gap-10">
|
<div class="px-6 pt-4 flex flex-col gap-10">
|
||||||
<div class="grid grid-cols-1 @[32rem]:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 @[32rem]:grid-cols-2 gap-4">
|
||||||
<For each={stats}>
|
<For each={stats}>
|
||||||
{(stat) => <Stat label={language.t(stat.label as Parameters<typeof language.t>[0])} value={stat.value()} />}
|
{(stat) => <Stat label={language.t(stat.label as Parameters<typeof language.t>[0])} value={stat.value()} />}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
})
|
})
|
||||||
let input: HTMLInputElement | undefined
|
let input: HTMLInputElement | undefined
|
||||||
let blurFrame: number | undefined
|
let blurFrame: number | undefined
|
||||||
let editRequested = false
|
|
||||||
|
|
||||||
const isDefaultTitle = () => {
|
const isDefaultTitle = () => {
|
||||||
const number = props.terminal.titleNumber
|
const number = props.terminal.titleNumber
|
||||||
@@ -169,14 +168,8 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
left: `${store.menuPosition.x}px`,
|
left: `${store.menuPosition.x}px`,
|
||||||
top: `${store.menuPosition.y}px`,
|
top: `${store.menuPosition.y}px`,
|
||||||
}}
|
}}
|
||||||
onCloseAutoFocus={(e) => {
|
|
||||||
if (!editRequested) return
|
|
||||||
e.preventDefault()
|
|
||||||
editRequested = false
|
|
||||||
requestAnimationFrame(() => edit())
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DropdownMenu.Item onSelect={() => (editRequested = true)}>
|
<DropdownMenu.Item onSelect={edit}>
|
||||||
<Icon name="edit" class="w-4 h-4 mr-2" />
|
<Icon name="edit" class="w-4 h-4 mr-2" />
|
||||||
{language.t("common.rename")}
|
{language.t("common.rename")}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|||||||
@@ -1,43 +1,27 @@
|
|||||||
import { Component, Show, createMemo, createResource, onMount, type JSX } from "solid-js"
|
import { Component, Show, createMemo, createResource, type JSX } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import {
|
import { useSettings, monoFontFamily } from "@/context/settings"
|
||||||
monoDefault,
|
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
|
||||||
monoFontFamily,
|
|
||||||
monoInput,
|
|
||||||
sansDefault,
|
|
||||||
sansFontFamily,
|
|
||||||
sansInput,
|
|
||||||
useSettings,
|
|
||||||
} from "@/context/settings"
|
|
||||||
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
|
|
||||||
import { Link } from "./link"
|
import { Link } from "./link"
|
||||||
import { SettingsList } from "./settings-list"
|
import { SettingsList } from "./settings-list"
|
||||||
|
|
||||||
let demoSoundState = {
|
let demoSoundState = {
|
||||||
cleanup: undefined as (() => void) | undefined,
|
cleanup: undefined as (() => void) | undefined,
|
||||||
timeout: undefined as NodeJS.Timeout | undefined,
|
timeout: undefined as NodeJS.Timeout | undefined,
|
||||||
run: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeOption = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
|
// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
|
||||||
// delay the playback by 100ms during quick selection changes and pause existing sounds.
|
// delay the playback by 100ms during quick selection changes and pause existing sounds.
|
||||||
const stopDemoSound = () => {
|
const stopDemoSound = () => {
|
||||||
demoSoundState.run += 1
|
|
||||||
if (demoSoundState.cleanup) {
|
if (demoSoundState.cleanup) {
|
||||||
demoSoundState.cleanup()
|
demoSoundState.cleanup()
|
||||||
}
|
}
|
||||||
@@ -45,19 +29,12 @@ const stopDemoSound = () => {
|
|||||||
demoSoundState.cleanup = undefined
|
demoSoundState.cleanup = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const playDemoSound = (id: string | undefined) => {
|
const playDemoSound = (src: string | undefined) => {
|
||||||
stopDemoSound()
|
stopDemoSound()
|
||||||
if (!id) return
|
if (!src) return
|
||||||
|
|
||||||
const run = ++demoSoundState.run
|
|
||||||
demoSoundState.timeout = setTimeout(() => {
|
demoSoundState.timeout = setTimeout(() => {
|
||||||
void playSoundById(id).then((cleanup) => {
|
demoSoundState.cleanup = playSound(src)
|
||||||
if (demoSoundState.run !== run) {
|
|
||||||
cleanup?.()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
demoSoundState.cleanup = cleanup
|
|
||||||
})
|
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,10 +44,6 @@ export const SettingsGeneral: Component = () => {
|
|||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
void theme.loadThemes()
|
|
||||||
})
|
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
checking: false,
|
checking: false,
|
||||||
})
|
})
|
||||||
@@ -131,7 +104,9 @@ export const SettingsGeneral: Component = () => {
|
|||||||
.finally(() => setStore("checking", false))
|
.finally(() => setStore("checking", false))
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeOptions = createMemo<ThemeOption[]>(() => theme.ids().map((id) => ({ id, name: theme.name(id) })))
|
const themeOptions = createMemo(() =>
|
||||||
|
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
|
||||||
|
)
|
||||||
|
|
||||||
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
|
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
|
||||||
{ value: "system", label: language.t("theme.scheme.system") },
|
{ value: "system", label: language.t("theme.scheme.system") },
|
||||||
@@ -151,10 +126,25 @@ export const SettingsGeneral: Component = () => {
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
const noneSound = { id: "none", label: "sound.option.none" } as const
|
const fontOptions = [
|
||||||
|
{ value: "ibm-plex-mono", label: "font.option.ibmPlexMono" },
|
||||||
|
{ value: "cascadia-code", label: "font.option.cascadiaCode" },
|
||||||
|
{ value: "fira-code", label: "font.option.firaCode" },
|
||||||
|
{ value: "hack", label: "font.option.hack" },
|
||||||
|
{ value: "inconsolata", label: "font.option.inconsolata" },
|
||||||
|
{ value: "intel-one-mono", label: "font.option.intelOneMono" },
|
||||||
|
{ value: "iosevka", label: "font.option.iosevka" },
|
||||||
|
{ value: "jetbrains-mono", label: "font.option.jetbrainsMono" },
|
||||||
|
{ value: "meslo-lgs", label: "font.option.mesloLgs" },
|
||||||
|
{ value: "roboto-mono", label: "font.option.robotoMono" },
|
||||||
|
{ value: "source-code-pro", label: "font.option.sourceCodePro" },
|
||||||
|
{ value: "ubuntu-mono", label: "font.option.ubuntuMono" },
|
||||||
|
{ value: "geist-mono", label: "font.option.geistMono" },
|
||||||
|
] as const
|
||||||
|
const fontOptionsList = [...fontOptions]
|
||||||
|
|
||||||
|
const noneSound = { id: "none", label: "sound.option.none", src: undefined } as const
|
||||||
const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
||||||
const mono = () => monoInput(settings.appearance.font())
|
|
||||||
const sans = () => sansInput(settings.appearance.uiFont())
|
|
||||||
|
|
||||||
const soundSelectProps = (
|
const soundSelectProps = (
|
||||||
enabled: () => boolean,
|
enabled: () => boolean,
|
||||||
@@ -168,7 +158,7 @@ export const SettingsGeneral: Component = () => {
|
|||||||
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
|
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
|
||||||
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
|
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
playDemoSound(option.id === "none" ? undefined : option.id)
|
playDemoSound(option.src)
|
||||||
},
|
},
|
||||||
onSelect: (option: (typeof soundOptions)[number] | undefined) => {
|
onSelect: (option: (typeof soundOptions)[number] | undefined) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
@@ -179,7 +169,7 @@ export const SettingsGeneral: Component = () => {
|
|||||||
}
|
}
|
||||||
setEnabled(true)
|
setEnabled(true)
|
||||||
set(option.id)
|
set(option.id)
|
||||||
playDemoSound(option.id)
|
playDemoSound(option.src)
|
||||||
},
|
},
|
||||||
variant: "secondary" as const,
|
variant: "secondary" as const,
|
||||||
size: "small" as const,
|
size: "small" as const,
|
||||||
@@ -321,50 +311,28 @@ export const SettingsGeneral: Component = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
|
||||||
title={language.t("settings.general.row.uiFont.title")}
|
|
||||||
description={language.t("settings.general.row.uiFont.description")}
|
|
||||||
>
|
|
||||||
<div class="w-full sm:w-[220px]">
|
|
||||||
<TextField
|
|
||||||
data-action="settings-ui-font"
|
|
||||||
label={language.t("settings.general.row.uiFont.title")}
|
|
||||||
hideLabel
|
|
||||||
type="text"
|
|
||||||
value={sans()}
|
|
||||||
onChange={(value) => settings.appearance.setUIFont(value)}
|
|
||||||
placeholder={sansDefault}
|
|
||||||
spellcheck={false}
|
|
||||||
autocorrect="off"
|
|
||||||
autocomplete="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
class="text-12-regular"
|
|
||||||
style={{ "font-family": sansFontFamily(settings.appearance.uiFont()) }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsRow>
|
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
title={language.t("settings.general.row.font.title")}
|
title={language.t("settings.general.row.font.title")}
|
||||||
description={language.t("settings.general.row.font.description")}
|
description={language.t("settings.general.row.font.description")}
|
||||||
>
|
>
|
||||||
<div class="w-full sm:w-[220px]">
|
<Select
|
||||||
<TextField
|
data-action="settings-font"
|
||||||
data-action="settings-code-font"
|
options={fontOptionsList}
|
||||||
label={language.t("settings.general.row.font.title")}
|
current={fontOptionsList.find((o) => o.value === settings.appearance.font())}
|
||||||
hideLabel
|
value={(o) => o.value}
|
||||||
type="text"
|
label={(o) => language.t(o.label)}
|
||||||
value={mono()}
|
onSelect={(option) => option && settings.appearance.setFont(option.value)}
|
||||||
onChange={(value) => settings.appearance.setFont(value)}
|
variant="secondary"
|
||||||
placeholder={monoDefault}
|
size="small"
|
||||||
spellcheck={false}
|
triggerVariant="settings"
|
||||||
autocorrect="off"
|
triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "min-width": "180px" }}
|
||||||
autocomplete="off"
|
>
|
||||||
autocapitalize="off"
|
{(option) => (
|
||||||
class="text-12-regular"
|
<span style={{ "font-family": monoFontFamily(option?.value) }}>
|
||||||
style={{ "font-family": monoFontFamily(settings.appearance.font()) }}
|
{option ? language.t(option.label) : ""}
|
||||||
/>
|
</span>
|
||||||
</div>
|
)}
|
||||||
|
</Select>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</SettingsList>
|
</SettingsList>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,445 +0,0 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
|
||||||
import { useNavigate } from "@solidjs/router"
|
|
||||||
import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js"
|
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
|
||||||
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { useSDK } from "@/context/sdk"
|
|
||||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
|
||||||
import { useSync } from "@/context/sync"
|
|
||||||
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
|
||||||
|
|
||||||
const pollMs = 10_000
|
|
||||||
|
|
||||||
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
|
||||||
const parts = value.split(file)
|
|
||||||
if (parts.length === 1) return value
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{parts[0]}
|
|
||||||
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm text-text-base">{file}</code>
|
|
||||||
{parts.slice(1).join(file)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const listServersByHealth = (
|
|
||||||
list: ServerConnection.Any[],
|
|
||||||
active: ServerConnection.Key | undefined,
|
|
||||||
status: Record<ServerConnection.Key, ServerHealth | undefined>,
|
|
||||||
) => {
|
|
||||||
if (!list.length) return list
|
|
||||||
const order = new Map(list.map((url, index) => [url, index] as const))
|
|
||||||
const rank = (value?: ServerHealth) => {
|
|
||||||
if (value?.healthy === true) return 0
|
|
||||||
if (value?.healthy === false) return 2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.slice().sort((a, b) => {
|
|
||||||
if (ServerConnection.key(a) === active) return -1
|
|
||||||
if (ServerConnection.key(b) === active) return 1
|
|
||||||
const diff = rank(status[ServerConnection.key(a)]) - rank(status[ServerConnection.key(b)])
|
|
||||||
if (diff !== 0) return diff
|
|
||||||
return (order.get(a) ?? 0) - (order.get(b) ?? 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const useServerHealth = (servers: Accessor<ServerConnection.Any[]>, enabled: Accessor<boolean>) => {
|
|
||||||
const checkServerHealth = useCheckServerHealth()
|
|
||||||
const [status, setStatus] = createStore({} as Record<ServerConnection.Key, ServerHealth | undefined>)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!enabled()) {
|
|
||||||
setStatus(reconcile({}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const list = servers()
|
|
||||||
let dead = false
|
|
||||||
|
|
||||||
const refresh = async () => {
|
|
||||||
const results: Record<string, ServerHealth> = {}
|
|
||||||
await Promise.all(
|
|
||||||
list.map(async (conn) => {
|
|
||||||
results[ServerConnection.key(conn)] = await checkServerHealth(conn.http)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if (dead) return
|
|
||||||
setStatus(reconcile(results))
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh()
|
|
||||||
const id = setInterval(() => void refresh(), pollMs)
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
clearInterval(id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
|
||||||
const useDefaultServerKey = (
|
|
||||||
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
|
||||||
) => {
|
|
||||||
const [state, setState] = createStore({
|
|
||||||
url: undefined as string | undefined,
|
|
||||||
tick: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
state.tick
|
|
||||||
let dead = false
|
|
||||||
const result = get?.()
|
|
||||||
if (!result) {
|
|
||||||
setState("url", undefined)
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
void result.then((next) => {
|
|
||||||
if (dead) return
|
|
||||||
setState("url", next ? normalizeServerUrl(next) : undefined)
|
|
||||||
})
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setState("url", normalizeServerUrl(result))
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: () => {
|
|
||||||
const u = state.url
|
|
||||||
if (!u) return
|
|
||||||
return ServerConnection.key({ type: "http", http: { url: u } })
|
|
||||||
},
|
|
||||||
refresh: () => setState("tick", (value) => value + 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useMcpToggleMutation = () => {
|
|
||||||
const sync = useSync()
|
|
||||||
const sdk = useSDK()
|
|
||||||
const language = useLanguage()
|
|
||||||
|
|
||||||
return useMutation(() => ({
|
|
||||||
mutationFn: async (name: string) => {
|
|
||||||
const status = sync.data.mcp[name]
|
|
||||||
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
|
||||||
const result = await sdk.client.mcp.status()
|
|
||||||
if (result.data) sync.set("mcp", result.data)
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: language.t("common.requestFailed"),
|
|
||||||
description: err instanceof Error ? err.message : String(err),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
|
|
||||||
const sync = useSync()
|
|
||||||
const server = useServer()
|
|
||||||
const platform = usePlatform()
|
|
||||||
const dialog = useDialog()
|
|
||||||
const language = useLanguage()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const sdk = useSDK()
|
|
||||||
|
|
||||||
const [load, setLoad] = createStore({
|
|
||||||
lspDone: false,
|
|
||||||
lspLoading: false,
|
|
||||||
mcpDone: false,
|
|
||||||
mcpLoading: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fail = (err: unknown) => {
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: language.t("common.requestFailed"),
|
|
||||||
description: err instanceof Error ? err.message : String(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!props.shown()) return
|
|
||||||
|
|
||||||
if (!sync.data.mcp_ready && !load.mcpDone && !load.mcpLoading) {
|
|
||||||
setLoad("mcpLoading", true)
|
|
||||||
void sdk.client.mcp
|
|
||||||
.status()
|
|
||||||
.then((result) => {
|
|
||||||
sync.set("mcp", result.data ?? {})
|
|
||||||
sync.set("mcp_ready", true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setLoad("mcpDone", true)
|
|
||||||
fail(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoad("mcpLoading", false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sync.data.lsp_ready && !load.lspDone && !load.lspLoading) {
|
|
||||||
setLoad("lspLoading", true)
|
|
||||||
void sdk.client.lsp
|
|
||||||
.status()
|
|
||||||
.then((result) => {
|
|
||||||
sync.set("lsp", result.data ?? [])
|
|
||||||
sync.set("lsp_ready", true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setLoad("lspDone", true)
|
|
||||||
fail(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoad("lspLoading", false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let dialogRun = 0
|
|
||||||
let dialogDead = false
|
|
||||||
onCleanup(() => {
|
|
||||||
dialogDead = true
|
|
||||||
dialogRun += 1
|
|
||||||
})
|
|
||||||
const servers = createMemo(() => {
|
|
||||||
const current = server.current
|
|
||||||
const list = server.list
|
|
||||||
if (!current) return list
|
|
||||||
if (list.every((item) => ServerConnection.key(item) !== ServerConnection.key(current))) return [current, ...list]
|
|
||||||
return [current, ...list.filter((item) => ServerConnection.key(item) !== ServerConnection.key(current))]
|
|
||||||
})
|
|
||||||
const health = useServerHealth(servers, props.shown)
|
|
||||||
const sortedServers = createMemo(() => listServersByHealth(servers(), server.key, health))
|
|
||||||
const toggleMcp = useMcpToggleMutation()
|
|
||||||
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
|
|
||||||
const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
|
||||||
const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status
|
|
||||||
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
|
||||||
const lspItems = createMemo(() => sync.data.lsp ?? [])
|
|
||||||
const lspCount = createMemo(() => lspItems().length)
|
|
||||||
const plugins = createMemo(() =>
|
|
||||||
(sync.data.config.plugin ?? []).map((item) => (typeof item === "string" ? item : item[0])),
|
|
||||||
)
|
|
||||||
const pluginCount = createMemo(() => plugins().length)
|
|
||||||
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json"))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]">
|
|
||||||
<Tabs
|
|
||||||
aria-label={language.t("status.popover.ariaLabel")}
|
|
||||||
class="tabs bg-background-strong rounded-xl overflow-hidden"
|
|
||||||
data-component="tabs"
|
|
||||||
data-active="servers"
|
|
||||||
defaultValue="servers"
|
|
||||||
variant="alt"
|
|
||||||
>
|
|
||||||
<Tabs.List data-slot="tablist" class="bg-transparent border-b-0 px-4 pt-2 pb-0 gap-4 h-10">
|
|
||||||
<Tabs.Trigger value="servers" data-slot="tab" class="text-12-regular">
|
|
||||||
{sortedServers().length > 0 ? `${sortedServers().length} ` : ""}
|
|
||||||
{language.t("status.popover.tab.servers")}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Tabs.Trigger value="mcp" data-slot="tab" class="text-12-regular">
|
|
||||||
{mcpConnected() > 0 ? `${mcpConnected()} ` : ""}
|
|
||||||
{language.t("status.popover.tab.mcp")}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Tabs.Trigger value="lsp" data-slot="tab" class="text-12-regular">
|
|
||||||
{lspCount() > 0 ? `${lspCount()} ` : ""}
|
|
||||||
{language.t("status.popover.tab.lsp")}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Tabs.Trigger value="plugins" data-slot="tab" class="text-12-regular">
|
|
||||||
{pluginCount() > 0 ? `${pluginCount()} ` : ""}
|
|
||||||
{language.t("status.popover.tab.plugins")}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
</Tabs.List>
|
|
||||||
|
|
||||||
<Tabs.Content value="servers">
|
|
||||||
<div class="flex flex-col px-2 pb-2">
|
|
||||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
||||||
<For each={sortedServers()}>
|
|
||||||
{(s) => {
|
|
||||||
const key = ServerConnection.key(s)
|
|
||||||
const blocked = () => health[key]?.healthy === false
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
|
|
||||||
classList={{
|
|
||||||
"hover:bg-surface-raised-base-hover": !blocked(),
|
|
||||||
"cursor-not-allowed": blocked(),
|
|
||||||
}}
|
|
||||||
aria-disabled={blocked()}
|
|
||||||
onClick={() => {
|
|
||||||
if (blocked()) return
|
|
||||||
navigate("/")
|
|
||||||
queueMicrotask(() => server.setActive(key))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ServerHealthIndicator health={health[key]} />
|
|
||||||
<ServerRow
|
|
||||||
conn={s}
|
|
||||||
dimmed={blocked()}
|
|
||||||
status={health[key]}
|
|
||||||
class="flex items-center gap-2 w-full min-w-0"
|
|
||||||
nameClass="text-14-regular text-text-base truncate"
|
|
||||||
versionClass="text-12-regular text-text-weak truncate"
|
|
||||||
badge={
|
|
||||||
<Show when={key === defaultServer.key()}>
|
|
||||||
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
|
|
||||||
{language.t("common.default")}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div class="flex-1" />
|
|
||||||
<Show when={server.current && key === ServerConnection.key(server.current)}>
|
|
||||||
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
|
|
||||||
</Show>
|
|
||||||
</ServerRow>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
class="mt-3 self-start h-8 px-3 py-1.5"
|
|
||||||
onClick={() => {
|
|
||||||
const run = ++dialogRun
|
|
||||||
void import("./dialog-select-server").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(() => <x.DialogSelectServer />, defaultServer.refresh)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{language.t("status.popover.action.manageServers")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
|
|
||||||
<Tabs.Content value="mcp">
|
|
||||||
<div class="flex flex-col px-2 pb-2">
|
|
||||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
||||||
<Show
|
|
||||||
when={mcpNames().length > 0}
|
|
||||||
fallback={
|
|
||||||
<div class="text-14-regular text-text-base text-center my-auto">{language.t("dialog.mcp.empty")}</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={mcpNames()}>
|
|
||||||
{(name) => {
|
|
||||||
const status = () => mcpStatus(name)
|
|
||||||
const enabled = () => status() === "connected"
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
|
|
||||||
onClick={() => {
|
|
||||||
if (toggleMcp.isPending) return
|
|
||||||
toggleMcp.mutate(name)
|
|
||||||
}}
|
|
||||||
disabled={toggleMcp.isPending && toggleMcp.variables === name}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
classList={{
|
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
|
||||||
"bg-icon-success-base": status() === "connected",
|
|
||||||
"bg-icon-critical-base": status() === "failed",
|
|
||||||
"bg-border-weak-base": status() === "disabled",
|
|
||||||
"bg-icon-warning-base":
|
|
||||||
status() === "needs_auth" || status() === "needs_client_registration",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="text-14-regular text-text-base truncate flex-1">{name}</span>
|
|
||||||
<div onClick={(event) => event.stopPropagation()}>
|
|
||||||
<Switch
|
|
||||||
checked={enabled()}
|
|
||||||
disabled={toggleMcp.isPending && toggleMcp.variables === name}
|
|
||||||
onChange={() => {
|
|
||||||
if (toggleMcp.isPending) return
|
|
||||||
toggleMcp.mutate(name)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
|
|
||||||
<Tabs.Content value="lsp">
|
|
||||||
<div class="flex flex-col px-2 pb-2">
|
|
||||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
||||||
<Show
|
|
||||||
when={lspItems().length > 0}
|
|
||||||
fallback={
|
|
||||||
<div class="text-14-regular text-text-base text-center my-auto">{language.t("dialog.lsp.empty")}</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={lspItems()}>
|
|
||||||
{(item) => (
|
|
||||||
<div class="flex items-center gap-2 w-full px-2 py-1">
|
|
||||||
<div
|
|
||||||
classList={{
|
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
|
||||||
"bg-icon-success-base": item.status === "connected",
|
|
||||||
"bg-icon-critical-base": item.status === "error",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="text-14-regular text-text-base truncate">{item.name || item.id}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
|
|
||||||
<Tabs.Content value="plugins">
|
|
||||||
<div class="flex flex-col px-2 pb-2">
|
|
||||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
||||||
<Show
|
|
||||||
when={plugins().length > 0}
|
|
||||||
fallback={<div class="text-14-regular text-text-base text-center my-auto">{pluginEmpty()}</div>}
|
|
||||||
>
|
|
||||||
<For each={plugins()}>
|
|
||||||
{(plugin) => (
|
|
||||||
<div class="flex items-center gap-2 w-full px-2 py-1">
|
|
||||||
<div class="size-1.5 rounded-full shrink-0 bg-icon-success-base" />
|
|
||||||
<span class="text-14-regular text-text-base truncate">{plugin}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,203 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Popover } from "@opencode-ai/ui/popover"
|
import { Popover } from "@opencode-ai/ui/popover"
|
||||||
import { Suspense, createMemo, createSignal, lazy, Show } from "solid-js"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
|
import { useNavigate } from "@solidjs/router"
|
||||||
|
import { type Accessor, createEffect, createMemo, createSignal, For, type JSXElement, onCleanup, Show } from "solid-js"
|
||||||
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
|
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServer } from "@/context/server"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useSDK } from "@/context/sdk"
|
||||||
|
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||||
|
import { DialogSelectServer } from "./dialog-select-server"
|
||||||
|
|
||||||
const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody })))
|
const pollMs = 10_000
|
||||||
|
|
||||||
|
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
||||||
|
const parts = value.split(file)
|
||||||
|
if (parts.length === 1) return value
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{parts[0]}
|
||||||
|
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm text-text-base">{file}</code>
|
||||||
|
{parts.slice(1).join(file)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const listServersByHealth = (
|
||||||
|
list: ServerConnection.Any[],
|
||||||
|
active: ServerConnection.Key | undefined,
|
||||||
|
status: Record<ServerConnection.Key, ServerHealth | undefined>,
|
||||||
|
) => {
|
||||||
|
if (!list.length) return list
|
||||||
|
const order = new Map(list.map((url, index) => [url, index] as const))
|
||||||
|
const rank = (value?: ServerHealth) => {
|
||||||
|
if (value?.healthy === true) return 0
|
||||||
|
if (value?.healthy === false) return 2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.slice().sort((a, b) => {
|
||||||
|
if (ServerConnection.key(a) === active) return -1
|
||||||
|
if (ServerConnection.key(b) === active) return 1
|
||||||
|
const diff = rank(status[ServerConnection.key(a)]) - rank(status[ServerConnection.key(b)])
|
||||||
|
if (diff !== 0) return diff
|
||||||
|
return (order.get(a) ?? 0) - (order.get(b) ?? 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const useServerHealth = (servers: Accessor<ServerConnection.Any[]>) => {
|
||||||
|
const checkServerHealth = useCheckServerHealth()
|
||||||
|
const [status, setStatus] = createStore({} as Record<ServerConnection.Key, ServerHealth | undefined>)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const list = servers()
|
||||||
|
let dead = false
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
const results: Record<string, ServerHealth> = {}
|
||||||
|
await Promise.all(
|
||||||
|
list.map(async (conn) => {
|
||||||
|
results[ServerConnection.key(conn)] = await checkServerHealth(conn.http)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
if (dead) return
|
||||||
|
setStatus(reconcile(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh()
|
||||||
|
const id = setInterval(() => void refresh(), pollMs)
|
||||||
|
onCleanup(() => {
|
||||||
|
dead = true
|
||||||
|
clearInterval(id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDefaultServerKey = (
|
||||||
|
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
||||||
|
) => {
|
||||||
|
const [state, setState] = createStore({
|
||||||
|
url: undefined as string | undefined,
|
||||||
|
tick: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
state.tick
|
||||||
|
let dead = false
|
||||||
|
const result = get?.()
|
||||||
|
if (!result) {
|
||||||
|
setState("url", undefined)
|
||||||
|
onCleanup(() => {
|
||||||
|
dead = true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
void result.then((next) => {
|
||||||
|
if (dead) return
|
||||||
|
setState("url", next ? normalizeServerUrl(next) : undefined)
|
||||||
|
})
|
||||||
|
onCleanup(() => {
|
||||||
|
dead = true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setState("url", normalizeServerUrl(result))
|
||||||
|
onCleanup(() => {
|
||||||
|
dead = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: () => {
|
||||||
|
const u = state.url
|
||||||
|
if (!u) return
|
||||||
|
return ServerConnection.key({ type: "http", http: { url: u } })
|
||||||
|
},
|
||||||
|
refresh: () => setState("tick", (value) => value + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useMcpToggle = (input: {
|
||||||
|
sync: ReturnType<typeof useSync>
|
||||||
|
sdk: ReturnType<typeof useSDK>
|
||||||
|
language: ReturnType<typeof useLanguage>
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = createSignal<string | null>(null)
|
||||||
|
|
||||||
|
const toggle = async (name: string) => {
|
||||||
|
if (loading()) return
|
||||||
|
setLoading(name)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = input.sync.data.mcp[name]
|
||||||
|
await (status?.status === "connected"
|
||||||
|
? input.sdk.client.mcp.disconnect({ name })
|
||||||
|
: input.sdk.client.mcp.connect({ name }))
|
||||||
|
const result = await input.sdk.client.mcp.status()
|
||||||
|
if (result.data) input.sync.set("mcp", result.data)
|
||||||
|
} catch (err) {
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.language.t("common.requestFailed"),
|
||||||
|
description: err instanceof Error ? err.message : String(err),
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { loading, toggle }
|
||||||
|
}
|
||||||
|
|
||||||
export function StatusPopover() {
|
export function StatusPopover() {
|
||||||
const language = useLanguage()
|
|
||||||
const server = useServer()
|
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const sdk = useSDK()
|
||||||
|
const server = useServer()
|
||||||
|
const platform = usePlatform()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const language = useLanguage()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const [shown, setShown] = createSignal(false)
|
const [shown, setShown] = createSignal(false)
|
||||||
const ready = createMemo(() => server.healthy() === false || sync.data.mcp_ready)
|
const servers = createMemo(() => {
|
||||||
const healthy = createMemo(() => {
|
const current = server.current
|
||||||
|
const list = server.list
|
||||||
|
if (!current) return list
|
||||||
|
if (list.every((item) => ServerConnection.key(item) !== ServerConnection.key(current))) return [current, ...list]
|
||||||
|
return [current, ...list.filter((item) => ServerConnection.key(item) !== ServerConnection.key(current))]
|
||||||
|
})
|
||||||
|
const health = useServerHealth(servers)
|
||||||
|
const sortedServers = createMemo(() => listServersByHealth(servers(), server.key, health))
|
||||||
|
const mcp = useMcpToggle({ sync, sdk, language })
|
||||||
|
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
|
||||||
|
const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
||||||
|
const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status
|
||||||
|
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
||||||
|
const lspItems = createMemo(() => sync.data.lsp ?? [])
|
||||||
|
const lspCount = createMemo(() => lspItems().length)
|
||||||
|
const plugins = createMemo(() => sync.data.config.plugin ?? [])
|
||||||
|
const pluginCount = createMemo(() => plugins().length)
|
||||||
|
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "opencode.json"))
|
||||||
|
const overallHealthy = createMemo(() => {
|
||||||
const serverHealthy = server.healthy() === true
|
const serverHealthy = server.healthy() === true
|
||||||
const mcp = Object.values(sync.data.mcp ?? {})
|
const anyMcpIssue = mcpNames().some((name) => {
|
||||||
const issue = mcp.some((item) => item.status !== "connected" && item.status !== "disabled")
|
const status = mcpStatus(name)
|
||||||
return serverHealthy && !issue
|
return status !== "connected" && status !== "disabled"
|
||||||
|
})
|
||||||
|
return serverHealthy && !anyMcpIssue
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,9 +219,9 @@ export function StatusPopover() {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"absolute -top-px -right-px size-1.5 rounded-full": true,
|
"absolute -top-px -right-px size-1.5 rounded-full": true,
|
||||||
"bg-icon-success-base": ready() && healthy(),
|
"bg-icon-success-base": overallHealthy(),
|
||||||
"bg-icon-critical-base": server.healthy() === false || (ready() && !healthy()),
|
"bg-icon-critical-base": !overallHealthy() && server.healthy() !== undefined,
|
||||||
"bg-border-weak-base": server.healthy() === undefined || !ready(),
|
"bg-border-weak-base": server.healthy() === undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,15 +231,193 @@ export function StatusPopover() {
|
|||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
shift={-168}
|
shift={-168}
|
||||||
>
|
>
|
||||||
<Show when={shown()}>
|
<div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]">
|
||||||
<Suspense
|
<Tabs
|
||||||
fallback={
|
aria-label={language.t("status.popover.ariaLabel")}
|
||||||
<div class="w-[360px] h-14 rounded-xl bg-background-strong shadow-[var(--shadow-lg-border-base)]" />
|
class="tabs bg-background-strong rounded-xl overflow-hidden"
|
||||||
|
data-component="tabs"
|
||||||
|
data-active="servers"
|
||||||
|
defaultValue="servers"
|
||||||
|
variant="alt"
|
||||||
|
>
|
||||||
|
<Tabs.List data-slot="tablist" class="bg-transparent border-b-0 px-4 pt-2 pb-0 gap-4 h-10">
|
||||||
|
<Tabs.Trigger value="servers" data-slot="tab" class="text-12-regular">
|
||||||
|
{sortedServers().length > 0 ? `${sortedServers().length} ` : ""}
|
||||||
|
{language.t("status.popover.tab.servers")}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger value="mcp" data-slot="tab" class="text-12-regular">
|
||||||
|
{mcpConnected() > 0 ? `${mcpConnected()} ` : ""}
|
||||||
|
{language.t("status.popover.tab.mcp")}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger value="lsp" data-slot="tab" class="text-12-regular">
|
||||||
|
{lspCount() > 0 ? `${lspCount()} ` : ""}
|
||||||
|
{language.t("status.popover.tab.lsp")}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger value="plugins" data-slot="tab" class="text-12-regular">
|
||||||
|
{pluginCount() > 0 ? `${pluginCount()} ` : ""}
|
||||||
|
{language.t("status.popover.tab.plugins")}
|
||||||
|
</Tabs.Trigger>
|
||||||
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Tabs.Content value="servers">
|
||||||
|
<div class="flex flex-col px-2 pb-2">
|
||||||
|
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||||
|
<For each={sortedServers()}>
|
||||||
|
{(s) => {
|
||||||
|
const key = ServerConnection.key(s)
|
||||||
|
const isBlocked = () => health[key]?.healthy === false
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
|
||||||
|
classList={{
|
||||||
|
"hover:bg-surface-raised-base-hover": !isBlocked(),
|
||||||
|
"cursor-not-allowed": isBlocked(),
|
||||||
|
}}
|
||||||
|
aria-disabled={isBlocked()}
|
||||||
|
onClick={() => {
|
||||||
|
if (isBlocked()) return
|
||||||
|
navigate("/")
|
||||||
|
queueMicrotask(() => server.setActive(key))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ServerHealthIndicator health={health[key]} />
|
||||||
|
<ServerRow
|
||||||
|
conn={s}
|
||||||
|
dimmed={isBlocked()}
|
||||||
|
status={health[key]}
|
||||||
|
class="flex items-center gap-2 w-full min-w-0"
|
||||||
|
nameClass="text-14-regular text-text-base truncate"
|
||||||
|
versionClass="text-12-regular text-text-weak truncate"
|
||||||
|
badge={
|
||||||
|
<Show when={key === defaultServer.key()}>
|
||||||
|
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
|
||||||
|
{language.t("common.default")}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Body shown={shown} />
|
<div class="flex-1" />
|
||||||
</Suspense>
|
<Show when={server.current && key === ServerConnection.key(server.current)}>
|
||||||
|
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
|
||||||
</Show>
|
</Show>
|
||||||
|
</ServerRow>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
class="mt-3 self-start h-8 px-3 py-1.5"
|
||||||
|
onClick={() => dialog.show(() => <DialogSelectServer />, defaultServer.refresh)}
|
||||||
|
>
|
||||||
|
{language.t("status.popover.action.manageServers")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tabs.Content>
|
||||||
|
|
||||||
|
<Tabs.Content value="mcp">
|
||||||
|
<div class="flex flex-col px-2 pb-2">
|
||||||
|
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||||
|
<Show
|
||||||
|
when={mcpNames().length > 0}
|
||||||
|
fallback={
|
||||||
|
<div class="text-14-regular text-text-base text-center my-auto">
|
||||||
|
{language.t("dialog.mcp.empty")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<For each={mcpNames()}>
|
||||||
|
{(name) => {
|
||||||
|
const status = () => mcpStatus(name)
|
||||||
|
const enabled = () => status() === "connected"
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
|
||||||
|
onClick={() => mcp.toggle(name)}
|
||||||
|
disabled={mcp.loading() === name}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
|
"bg-icon-success-base": status() === "connected",
|
||||||
|
"bg-icon-critical-base": status() === "failed",
|
||||||
|
"bg-border-weak-base": status() === "disabled",
|
||||||
|
"bg-icon-warning-base":
|
||||||
|
status() === "needs_auth" || status() === "needs_client_registration",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="text-14-regular text-text-base truncate flex-1">{name}</span>
|
||||||
|
<div onClick={(event) => event.stopPropagation()}>
|
||||||
|
<Switch
|
||||||
|
checked={enabled()}
|
||||||
|
disabled={mcp.loading() === name}
|
||||||
|
onChange={() => mcp.toggle(name)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tabs.Content>
|
||||||
|
|
||||||
|
<Tabs.Content value="lsp">
|
||||||
|
<div class="flex flex-col px-2 pb-2">
|
||||||
|
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||||
|
<Show
|
||||||
|
when={lspItems().length > 0}
|
||||||
|
fallback={
|
||||||
|
<div class="text-14-regular text-text-base text-center my-auto">
|
||||||
|
{language.t("dialog.lsp.empty")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<For each={lspItems()}>
|
||||||
|
{(item) => (
|
||||||
|
<div class="flex items-center gap-2 w-full px-2 py-1">
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
|
"bg-icon-success-base": item.status === "connected",
|
||||||
|
"bg-icon-critical-base": item.status === "error",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="text-14-regular text-text-base truncate">{item.name || item.id}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tabs.Content>
|
||||||
|
|
||||||
|
<Tabs.Content value="plugins">
|
||||||
|
<div class="flex flex-col px-2 pb-2">
|
||||||
|
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
||||||
|
<Show
|
||||||
|
when={plugins().length > 0}
|
||||||
|
fallback={<div class="text-14-regular text-text-base text-center my-auto">{pluginEmpty()}</div>}
|
||||||
|
>
|
||||||
|
<For each={plugins()}>
|
||||||
|
{(plugin) => (
|
||||||
|
<div class="flex items-center gap-2 w-full px-2 py-1">
|
||||||
|
<div class="size-1.5 rounded-full shrink-0 bg-icon-success-base" />
|
||||||
|
<span class="text-14-regular text-text-base truncate">{plugin}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tabs.Content>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { withAlpha } from "@opencode-ai/ui/theme/color"
|
import { type HexColor, resolveThemeVariant, useTheme, withAlpha } from "@opencode-ai/ui/theme"
|
||||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
|
||||||
import { resolveThemeVariant } from "@opencode-ai/ui/theme/resolve"
|
|
||||||
import type { HexColor } from "@opencode-ai/ui/theme/types"
|
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import type { FitAddon, Ghostty, Terminal as Term } from "ghostty-web"
|
import type { FitAddon, Ghostty, Terminal as Term } from "ghostty-web"
|
||||||
import { type ComponentProps, createEffect, createMemo, onCleanup, onMount, splitProps } from "solid-js"
|
import { type ComponentProps, createEffect, createMemo, onCleanup, onMount, splitProps } from "solid-js"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
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"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
import { useTheme } from "@opencode-ai/ui/theme"
|
||||||
|
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
|||||||
@@ -32,25 +32,6 @@ describe("command keybind helpers", () => {
|
|||||||
expect(matchKeybind(keybinds, new KeyboardEvent("keydown", { key: ",", ctrlKey: true, altKey: true }))).toBe(false)
|
expect(matchKeybind(keybinds, new KeyboardEvent("keydown", { key: ",", ctrlKey: true, altKey: true }))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("matchKeybind supports bracket keys", () => {
|
|
||||||
const keybinds = parseKeybind("mod+alt+[, mod+alt+]")
|
|
||||||
const prev = keybinds[0]
|
|
||||||
const next = keybinds[1]
|
|
||||||
|
|
||||||
expect(
|
|
||||||
matchKeybind(
|
|
||||||
keybinds,
|
|
||||||
new KeyboardEvent("keydown", { key: "[", ctrlKey: prev?.ctrl, metaKey: prev?.meta, altKey: true }),
|
|
||||||
),
|
|
||||||
).toBe(true)
|
|
||||||
expect(
|
|
||||||
matchKeybind(
|
|
||||||
keybinds,
|
|
||||||
new KeyboardEvent("keydown", { key: "]", ctrlKey: next?.ctrl, metaKey: next?.meta, altKey: true }),
|
|
||||||
),
|
|
||||||
).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("formatKeybind returns human readable output", () => {
|
test("formatKeybind returns human readable output", () => {
|
||||||
const display = formatKeybind("ctrl+alt+arrowup")
|
const display = formatKeybind("ctrl+alt+arrowup")
|
||||||
|
|
||||||
@@ -59,11 +40,4 @@ describe("command keybind helpers", () => {
|
|||||||
expect(display.includes("Alt") || display.includes("⌥")).toBe(true)
|
expect(display.includes("Alt") || display.includes("⌥")).toBe(true)
|
||||||
expect(formatKeybind("none")).toBe("")
|
expect(formatKeybind("none")).toBe("")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("formatKeybind prefers the first combo", () => {
|
|
||||||
const display = formatKeybind("mod+k,mod+p")
|
|
||||||
|
|
||||||
expect(display.includes("K") || display.includes("k")).toBe(true)
|
|
||||||
expect(display.includes("P") || display.includes("p")).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,13 +9,23 @@ import type {
|
|||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
|
import {
|
||||||
|
createContext,
|
||||||
|
getOwner,
|
||||||
|
Match,
|
||||||
|
onCleanup,
|
||||||
|
onMount,
|
||||||
|
type ParentProps,
|
||||||
|
Switch,
|
||||||
|
untrack,
|
||||||
|
useContext,
|
||||||
|
} from "solid-js"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import type { InitError } from "../pages/error"
|
import type { InitError } from "../pages/error"
|
||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
import { bootstrapDirectory, bootstrapGlobal, clearProviderRev } from "./global-sync/bootstrap"
|
import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap"
|
||||||
import { createChildStoreManager } from "./global-sync/child-store"
|
import { createChildStoreManager } from "./global-sync/child-store"
|
||||||
import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./global-sync/event-reducer"
|
import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./global-sync/event-reducer"
|
||||||
import { createRefreshQueue } from "./global-sync/queue"
|
import { createRefreshQueue } from "./global-sync/queue"
|
||||||
@@ -70,8 +80,6 @@ function createGlobalSync() {
|
|||||||
|
|
||||||
let active = true
|
let active = true
|
||||||
let projectWritten = false
|
let projectWritten = false
|
||||||
let bootedAt = 0
|
|
||||||
let bootingRoot = false
|
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
active = false
|
active = false
|
||||||
@@ -154,7 +162,6 @@ function createGlobalSync() {
|
|||||||
queue.clear(directory)
|
queue.clear(directory)
|
||||||
sessionMeta.delete(directory)
|
sessionMeta.delete(directory)
|
||||||
sdkCache.delete(directory)
|
sdkCache.delete(directory)
|
||||||
clearProviderRev(directory)
|
|
||||||
clearSessionPrefetchDirectory(directory)
|
clearSessionPrefetchDirectory(directory)
|
||||||
},
|
},
|
||||||
translate: language.t,
|
translate: language.t,
|
||||||
@@ -251,12 +258,6 @@ function createGlobalSync() {
|
|||||||
const sdk = sdkFor(directory)
|
const sdk = sdkFor(directory)
|
||||||
await bootstrapDirectory({
|
await bootstrapDirectory({
|
||||||
directory,
|
directory,
|
||||||
global: {
|
|
||||||
config: globalStore.config,
|
|
||||||
path: globalStore.path,
|
|
||||||
project: globalStore.project,
|
|
||||||
provider: globalStore.provider,
|
|
||||||
},
|
|
||||||
sdk,
|
sdk,
|
||||||
store: child[0],
|
store: child[0],
|
||||||
setStore: child[1],
|
setStore: child[1],
|
||||||
@@ -277,20 +278,15 @@ function createGlobalSync() {
|
|||||||
const unsub = globalSDK.event.listen((e) => {
|
const unsub = globalSDK.event.listen((e) => {
|
||||||
const directory = e.name
|
const directory = e.name
|
||||||
const event = e.details
|
const event = e.details
|
||||||
const recent = bootingRoot || Date.now() - bootedAt < 1500
|
|
||||||
|
|
||||||
if (directory === "global") {
|
if (directory === "global") {
|
||||||
applyGlobalEvent({
|
applyGlobalEvent({
|
||||||
event,
|
event,
|
||||||
project: globalStore.project,
|
project: globalStore.project,
|
||||||
refresh: () => {
|
refresh: queue.refresh,
|
||||||
if (recent) return
|
|
||||||
queue.refresh()
|
|
||||||
},
|
|
||||||
setGlobalProject: setProjects,
|
setGlobalProject: setProjects,
|
||||||
})
|
})
|
||||||
if (event.type === "server.connected" || event.type === "global.disposed") {
|
if (event.type === "server.connected" || event.type === "global.disposed") {
|
||||||
if (recent) return
|
|
||||||
for (const directory of Object.keys(children.children)) {
|
for (const directory of Object.keys(children.children)) {
|
||||||
queue.push(directory)
|
queue.push(directory)
|
||||||
}
|
}
|
||||||
@@ -313,10 +309,7 @@ function createGlobalSync() {
|
|||||||
loadLsp: () => {
|
loadLsp: () => {
|
||||||
sdkFor(directory)
|
sdkFor(directory)
|
||||||
.lsp.status()
|
.lsp.status()
|
||||||
.then((x) => {
|
.then((x) => setStore("lsp", x.data ?? []))
|
||||||
setStore("lsp", x.data ?? [])
|
|
||||||
setStore("lsp_ready", true)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -332,19 +325,17 @@ function createGlobalSync() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
bootingRoot = true
|
|
||||||
try {
|
|
||||||
await bootstrapGlobal({
|
await bootstrapGlobal({
|
||||||
globalSDK: globalSDK.client,
|
globalSDK: globalSDK.client,
|
||||||
|
connectErrorTitle: language.t("dialog.server.add.error"),
|
||||||
|
connectErrorDescription: language.t("error.globalSync.connectFailed", {
|
||||||
|
url: globalSDK.url,
|
||||||
|
}),
|
||||||
requestFailedTitle: language.t("common.requestFailed"),
|
requestFailedTitle: language.t("common.requestFailed"),
|
||||||
translate: language.t,
|
translate: language.t,
|
||||||
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||||
setGlobalStore: setBootStore,
|
setGlobalStore: setBootStore,
|
||||||
})
|
})
|
||||||
bootedAt = Date.now()
|
|
||||||
} finally {
|
|
||||||
bootingRoot = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -401,7 +392,13 @@ const GlobalSyncContext = createContext<ReturnType<typeof createGlobalSync>>()
|
|||||||
|
|
||||||
export function GlobalSyncProvider(props: ParentProps) {
|
export function GlobalSyncProvider(props: ParentProps) {
|
||||||
const value = createGlobalSync()
|
const value = createGlobalSync()
|
||||||
return <GlobalSyncContext.Provider value={value}>{props.children}</GlobalSyncContext.Provider>
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Match when={value.ready}>
|
||||||
|
<GlobalSyncContext.Provider value={value}>{props.children}</GlobalSyncContext.Provider>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGlobalSync() {
|
export function useGlobalSync() {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import type {
|
|||||||
ProviderAuthResponse,
|
ProviderAuthResponse,
|
||||||
ProviderListResponse,
|
ProviderListResponse,
|
||||||
QuestionRequest,
|
QuestionRequest,
|
||||||
Session,
|
|
||||||
Todo,
|
Todo,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
@@ -16,7 +15,7 @@ import { retry } from "@opencode-ai/util/retry"
|
|||||||
import { batch } from "solid-js"
|
import { batch } from "solid-js"
|
||||||
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
import { cmp, normalizeProviderList } from "./utils"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
@@ -32,83 +31,40 @@ type GlobalStore = {
|
|||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForPaint() {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
let done = false
|
|
||||||
const finish = () => {
|
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
const timer = setTimeout(finish, 50)
|
|
||||||
if (typeof requestAnimationFrame !== "function") return
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
clearTimeout(timer)
|
|
||||||
finish()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function errors(list: PromiseSettledResult<unknown>[]) {
|
|
||||||
return list.filter((item): item is PromiseRejectedResult => item.status === "rejected").map((item) => item.reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
const providerRev = new Map<string, number>()
|
|
||||||
|
|
||||||
export function clearProviderRev(directory: string) {
|
|
||||||
providerRev.delete(directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
function runAll(list: Array<() => Promise<unknown>>) {
|
|
||||||
return Promise.allSettled(list.map((item) => item()))
|
|
||||||
}
|
|
||||||
|
|
||||||
function showErrors(input: {
|
|
||||||
errors: unknown[]
|
|
||||||
title: string
|
|
||||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
|
||||||
formatMoreCount: (count: number) => string
|
|
||||||
}) {
|
|
||||||
if (input.errors.length === 0) return
|
|
||||||
const message = formatServerError(input.errors[0], input.translate)
|
|
||||||
const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: input.title,
|
|
||||||
description: message + more,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function bootstrapGlobal(input: {
|
export async function bootstrapGlobal(input: {
|
||||||
globalSDK: OpencodeClient
|
globalSDK: OpencodeClient
|
||||||
|
connectErrorTitle: string
|
||||||
|
connectErrorDescription: string
|
||||||
requestFailedTitle: string
|
requestFailedTitle: string
|
||||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||||
formatMoreCount: (count: number) => string
|
formatMoreCount: (count: number) => string
|
||||||
setGlobalStore: SetStoreFunction<GlobalStore>
|
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||||
}) {
|
}) {
|
||||||
const fast = [
|
const health = await input.globalSDK.global
|
||||||
() =>
|
.health()
|
||||||
|
.then((x) => x.data)
|
||||||
|
.catch(() => undefined)
|
||||||
|
if (!health?.healthy) {
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.connectErrorTitle,
|
||||||
|
description: input.connectErrorDescription,
|
||||||
|
})
|
||||||
|
input.setGlobalStore("ready", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tasks = [
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.globalSDK.path.get().then((x) => {
|
input.globalSDK.path.get().then((x) => {
|
||||||
input.setGlobalStore("path", x.data!)
|
input.setGlobalStore("path", x.data!)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
() =>
|
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.globalSDK.global.config.get().then((x) => {
|
input.globalSDK.global.config.get().then((x) => {
|
||||||
input.setGlobalStore("config", x.data!)
|
input.setGlobalStore("config", x.data!)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.globalSDK.provider.list().then((x) => {
|
|
||||||
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
const slow = [
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.globalSDK.project.list().then((x) => {
|
input.globalSDK.project.list().then((x) => {
|
||||||
const projects = (x.data ?? [])
|
const projects = (x.data ?? [])
|
||||||
@@ -119,21 +75,29 @@ export async function bootstrapGlobal(input: {
|
|||||||
input.setGlobalStore("project", projects)
|
input.setGlobalStore("project", projects)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
retry(() =>
|
||||||
|
input.globalSDK.provider.list().then((x) => {
|
||||||
|
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
retry(() =>
|
||||||
|
input.globalSDK.provider.auth().then((x) => {
|
||||||
|
input.setGlobalStore("provider_auth", x.data ?? {})
|
||||||
|
}),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
showErrors({
|
const results = await Promise.allSettled(tasks)
|
||||||
errors: errors(await runAll(fast)),
|
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
|
||||||
|
if (errors.length) {
|
||||||
|
const message = formatServerError(errors[0], input.translate)
|
||||||
|
const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
title: input.requestFailedTitle,
|
title: input.requestFailedTitle,
|
||||||
translate: input.translate,
|
description: message + more,
|
||||||
formatMoreCount: input.formatMoreCount,
|
|
||||||
})
|
|
||||||
await waitForPaint()
|
|
||||||
showErrors({
|
|
||||||
errors: errors(await runAll(slow)),
|
|
||||||
title: input.requestFailedTitle,
|
|
||||||
translate: input.translate,
|
|
||||||
formatMoreCount: input.formatMoreCount,
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
input.setGlobalStore("ready", true)
|
input.setGlobalStore("ready", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,44 +111,6 @@ function groupBySession<T extends { id: string; sessionID: string }>(input: T[])
|
|||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectID(directory: string, projects: Project[]) {
|
|
||||||
return projects.find((project) => project.worktree === directory || project.sandboxes?.includes(directory))?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeSession(setStore: SetStoreFunction<State>, session: Session) {
|
|
||||||
setStore("session", (list) => {
|
|
||||||
const next = list.slice()
|
|
||||||
const idx = next.findIndex((item) => item.id >= session.id)
|
|
||||||
if (idx === -1) return [...next, session]
|
|
||||||
if (next[idx]?.id === session.id) {
|
|
||||||
next[idx] = session
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
next.splice(idx, 0, session)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function warmSessions(input: {
|
|
||||||
ids: string[]
|
|
||||||
store: Store<State>
|
|
||||||
setStore: SetStoreFunction<State>
|
|
||||||
sdk: OpencodeClient
|
|
||||||
}) {
|
|
||||||
const known = new Set(input.store.session.map((item) => item.id))
|
|
||||||
const ids = [...new Set(input.ids)].filter((id) => !!id && !known.has(id))
|
|
||||||
if (ids.length === 0) return Promise.resolve()
|
|
||||||
return Promise.all(
|
|
||||||
ids.map((sessionID) =>
|
|
||||||
retry(() => input.sdk.session.get({ sessionID })).then((x) => {
|
|
||||||
const session = x.data
|
|
||||||
if (!session?.id) return
|
|
||||||
mergeSession(input.setStore, session)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).then(() => undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function bootstrapDirectory(input: {
|
export async function bootstrapDirectory(input: {
|
||||||
directory: string
|
directory: string
|
||||||
sdk: OpencodeClient
|
sdk: OpencodeClient
|
||||||
@@ -193,68 +119,51 @@ export async function bootstrapDirectory(input: {
|
|||||||
vcsCache: VcsCache
|
vcsCache: VcsCache
|
||||||
loadSessions: (directory: string) => Promise<void> | void
|
loadSessions: (directory: string) => Promise<void> | void
|
||||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||||
global: {
|
|
||||||
config: Config
|
|
||||||
path: Path
|
|
||||||
project: Project[]
|
|
||||||
provider: ProviderListResponse
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
const loading = input.store.status !== "complete"
|
if (input.store.status !== "complete") input.setStore("status", "loading")
|
||||||
const seededProject = projectID(input.directory, input.global.project)
|
|
||||||
const seededPath = input.global.path.directory === input.directory ? input.global.path : undefined
|
|
||||||
if (seededProject) input.setStore("project", seededProject)
|
|
||||||
if (seededPath) input.setStore("path", seededPath)
|
|
||||||
if (input.store.provider.all.length === 0 && input.global.provider.all.length > 0) {
|
|
||||||
input.setStore("provider", input.global.provider)
|
|
||||||
}
|
|
||||||
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
|
|
||||||
input.setStore("config", input.global.config)
|
|
||||||
}
|
|
||||||
if (loading || input.store.provider.all.length === 0) {
|
|
||||||
input.setStore("provider_ready", false)
|
|
||||||
}
|
|
||||||
input.setStore("mcp_ready", false)
|
|
||||||
input.setStore("mcp", {})
|
|
||||||
input.setStore("lsp_ready", false)
|
|
||||||
input.setStore("lsp", [])
|
|
||||||
if (loading) input.setStore("status", "partial")
|
|
||||||
|
|
||||||
const fast = [
|
const blockingRequests = {
|
||||||
() =>
|
project: () => input.sdk.project.current().then((x) => input.setStore("project", x.data!.id)),
|
||||||
seededProject
|
provider: () =>
|
||||||
? Promise.resolve()
|
input.sdk.provider.list().then((x) => {
|
||||||
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
|
input.setStore("provider", normalizeProviderList(x.data!))
|
||||||
() => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))),
|
|
||||||
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
|
|
||||||
() =>
|
|
||||||
seededPath
|
|
||||||
? Promise.resolve()
|
|
||||||
: retry(() =>
|
|
||||||
input.sdk.path.get().then((x) => {
|
|
||||||
input.setStore("path", x.data!)
|
|
||||||
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
|
||||||
if (next) input.setStore("project", next)
|
|
||||||
}),
|
}),
|
||||||
),
|
agent: () => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? [])),
|
||||||
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
config: () => input.sdk.config.get().then((x) => input.setStore("config", x.data!)),
|
||||||
() =>
|
}
|
||||||
retry(() =>
|
|
||||||
|
try {
|
||||||
|
await Promise.all(Object.values(blockingRequests).map((p) => retry(p)))
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to bootstrap instance", err)
|
||||||
|
const project = getFilename(input.directory)
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||||
|
description: formatServerError(err, input.translate),
|
||||||
|
})
|
||||||
|
input.setStore("status", "partial")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.store.status !== "complete") input.setStore("status", "partial")
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
input.sdk.path.get().then((x) => input.setStore("path", x.data!)),
|
||||||
|
input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])),
|
||||||
|
input.sdk.session.status().then((x) => input.setStore("session_status", x.data!)),
|
||||||
|
input.loadSessions(input.directory),
|
||||||
|
input.sdk.mcp.status().then((x) => input.setStore("mcp", x.data!)),
|
||||||
|
input.sdk.lsp.status().then((x) => input.setStore("lsp", x.data!)),
|
||||||
input.sdk.vcs.get().then((x) => {
|
input.sdk.vcs.get().then((x) => {
|
||||||
const next = x.data ?? input.store.vcs
|
const next = x.data ?? input.store.vcs
|
||||||
input.setStore("vcs", next)
|
input.setStore("vcs", next)
|
||||||
if (next?.branch) input.vcsCache.setStore("value", next)
|
if (next?.branch) input.vcsCache.setStore("value", next)
|
||||||
}),
|
}),
|
||||||
),
|
|
||||||
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.permission.list().then((x) => {
|
input.sdk.permission.list().then((x) => {
|
||||||
const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
|
|
||||||
const grouped = groupBySession(
|
const grouped = groupBySession(
|
||||||
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
||||||
)
|
)
|
||||||
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
for (const sessionID of Object.keys(input.store.permission)) {
|
for (const sessionID of Object.keys(input.store.permission)) {
|
||||||
if (grouped[sessionID]) continue
|
if (grouped[sessionID]) continue
|
||||||
@@ -270,16 +179,10 @@ export async function bootstrapDirectory(input: {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.question.list().then((x) => {
|
input.sdk.question.list().then((x) => {
|
||||||
const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
|
|
||||||
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
||||||
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
for (const sessionID of Object.keys(input.store.question)) {
|
for (const sessionID of Object.keys(input.store.question)) {
|
||||||
if (grouped[sessionID]) continue
|
if (grouped[sessionID]) continue
|
||||||
@@ -295,64 +198,9 @@ export async function bootstrapDirectory(input: {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
)
|
]).then(() => {
|
||||||
}),
|
input.setStore("status", "complete")
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
const slow = [
|
|
||||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.mcp.status().then((x) => {
|
|
||||||
input.setStore("mcp", x.data!)
|
|
||||||
input.setStore("mcp_ready", true)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
const errs = errors(await runAll(fast))
|
|
||||||
if (errs.length > 0) {
|
|
||||||
console.error("Failed to bootstrap instance", errs[0])
|
|
||||||
const project = getFilename(input.directory)
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
|
||||||
description: formatServerError(errs[0], input.translate),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await waitForPaint()
|
|
||||||
const slowErrs = errors(await runAll(slow))
|
|
||||||
if (slowErrs.length > 0) {
|
|
||||||
console.error("Failed to finish bootstrap instance", slowErrs[0])
|
|
||||||
const project = getFilename(input.directory)
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
|
||||||
description: formatServerError(slowErrs[0], input.translate),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
|
|
||||||
|
|
||||||
const rev = (providerRev.get(input.directory) ?? 0) + 1
|
|
||||||
providerRev.set(input.directory, rev)
|
|
||||||
void retry(() => input.sdk.provider.list())
|
|
||||||
.then((x) => {
|
|
||||||
if (providerRev.get(input.directory) !== rev) return
|
|
||||||
input.setStore("provider", normalizeProviderList(x.data!))
|
|
||||||
input.setStore("provider_ready", true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (providerRev.get(input.directory) !== rev) return
|
|
||||||
console.error("Failed to refresh provider list", err)
|
|
||||||
const project = getFilename(input.directory)
|
|
||||||
showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
|
||||||
description: formatServerError(err, input.translate),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,6 @@ export function createChildStoreManager(input: {
|
|||||||
project: "",
|
project: "",
|
||||||
projectMeta: initialMeta,
|
projectMeta: initialMeta,
|
||||||
icon: initialIcon,
|
icon: initialIcon,
|
||||||
provider_ready: false,
|
|
||||||
provider: { all: [], connected: [], default: {} },
|
provider: { all: [], connected: [], default: {} },
|
||||||
config: {},
|
config: {},
|
||||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||||
@@ -174,9 +173,7 @@ export function createChildStoreManager(input: {
|
|||||||
todo: {},
|
todo: {},
|
||||||
permission: {},
|
permission: {},
|
||||||
question: {},
|
question: {},
|
||||||
mcp_ready: false,
|
|
||||||
mcp: {},
|
mcp: {},
|
||||||
lsp_ready: false,
|
|
||||||
lsp: [],
|
lsp: [],
|
||||||
vcs: vcsStore.value,
|
vcs: vcsStore.value,
|
||||||
limit: 5,
|
limit: 5,
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import type { State, VcsCache } from "./types"
|
|||||||
import { trimSessions } from "./session-trim"
|
import { trimSessions } from "./session-trim"
|
||||||
import { dropSessionCaches } from "./session-cache"
|
import { dropSessionCaches } from "./session-cache"
|
||||||
|
|
||||||
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
|
||||||
|
|
||||||
export function applyGlobalEvent(input: {
|
export function applyGlobalEvent(input: {
|
||||||
event: { type: string; properties?: unknown }
|
event: { type: string; properties?: unknown }
|
||||||
project: Project[]
|
project: Project[]
|
||||||
@@ -213,7 +211,6 @@ export function applyDirectoryEvent(input: {
|
|||||||
}
|
}
|
||||||
case "message.part.updated": {
|
case "message.part.updated": {
|
||||||
const part = (event.properties as { part: Part }).part
|
const part = (event.properties as { part: Part }).part
|
||||||
if (SKIP_PARTS.has(part.type)) break
|
|
||||||
const parts = input.store.part[part.messageID]
|
const parts = input.store.part[part.messageID]
|
||||||
if (!parts) {
|
if (!parts) {
|
||||||
input.setStore("part", part.messageID, [part])
|
input.setStore("part", part.messageID, [part])
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export type State = {
|
|||||||
project: string
|
project: string
|
||||||
projectMeta: ProjectMeta | undefined
|
projectMeta: ProjectMeta | undefined
|
||||||
icon: string | undefined
|
icon: string | undefined
|
||||||
provider_ready: boolean
|
|
||||||
provider: ProviderListResponse
|
provider: ProviderListResponse
|
||||||
config: Config
|
config: Config
|
||||||
path: Path
|
path: Path
|
||||||
@@ -59,11 +58,9 @@ export type State = {
|
|||||||
question: {
|
question: {
|
||||||
[sessionID: string]: QuestionRequest[]
|
[sessionID: string]: QuestionRequest[]
|
||||||
}
|
}
|
||||||
mcp_ready: boolean
|
|
||||||
mcp: {
|
mcp: {
|
||||||
[name: string]: McpStatus
|
[name: string]: McpStatus
|
||||||
}
|
}
|
||||||
lsp_ready: boolean
|
|
||||||
lsp: LspStatus[]
|
lsp: LspStatus[]
|
||||||
vcs: VcsInfo | undefined
|
vcs: VcsInfo | undefined
|
||||||
limit: number
|
limit: number
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import type { Agent } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { normalizeAgentList } from "./utils"
|
|
||||||
|
|
||||||
const agent = (name = "build") =>
|
|
||||||
({
|
|
||||||
name,
|
|
||||||
mode: "primary",
|
|
||||||
permission: {},
|
|
||||||
options: {},
|
|
||||||
}) as Agent
|
|
||||||
|
|
||||||
describe("normalizeAgentList", () => {
|
|
||||||
test("keeps array payloads", () => {
|
|
||||||
expect(normalizeAgentList([agent("build"), agent("docs")])).toEqual([agent("build"), agent("docs")])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("wraps a single agent payload", () => {
|
|
||||||
expect(normalizeAgentList(agent("docs"))).toEqual([agent("docs")])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("extracts agents from keyed objects", () => {
|
|
||||||
expect(
|
|
||||||
normalizeAgentList({
|
|
||||||
build: agent("build"),
|
|
||||||
docs: agent("docs"),
|
|
||||||
}),
|
|
||||||
).toEqual([agent("build"), agent("docs")])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("drops invalid payloads", () => {
|
|
||||||
expect(normalizeAgentList({ name: "AbortError" })).toEqual([])
|
|
||||||
expect(normalizeAgentList([{ name: "build" }, agent("docs")])).toEqual([agent("docs")])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,21 +1,7 @@
|
|||||||
import type { Agent, Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client"
|
import type { Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client"
|
||||||
|
|
||||||
export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||||
|
|
||||||
function isAgent(input: unknown): input is Agent {
|
|
||||||
if (!input || typeof input !== "object") return false
|
|
||||||
const item = input as { name?: unknown; mode?: unknown }
|
|
||||||
if (typeof item.name !== "string") return false
|
|
||||||
return item.mode === "subagent" || item.mode === "primary" || item.mode === "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeAgentList(input: unknown): Agent[] {
|
|
||||||
if (Array.isArray(input)) return input.filter(isAgent)
|
|
||||||
if (isAgent(input)) return [input]
|
|
||||||
if (!input || typeof input !== "object") return []
|
|
||||||
return Object.values(input).filter(isAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeProviderList(input: ProviderListResponse): ProviderListResponse {
|
export function normalizeProviderList(input: ProviderListResponse): ProviderListResponse {
|
||||||
return {
|
return {
|
||||||
...input,
|
...input,
|
||||||
|
|||||||
@@ -1,10 +1,42 @@
|
|||||||
import * as i18n from "@solid-primitives/i18n"
|
import * as i18n from "@solid-primitives/i18n"
|
||||||
import { createEffect, createMemo, createResource } from "solid-js"
|
import { createEffect, createMemo } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { dict as en } from "@/i18n/en"
|
import { dict as en } from "@/i18n/en"
|
||||||
|
import { dict as zh } from "@/i18n/zh"
|
||||||
|
import { dict as zht } from "@/i18n/zht"
|
||||||
|
import { dict as ko } from "@/i18n/ko"
|
||||||
|
import { dict as de } from "@/i18n/de"
|
||||||
|
import { dict as es } from "@/i18n/es"
|
||||||
|
import { dict as fr } from "@/i18n/fr"
|
||||||
|
import { dict as da } from "@/i18n/da"
|
||||||
|
import { dict as ja } from "@/i18n/ja"
|
||||||
|
import { dict as pl } from "@/i18n/pl"
|
||||||
|
import { dict as ru } from "@/i18n/ru"
|
||||||
|
import { dict as ar } from "@/i18n/ar"
|
||||||
|
import { dict as no } from "@/i18n/no"
|
||||||
|
import { dict as br } from "@/i18n/br"
|
||||||
|
import { dict as th } from "@/i18n/th"
|
||||||
|
import { dict as bs } from "@/i18n/bs"
|
||||||
|
import { dict as tr } from "@/i18n/tr"
|
||||||
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
|
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
|
||||||
|
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
||||||
|
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
|
||||||
|
import { dict as uiKo } from "@opencode-ai/ui/i18n/ko"
|
||||||
|
import { dict as uiDe } from "@opencode-ai/ui/i18n/de"
|
||||||
|
import { dict as uiEs } from "@opencode-ai/ui/i18n/es"
|
||||||
|
import { dict as uiFr } from "@opencode-ai/ui/i18n/fr"
|
||||||
|
import { dict as uiDa } from "@opencode-ai/ui/i18n/da"
|
||||||
|
import { dict as uiJa } from "@opencode-ai/ui/i18n/ja"
|
||||||
|
import { dict as uiPl } from "@opencode-ai/ui/i18n/pl"
|
||||||
|
import { dict as uiRu } from "@opencode-ai/ui/i18n/ru"
|
||||||
|
import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
|
||||||
|
import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
|
||||||
|
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
|
||||||
|
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
|
||||||
|
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
|
||||||
|
import { dict as uiTr } from "@opencode-ai/ui/i18n/tr"
|
||||||
|
|
||||||
export type Locale =
|
export type Locale =
|
||||||
| "en"
|
| "en"
|
||||||
@@ -27,7 +59,6 @@ export type Locale =
|
|||||||
|
|
||||||
type RawDictionary = typeof en & typeof uiEn
|
type RawDictionary = typeof en & typeof uiEn
|
||||||
type Dictionary = i18n.Flatten<RawDictionary>
|
type Dictionary = i18n.Flatten<RawDictionary>
|
||||||
type Source = { dict: Record<string, string> }
|
|
||||||
|
|
||||||
function cookie(locale: Locale) {
|
function cookie(locale: Locale) {
|
||||||
return `oc_locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax`
|
return `oc_locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax`
|
||||||
@@ -94,43 +125,24 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const base = i18n.flatten({ ...en, ...uiEn })
|
const base = i18n.flatten({ ...en, ...uiEn })
|
||||||
const dicts = new Map<Locale, Dictionary>([["en", base]])
|
const DICT: Record<Locale, Dictionary> = {
|
||||||
|
en: base,
|
||||||
const merge = (app: Promise<Source>, ui: Promise<Source>) =>
|
zh: { ...base, ...i18n.flatten({ ...zh, ...uiZh }) },
|
||||||
Promise.all([app, ui]).then(([a, b]) => ({ ...base, ...i18n.flatten({ ...a.dict, ...b.dict }) }) as Dictionary)
|
zht: { ...base, ...i18n.flatten({ ...zht, ...uiZht }) },
|
||||||
|
ko: { ...base, ...i18n.flatten({ ...ko, ...uiKo }) },
|
||||||
const loaders: Record<Exclude<Locale, "en">, () => Promise<Dictionary>> = {
|
de: { ...base, ...i18n.flatten({ ...de, ...uiDe }) },
|
||||||
zh: () => merge(import("@/i18n/zh"), import("@opencode-ai/ui/i18n/zh")),
|
es: { ...base, ...i18n.flatten({ ...es, ...uiEs }) },
|
||||||
zht: () => merge(import("@/i18n/zht"), import("@opencode-ai/ui/i18n/zht")),
|
fr: { ...base, ...i18n.flatten({ ...fr, ...uiFr }) },
|
||||||
ko: () => merge(import("@/i18n/ko"), import("@opencode-ai/ui/i18n/ko")),
|
da: { ...base, ...i18n.flatten({ ...da, ...uiDa }) },
|
||||||
de: () => merge(import("@/i18n/de"), import("@opencode-ai/ui/i18n/de")),
|
ja: { ...base, ...i18n.flatten({ ...ja, ...uiJa }) },
|
||||||
es: () => merge(import("@/i18n/es"), import("@opencode-ai/ui/i18n/es")),
|
pl: { ...base, ...i18n.flatten({ ...pl, ...uiPl }) },
|
||||||
fr: () => merge(import("@/i18n/fr"), import("@opencode-ai/ui/i18n/fr")),
|
ru: { ...base, ...i18n.flatten({ ...ru, ...uiRu }) },
|
||||||
da: () => merge(import("@/i18n/da"), import("@opencode-ai/ui/i18n/da")),
|
ar: { ...base, ...i18n.flatten({ ...ar, ...uiAr }) },
|
||||||
ja: () => merge(import("@/i18n/ja"), import("@opencode-ai/ui/i18n/ja")),
|
no: { ...base, ...i18n.flatten({ ...no, ...uiNo }) },
|
||||||
pl: () => merge(import("@/i18n/pl"), import("@opencode-ai/ui/i18n/pl")),
|
br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
|
||||||
ru: () => merge(import("@/i18n/ru"), import("@opencode-ai/ui/i18n/ru")),
|
th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
|
||||||
ar: () => merge(import("@/i18n/ar"), import("@opencode-ai/ui/i18n/ar")),
|
bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
|
||||||
no: () => merge(import("@/i18n/no"), import("@opencode-ai/ui/i18n/no")),
|
tr: { ...base, ...i18n.flatten({ ...tr, ...uiTr }) },
|
||||||
br: () => merge(import("@/i18n/br"), import("@opencode-ai/ui/i18n/br")),
|
|
||||||
th: () => merge(import("@/i18n/th"), import("@opencode-ai/ui/i18n/th")),
|
|
||||||
bs: () => merge(import("@/i18n/bs"), import("@opencode-ai/ui/i18n/bs")),
|
|
||||||
tr: () => merge(import("@/i18n/tr"), import("@opencode-ai/ui/i18n/tr")),
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDict(locale: Locale) {
|
|
||||||
const hit = dicts.get(locale)
|
|
||||||
if (hit) return Promise.resolve(hit)
|
|
||||||
if (locale === "en") return Promise.resolve(base)
|
|
||||||
const load = loaders[locale]
|
|
||||||
return load().then((next: Dictionary) => {
|
|
||||||
dicts.set(locale, next)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadLocaleDict(locale: Locale) {
|
|
||||||
return loadDict(locale).then(() => undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
|
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
|
||||||
@@ -156,6 +168,27 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
|
|||||||
{ locale: "tr", match: (language) => language.startsWith("tr") },
|
{ locale: "tr", match: (language) => language.startsWith("tr") },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
|
||||||
|
const PARITY_CHECK: Record<Exclude<Locale, "en">, Record<ParityKey, string>> = {
|
||||||
|
zh,
|
||||||
|
zht,
|
||||||
|
ko,
|
||||||
|
de,
|
||||||
|
es,
|
||||||
|
fr,
|
||||||
|
da,
|
||||||
|
ja,
|
||||||
|
pl,
|
||||||
|
ru,
|
||||||
|
ar,
|
||||||
|
no,
|
||||||
|
br,
|
||||||
|
th,
|
||||||
|
bs,
|
||||||
|
tr,
|
||||||
|
}
|
||||||
|
void PARITY_CHECK
|
||||||
|
|
||||||
function detectLocale(): Locale {
|
function detectLocale(): Locale {
|
||||||
if (typeof navigator !== "object") return "en"
|
if (typeof navigator !== "object") return "en"
|
||||||
|
|
||||||
@@ -170,48 +203,27 @@ function detectLocale(): Locale {
|
|||||||
return "en"
|
return "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeLocale(value: string): Locale {
|
function normalizeLocale(value: string): Locale {
|
||||||
return LOCALES.includes(value as Locale) ? (value as Locale) : "en"
|
return LOCALES.includes(value as Locale) ? (value as Locale) : "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
function readStoredLocale() {
|
|
||||||
if (typeof localStorage !== "object") return
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem("opencode.global.dat:language")
|
|
||||||
if (!raw) return
|
|
||||||
const next = JSON.parse(raw) as { locale?: string }
|
|
||||||
if (typeof next?.locale !== "string") return
|
|
||||||
return normalizeLocale(next.locale)
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const warm = readStoredLocale() ?? detectLocale()
|
|
||||||
if (warm !== "en") void loadDict(warm)
|
|
||||||
|
|
||||||
export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
|
export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
|
||||||
name: "Language",
|
name: "Language",
|
||||||
init: (props: { locale?: Locale }) => {
|
init: () => {
|
||||||
const initial = props.locale ?? readStoredLocale() ?? detectLocale()
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
Persist.global("language", ["language.v1"]),
|
Persist.global("language", ["language.v1"]),
|
||||||
createStore({
|
createStore({
|
||||||
locale: initial,
|
locale: detectLocale() as Locale,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const locale = createMemo<Locale>(() => normalizeLocale(store.locale))
|
const locale = createMemo<Locale>(() => normalizeLocale(store.locale))
|
||||||
|
console.log("locale", locale())
|
||||||
const intl = createMemo(() => INTL[locale()])
|
const intl = createMemo(() => INTL[locale()])
|
||||||
|
|
||||||
const [dict] = createResource(locale, loadDict, {
|
const dict = createMemo<Dictionary>(() => DICT[locale()])
|
||||||
initialValue: dicts.get(initial) ?? base,
|
|
||||||
})
|
|
||||||
|
|
||||||
const t = i18n.translator(() => dict() ?? base, i18n.resolveTemplate) as (
|
const t = i18n.translator(dict, i18n.resolveTemplate)
|
||||||
key: keyof Dictionary,
|
|
||||||
params?: Record<string, string | number | boolean>,
|
|
||||||
) => string
|
|
||||||
|
|
||||||
const label = (value: Locale) => t(LABEL_KEY[value])
|
const label = (value: Locale) => t(LABEL_KEY[value])
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import { createScrollPersistence, type SessionScroll } from "./layout-scroll"
|
|||||||
import { createPathHelpers } from "./file/path"
|
import { createPathHelpers } from "./file/path"
|
||||||
|
|
||||||
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
||||||
const DEFAULT_SIDEBAR_WIDTH = 344
|
const DEFAULT_PANEL_WIDTH = 344
|
||||||
const DEFAULT_FILE_TREE_WIDTH = 200
|
|
||||||
const DEFAULT_SESSION_WIDTH = 600
|
const DEFAULT_SESSION_WIDTH = 600
|
||||||
const DEFAULT_TERMINAL_HEIGHT = 280
|
const DEFAULT_TERMINAL_HEIGHT = 280
|
||||||
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
||||||
@@ -162,11 +161,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
if (!isRecord(fileTree)) return fileTree
|
if (!isRecord(fileTree)) return fileTree
|
||||||
if (fileTree.tab === "changes" || fileTree.tab === "all") return fileTree
|
if (fileTree.tab === "changes" || fileTree.tab === "all") return fileTree
|
||||||
|
|
||||||
const width = typeof fileTree.width === "number" ? fileTree.width : DEFAULT_FILE_TREE_WIDTH
|
const width = typeof fileTree.width === "number" ? fileTree.width : DEFAULT_PANEL_WIDTH
|
||||||
return {
|
return {
|
||||||
...fileTree,
|
...fileTree,
|
||||||
opened: true,
|
opened: true,
|
||||||
width: width === 260 ? DEFAULT_FILE_TREE_WIDTH : width,
|
width: width === 260 ? DEFAULT_PANEL_WIDTH : width,
|
||||||
tab: "changes",
|
tab: "changes",
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -231,7 +230,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
createStore({
|
createStore({
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: false,
|
opened: false,
|
||||||
width: DEFAULT_SIDEBAR_WIDTH,
|
width: DEFAULT_PANEL_WIDTH,
|
||||||
workspaces: {} as Record<string, boolean>,
|
workspaces: {} as Record<string, boolean>,
|
||||||
workspacesDefault: false,
|
workspacesDefault: false,
|
||||||
},
|
},
|
||||||
@@ -244,8 +243,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
panelOpened: true,
|
panelOpened: true,
|
||||||
},
|
},
|
||||||
fileTree: {
|
fileTree: {
|
||||||
opened: false,
|
opened: true,
|
||||||
width: DEFAULT_FILE_TREE_WIDTH,
|
width: DEFAULT_PANEL_WIDTH,
|
||||||
tab: "changes" as "changes" | "all",
|
tab: "changes" as "changes" | "all",
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
@@ -629,32 +628,32 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
},
|
},
|
||||||
fileTree: {
|
fileTree: {
|
||||||
opened: createMemo(() => store.fileTree?.opened ?? true),
|
opened: createMemo(() => store.fileTree?.opened ?? true),
|
||||||
width: createMemo(() => store.fileTree?.width ?? DEFAULT_FILE_TREE_WIDTH),
|
width: createMemo(() => store.fileTree?.width ?? DEFAULT_PANEL_WIDTH),
|
||||||
tab: createMemo(() => store.fileTree?.tab ?? "changes"),
|
tab: createMemo(() => store.fileTree?.tab ?? "changes"),
|
||||||
setTab(tab: "changes" | "all") {
|
setTab(tab: "changes" | "all") {
|
||||||
if (!store.fileTree) {
|
if (!store.fileTree) {
|
||||||
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab })
|
setStore("fileTree", { opened: true, width: DEFAULT_PANEL_WIDTH, tab })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("fileTree", "tab", tab)
|
setStore("fileTree", "tab", tab)
|
||||||
},
|
},
|
||||||
open() {
|
open() {
|
||||||
if (!store.fileTree) {
|
if (!store.fileTree) {
|
||||||
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
|
setStore("fileTree", { opened: true, width: DEFAULT_PANEL_WIDTH, tab: "changes" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("fileTree", "opened", true)
|
setStore("fileTree", "opened", true)
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
if (!store.fileTree) {
|
if (!store.fileTree) {
|
||||||
setStore("fileTree", { opened: false, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
|
setStore("fileTree", { opened: false, width: DEFAULT_PANEL_WIDTH, tab: "changes" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("fileTree", "opened", false)
|
setStore("fileTree", "opened", false)
|
||||||
},
|
},
|
||||||
toggle() {
|
toggle() {
|
||||||
if (!store.fileTree) {
|
if (!store.fileTree) {
|
||||||
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
|
setStore("fileTree", { opened: true, width: DEFAULT_PANEL_WIDTH, tab: "changes" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("fileTree", "opened", (x) => !x)
|
setStore("fileTree", "opened", (x) => !x)
|
||||||
|
|||||||
@@ -390,18 +390,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (modelEnabled()) {
|
if (modelEnabled()) {
|
||||||
const probe = Symbol("model-probe")
|
|
||||||
|
|
||||||
modelProbe.bind(probe, {
|
|
||||||
setAgent: agent.set,
|
|
||||||
setModel: model.set,
|
|
||||||
setVariant: model.variant.set,
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const agent = result.agent.current()
|
const agent = result.agent.current()
|
||||||
const model = result.model.current()
|
const model = result.model.current()
|
||||||
modelProbe.set(probe, {
|
modelProbe.set({
|
||||||
dir: sdk.directory,
|
dir: sdk.directory,
|
||||||
sessionID: id(),
|
sessionID: id(),
|
||||||
last: store.last,
|
last: store.last,
|
||||||
@@ -419,20 +411,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
pick: scope(),
|
pick: scope(),
|
||||||
base: undefined,
|
base: undefined,
|
||||||
current: store.current,
|
current: store.current,
|
||||||
variants: result.model.variant.list(),
|
|
||||||
models: result.model
|
|
||||||
.list()
|
|
||||||
.filter((item) => result.model.visible({ providerID: item.provider.id, modelID: item.id }))
|
|
||||||
.map((item) => ({
|
|
||||||
providerID: item.provider.id,
|
|
||||||
modelID: item.id,
|
|
||||||
name: item.name,
|
|
||||||
})),
|
|
||||||
agents: result.agent.list().map((item) => ({ name: item.name })),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => modelProbe.clear(probe))
|
onCleanup(() => modelProbe.clear())
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { base64Encode } from "@opencode-ai/util/encode"
|
|||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { playSoundById } from "@/utils/sound"
|
import { playSound, soundSrc } from "@/utils/sound"
|
||||||
|
|
||||||
type NotificationBase = {
|
type NotificationBase = {
|
||||||
directory?: string
|
directory?: string
|
||||||
@@ -234,7 +234,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
if (session.parentID) return
|
if (session.parentID) return
|
||||||
|
|
||||||
if (settings.sounds.agentEnabled()) {
|
if (settings.sounds.agentEnabled()) {
|
||||||
void playSoundById(settings.sounds.agent())
|
playSound(soundSrc(settings.sounds.agent()))
|
||||||
}
|
}
|
||||||
|
|
||||||
append({
|
append({
|
||||||
@@ -263,7 +263,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
if (session?.parentID) return
|
if (session?.parentID) return
|
||||||
|
|
||||||
if (settings.sounds.errorsEnabled()) {
|
if (settings.sounds.errorsEnabled()) {
|
||||||
void playSoundById(settings.sounds.errors())
|
playSound(soundSrc(settings.sounds.errors()))
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = "error" in event.properties ? event.properties.error : undefined
|
const error = "error" in event.properties ? event.properties.error : undefined
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export interface Settings {
|
|||||||
appearance: {
|
appearance: {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
font: string
|
font: string
|
||||||
uiFont: string
|
|
||||||
}
|
}
|
||||||
keybinds: Record<string, string>
|
keybinds: Record<string, string>
|
||||||
permissions: {
|
permissions: {
|
||||||
@@ -43,56 +42,13 @@ export interface Settings {
|
|||||||
sounds: SoundSettings
|
sounds: SoundSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
export const monoDefault = "IBM Plex Mono"
|
|
||||||
export const sansDefault = "Inter"
|
|
||||||
|
|
||||||
const monoFallback =
|
|
||||||
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
||||||
const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
||||||
|
|
||||||
const monoBase = `"${monoDefault}", "IBM Plex Mono Fallback", ${monoFallback}`
|
|
||||||
const sansBase = `"${sansDefault}", "Inter Fallback", ${sansFallback}`
|
|
||||||
const monoKey = "ibm-plex-mono"
|
|
||||||
|
|
||||||
function input(font: string | undefined, key?: string) {
|
|
||||||
if (!font || font === key || !font.trim()) return ""
|
|
||||||
return font
|
|
||||||
}
|
|
||||||
|
|
||||||
function family(font: string) {
|
|
||||||
if (/^[\w-]+$/.test(font)) return font
|
|
||||||
return `"${font.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
function stack(font: string | undefined, base: string, key?: string) {
|
|
||||||
const value = input(font, key).trim()
|
|
||||||
if (!value) return base
|
|
||||||
return `${family(value)}, ${base}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function monoInput(font: string | undefined) {
|
|
||||||
return input(font, monoKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sansInput(font: string | undefined) {
|
|
||||||
return input(font)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function monoFontFamily(font: string | undefined) {
|
|
||||||
return stack(font, monoBase, monoKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sansFontFamily(font: string | undefined) {
|
|
||||||
return stack(font, sansBase)
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
general: {
|
general: {
|
||||||
autoSave: true,
|
autoSave: true,
|
||||||
releaseNotes: true,
|
releaseNotes: true,
|
||||||
followup: "steer",
|
followup: "steer",
|
||||||
showReasoningSummaries: false,
|
showReasoningSummaries: false,
|
||||||
shellToolPartsExpanded: false,
|
shellToolPartsExpanded: true,
|
||||||
editToolPartsExpanded: false,
|
editToolPartsExpanded: false,
|
||||||
},
|
},
|
||||||
updates: {
|
updates: {
|
||||||
@@ -100,8 +56,7 @@ const defaultSettings: Settings = {
|
|||||||
},
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
font: "",
|
font: "ibm-plex-mono",
|
||||||
uiFont: "",
|
|
||||||
},
|
},
|
||||||
keybinds: {},
|
keybinds: {},
|
||||||
permissions: {
|
permissions: {
|
||||||
@@ -122,6 +77,29 @@ const defaultSettings: Settings = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const monoFallback =
|
||||||
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
||||||
|
|
||||||
|
const monoFonts: Record<string, string> = {
|
||||||
|
"ibm-plex-mono": `"IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"cascadia-code": `"Cascadia Code Nerd Font", "Cascadia Code NF", "Cascadia Mono NF", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"fira-code": `"Fira Code Nerd Font", "FiraMono Nerd Font", "FiraMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
hack: `"Hack Nerd Font", "Hack Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
inconsolata: `"Inconsolata Nerd Font", "Inconsolata Nerd Font Mono","IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"intel-one-mono": `"Intel One Mono Nerd Font", "IntoneMono Nerd Font", "IntoneMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
iosevka: `"Iosevka Nerd Font", "Iosevka Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"jetbrains-mono": `"JetBrains Mono Nerd Font", "JetBrainsMono Nerd Font Mono", "JetBrainsMonoNL Nerd Font", "JetBrainsMonoNL Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"meslo-lgs": `"Meslo LGS Nerd Font", "MesloLGS Nerd Font", "MesloLGM Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"roboto-mono": `"Roboto Mono Nerd Font", "RobotoMono Nerd Font", "RobotoMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"source-code-pro": `"Source Code Pro Nerd Font", "SauceCodePro Nerd Font", "SauceCodePro Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"ubuntu-mono": `"Ubuntu Mono Nerd Font", "UbuntuMono Nerd Font", "UbuntuMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
"geist-mono": `"GeistMono Nerd Font", "GeistMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function monoFontFamily(font: string | undefined) {
|
||||||
|
return monoFonts[font ?? defaultSettings.appearance.font] ?? monoFonts[defaultSettings.appearance.font]
|
||||||
|
}
|
||||||
|
|
||||||
function withFallback<T>(read: () => T | undefined, fallback: T) {
|
function withFallback<T>(read: () => T | undefined, fallback: T) {
|
||||||
return createMemo(() => read() ?? fallback)
|
return createMemo(() => read() ?? fallback)
|
||||||
}
|
}
|
||||||
@@ -133,9 +111,7 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
const root = document.documentElement
|
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
|
||||||
root.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
|
|
||||||
root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.uiFont))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -191,11 +167,7 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
},
|
},
|
||||||
font: withFallback(() => store.appearance?.font, defaultSettings.appearance.font),
|
font: withFallback(() => store.appearance?.font, defaultSettings.appearance.font),
|
||||||
setFont(value: string) {
|
setFont(value: string) {
|
||||||
setStore("appearance", "font", value.trim() ? value : "")
|
setStore("appearance", "font", value)
|
||||||
},
|
|
||||||
uiFont: withFallback(() => store.appearance?.uiFont, defaultSettings.appearance.uiFont),
|
|
||||||
setUIFont(value: string) {
|
|
||||||
setStore("appearance", "uiFont", value.trim() ? value : "")
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
keybinds: {
|
keybinds: {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import { useSDK } from "./sdk"
|
|||||||
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
||||||
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
import { SESSION_CACHE_LIMIT, dropSessionCaches, pickSessionCacheEvictions } from "./global-sync/session-cache"
|
||||||
|
|
||||||
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
|
||||||
|
|
||||||
function sortParts(parts: Part[]) {
|
function sortParts(parts: Part[]) {
|
||||||
return parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
|
return parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
|
||||||
}
|
}
|
||||||
@@ -180,8 +178,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
return globalSync.child(directory)
|
return globalSync.child(directory)
|
||||||
}
|
}
|
||||||
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
|
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
|
||||||
const initialMessagePageSize = 80
|
const messagePageSize = 200
|
||||||
const historyMessagePageSize = 200
|
|
||||||
const inflight = new Map<string, Promise<void>>()
|
const inflight = new Map<string, Promise<void>>()
|
||||||
const inflightDiff = new Map<string, Promise<void>>()
|
const inflightDiff = new Map<string, Promise<void>>()
|
||||||
const inflightTodo = new Map<string, Promise<void>>()
|
const inflightTodo = new Map<string, Promise<void>>()
|
||||||
@@ -339,8 +336,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
batch(() => {
|
batch(() => {
|
||||||
input.setStore("message", input.sessionID, reconcile(message, { key: "id" }))
|
input.setStore("message", input.sessionID, reconcile(message, { key: "id" }))
|
||||||
for (const p of next.part) {
|
for (const p of next.part) {
|
||||||
const filtered = p.part.filter((x) => !SKIP_PARTS.has(x.type))
|
input.setStore("part", p.id, p.part)
|
||||||
if (filtered.length) input.setStore("part", p.id, filtered)
|
|
||||||
}
|
}
|
||||||
setMeta("limit", key, message.length)
|
setMeta("limit", key, message.length)
|
||||||
setMeta("cursor", key, next.cursor)
|
setMeta("cursor", key, next.cursor)
|
||||||
@@ -464,7 +460,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined
|
const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined
|
||||||
if (cached && hasSession && !opts?.force) return
|
if (cached && hasSession && !opts?.force) return
|
||||||
|
|
||||||
const limit = meta.limit[key] ?? initialMessagePageSize
|
const limit = meta.limit[key] ?? messagePageSize
|
||||||
const sessionReq =
|
const sessionReq =
|
||||||
hasSession && !opts?.force
|
hasSession && !opts?.force
|
||||||
? Promise.resolve()
|
? Promise.resolve()
|
||||||
@@ -561,7 +557,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
const [, setStore] = globalSync.child(directory)
|
const [, setStore] = globalSync.child(directory)
|
||||||
touch(directory, setStore, sessionID)
|
touch(directory, setStore, sessionID)
|
||||||
const key = keyFor(directory, sessionID)
|
const key = keyFor(directory, sessionID)
|
||||||
const step = count ?? historyMessagePageSize
|
const step = count ?? messagePageSize
|
||||||
if (meta.loading[key]) return
|
if (meta.loading[key]) return
|
||||||
if (meta.complete[key]) return
|
if (meta.complete[key]) return
|
||||||
const before = meta.cursor[key]
|
const before = meta.cursor[key]
|
||||||
|
|||||||
@@ -1,18 +1,45 @@
|
|||||||
const template = "Terminal {{number}}"
|
import { dict as ar } from "@/i18n/ar"
|
||||||
|
import { dict as br } from "@/i18n/br"
|
||||||
|
import { dict as bs } from "@/i18n/bs"
|
||||||
|
import { dict as da } from "@/i18n/da"
|
||||||
|
import { dict as de } from "@/i18n/de"
|
||||||
|
import { dict as en } from "@/i18n/en"
|
||||||
|
import { dict as es } from "@/i18n/es"
|
||||||
|
import { dict as fr } from "@/i18n/fr"
|
||||||
|
import { dict as ja } from "@/i18n/ja"
|
||||||
|
import { dict as ko } from "@/i18n/ko"
|
||||||
|
import { dict as no } from "@/i18n/no"
|
||||||
|
import { dict as pl } from "@/i18n/pl"
|
||||||
|
import { dict as ru } from "@/i18n/ru"
|
||||||
|
import { dict as th } from "@/i18n/th"
|
||||||
|
import { dict as tr } from "@/i18n/tr"
|
||||||
|
import { dict as zh } from "@/i18n/zh"
|
||||||
|
import { dict as zht } from "@/i18n/zht"
|
||||||
|
|
||||||
const numbered = [
|
const numbered = Array.from(
|
||||||
template,
|
new Set([
|
||||||
"محطة طرفية {{number}}",
|
en["terminal.title.numbered"],
|
||||||
"Терминал {{number}}",
|
ar["terminal.title.numbered"],
|
||||||
"ターミナル {{number}}",
|
br["terminal.title.numbered"],
|
||||||
"터미널 {{number}}",
|
bs["terminal.title.numbered"],
|
||||||
"เทอร์มินัล {{number}}",
|
da["terminal.title.numbered"],
|
||||||
"终端 {{number}}",
|
de["terminal.title.numbered"],
|
||||||
"終端機 {{number}}",
|
es["terminal.title.numbered"],
|
||||||
]
|
fr["terminal.title.numbered"],
|
||||||
|
ja["terminal.title.numbered"],
|
||||||
|
ko["terminal.title.numbered"],
|
||||||
|
no["terminal.title.numbered"],
|
||||||
|
pl["terminal.title.numbered"],
|
||||||
|
ru["terminal.title.numbered"],
|
||||||
|
th["terminal.title.numbered"],
|
||||||
|
tr["terminal.title.numbered"],
|
||||||
|
zh["terminal.title.numbered"],
|
||||||
|
zht["terminal.title.numbered"],
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
export function defaultTitle(number: number) {
|
export function defaultTitle(number: number) {
|
||||||
return template.replace("{{number}}", String(number))
|
return en["terminal.title.numbered"].replace("{{number}}", String(number))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDefaultTitle(title: string, number: number) {
|
export function isDefaultTitle(title: string, number: number) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function useProviders() {
|
|||||||
const providers = () => {
|
const providers = () => {
|
||||||
if (dir()) {
|
if (dir()) {
|
||||||
const [projectStore] = globalSync.child(dir())
|
const [projectStore] = globalSync.child(dir())
|
||||||
if (projectStore.provider_ready) return projectStore.provider
|
return projectStore.provider
|
||||||
}
|
}
|
||||||
return globalSync.data.provider
|
return globalSync.data.provider
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -564,10 +564,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "اختر ما إذا كان OpenCode يتبع سمة النظام أو الفاتح أو الداكن",
|
"settings.general.row.colorScheme.description": "اختر ما إذا كان OpenCode يتبع سمة النظام أو الفاتح أو الداكن",
|
||||||
"settings.general.row.theme.title": "السمة",
|
"settings.general.row.theme.title": "السمة",
|
||||||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||||
"settings.general.row.font.title": "خط الكود",
|
"settings.general.row.font.title": "الخط",
|
||||||
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية والطرفيات",
|
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
|
||||||
"settings.general.row.uiFont.title": "خط الواجهة",
|
|
||||||
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
|
|
||||||
"settings.general.row.followup.title": "سلوك المتابعة",
|
"settings.general.row.followup.title": "سلوك المتابعة",
|
||||||
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
|
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
|
||||||
"settings.general.row.followup.option.queue": "قائمة انتظار",
|
"settings.general.row.followup.option.queue": "قائمة انتظار",
|
||||||
@@ -594,6 +592,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "جارٍ التحقق...",
|
"settings.updates.action.checking": "جارٍ التحقق...",
|
||||||
"settings.updates.toast.latest.title": "أنت على آخر إصدار",
|
"settings.updates.toast.latest.title": "أنت على آخر إصدار",
|
||||||
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
|
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "بلا",
|
"sound.option.none": "بلا",
|
||||||
"sound.option.alert01": "تنبيه 01",
|
"sound.option.alert01": "تنبيه 01",
|
||||||
"sound.option.alert02": "تنبيه 02",
|
"sound.option.alert02": "تنبيه 02",
|
||||||
@@ -711,6 +722,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "تحميل مهارة بالاسم",
|
"settings.permissions.tool.skill.description": "تحميل مهارة بالاسم",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة",
|
"settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة",
|
||||||
|
"settings.permissions.tool.todoread.title": "قراءة المهام",
|
||||||
|
"settings.permissions.tool.todoread.description": "قراءة قائمة المهام",
|
||||||
"settings.permissions.tool.todowrite.title": "كتابة المهام",
|
"settings.permissions.tool.todowrite.title": "كتابة المهام",
|
||||||
"settings.permissions.tool.todowrite.description": "تحديث قائمة المهام",
|
"settings.permissions.tool.todowrite.description": "تحديث قائمة المهام",
|
||||||
"settings.permissions.tool.webfetch.title": "جلب الويب",
|
"settings.permissions.tool.webfetch.title": "جلب الويب",
|
||||||
|
|||||||
@@ -571,10 +571,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Escolha se o OpenCode segue o tema do sistema, claro ou escuro",
|
"settings.general.row.colorScheme.description": "Escolha se o OpenCode segue o tema do sistema, claro ou escuro",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
||||||
"settings.general.row.font.title": "Fonte de código",
|
"settings.general.row.font.title": "Fonte",
|
||||||
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código e terminais",
|
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
|
||||||
"settings.general.row.uiFont.title": "Fonte da interface",
|
|
||||||
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
|
|
||||||
"settings.general.row.followup.title": "Comportamento de acompanhamento",
|
"settings.general.row.followup.title": "Comportamento de acompanhamento",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
|
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
|
||||||
@@ -602,6 +600,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Verificando...",
|
"settings.updates.action.checking": "Verificando...",
|
||||||
"settings.updates.toast.latest.title": "Você está atualizado",
|
"settings.updates.toast.latest.title": "Você está atualizado",
|
||||||
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
|
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Nenhum",
|
"sound.option.none": "Nenhum",
|
||||||
"sound.option.alert01": "Alerta 01",
|
"sound.option.alert01": "Alerta 01",
|
||||||
"sound.option.alert02": "Alerta 02",
|
"sound.option.alert02": "Alerta 02",
|
||||||
@@ -721,6 +732,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Carregar uma habilidade por nome",
|
"settings.permissions.tool.skill.description": "Carregar uma habilidade por nome",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Executar consultas de servidor de linguagem",
|
"settings.permissions.tool.lsp.description": "Executar consultas de servidor de linguagem",
|
||||||
|
"settings.permissions.tool.todoread.title": "Ler Tarefas",
|
||||||
|
"settings.permissions.tool.todoread.description": "Ler a lista de tarefas",
|
||||||
"settings.permissions.tool.todowrite.title": "Escrever Tarefas",
|
"settings.permissions.tool.todowrite.title": "Escrever Tarefas",
|
||||||
"settings.permissions.tool.todowrite.description": "Atualizar a lista de tarefas",
|
"settings.permissions.tool.todowrite.description": "Atualizar a lista de tarefas",
|
||||||
"settings.permissions.tool.webfetch.title": "Buscar Web",
|
"settings.permissions.tool.webfetch.title": "Buscar Web",
|
||||||
|
|||||||
@@ -636,10 +636,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Odaberi da li OpenCode prati sistemsku, svijetlu ili tamnu temu",
|
"settings.general.row.colorScheme.description": "Odaberi da li OpenCode prati sistemsku, svijetlu ili tamnu temu",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
|
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
|
||||||
"settings.general.row.font.title": "Font za kod",
|
"settings.general.row.font.title": "Font",
|
||||||
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda i terminalima",
|
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
|
||||||
"settings.general.row.uiFont.title": "UI font",
|
|
||||||
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
|
|
||||||
"settings.general.row.followup.title": "Ponašanje nadovezivanja",
|
"settings.general.row.followup.title": "Ponašanje nadovezivanja",
|
||||||
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
|
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
|
||||||
"settings.general.row.followup.option.queue": "Red čekanja",
|
"settings.general.row.followup.option.queue": "Red čekanja",
|
||||||
@@ -669,6 +667,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Provjera...",
|
"settings.updates.action.checking": "Provjera...",
|
||||||
"settings.updates.toast.latest.title": "Sve je ažurno",
|
"settings.updates.toast.latest.title": "Sve je ažurno",
|
||||||
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
|
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Nijedan",
|
"sound.option.none": "Nijedan",
|
||||||
"sound.option.alert01": "Upozorenje 01",
|
"sound.option.alert01": "Upozorenje 01",
|
||||||
"sound.option.alert02": "Upozorenje 02",
|
"sound.option.alert02": "Upozorenje 02",
|
||||||
@@ -795,6 +806,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu",
|
"settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera",
|
"settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera",
|
||||||
|
"settings.permissions.tool.todoread.title": "Čitanje liste zadataka",
|
||||||
|
"settings.permissions.tool.todoread.description": "Čitanje liste zadataka",
|
||||||
"settings.permissions.tool.todowrite.title": "Ažuriranje liste zadataka",
|
"settings.permissions.tool.todowrite.title": "Ažuriranje liste zadataka",
|
||||||
"settings.permissions.tool.todowrite.description": "Ažuriraj listu zadataka",
|
"settings.permissions.tool.todowrite.description": "Ažuriraj listu zadataka",
|
||||||
"settings.permissions.tool.webfetch.title": "Web preuzimanje",
|
"settings.permissions.tool.webfetch.title": "Web preuzimanje",
|
||||||
|
|||||||
@@ -631,10 +631,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Vælg om OpenCode følger systemets, lyst eller mørkt tema",
|
"settings.general.row.colorScheme.description": "Vælg om OpenCode følger systemets, lyst eller mørkt tema",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
|
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
|
||||||
"settings.general.row.font.title": "Kode-skrifttype",
|
"settings.general.row.font.title": "Skrifttype",
|
||||||
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke og terminaler",
|
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
|
||||||
"settings.general.row.uiFont.title": "UI-skrifttype",
|
|
||||||
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
|
|
||||||
"settings.general.row.followup.title": "Opfølgningsadfærd",
|
"settings.general.row.followup.title": "Opfølgningsadfærd",
|
||||||
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
|
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
|
||||||
"settings.general.row.followup.option.queue": "Kø",
|
"settings.general.row.followup.option.queue": "Kø",
|
||||||
@@ -664,6 +662,19 @@ export const dict = {
|
|||||||
"settings.updates.toast.latest.title": "Du er opdateret",
|
"settings.updates.toast.latest.title": "Du er opdateret",
|
||||||
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
|
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
|
||||||
|
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Ingen",
|
"sound.option.none": "Ingen",
|
||||||
"sound.option.alert01": "Alarm 01",
|
"sound.option.alert01": "Alarm 01",
|
||||||
"sound.option.alert02": "Alarm 02",
|
"sound.option.alert02": "Alarm 02",
|
||||||
@@ -789,6 +800,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Indlæs en færdighed efter navn",
|
"settings.permissions.tool.skill.description": "Indlæs en færdighed efter navn",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Kør sprogserverforespørgsler",
|
"settings.permissions.tool.lsp.description": "Kør sprogserverforespørgsler",
|
||||||
|
"settings.permissions.tool.todoread.title": "Læs To-do",
|
||||||
|
"settings.permissions.tool.todoread.description": "Læs to-do listen",
|
||||||
"settings.permissions.tool.todowrite.title": "Skriv To-do",
|
"settings.permissions.tool.todowrite.title": "Skriv To-do",
|
||||||
"settings.permissions.tool.todowrite.description": "Opdater to-do listen",
|
"settings.permissions.tool.todowrite.description": "Opdater to-do listen",
|
||||||
"settings.permissions.tool.webfetch.title": "Webhentning",
|
"settings.permissions.tool.webfetch.title": "Webhentning",
|
||||||
|
|||||||
@@ -581,10 +581,8 @@ export const dict = {
|
|||||||
"Wählen Sie, ob OpenCode dem System-, hellen oder dunklen Thema folgt",
|
"Wählen Sie, ob OpenCode dem System-, hellen oder dunklen Thema folgt",
|
||||||
"settings.general.row.theme.title": "Thema",
|
"settings.general.row.theme.title": "Thema",
|
||||||
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
||||||
"settings.general.row.font.title": "Code-Schriftart",
|
"settings.general.row.font.title": "Schriftart",
|
||||||
"settings.general.row.font.description": "Die in Codeblöcken und Terminals verwendete Schriftart anpassen",
|
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
|
||||||
"settings.general.row.uiFont.title": "UI-Schriftart",
|
|
||||||
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
|
|
||||||
"settings.general.row.followup.title": "Verhalten bei Folgefragen",
|
"settings.general.row.followup.title": "Verhalten bei Folgefragen",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
|
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
|
||||||
@@ -613,6 +611,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Wird geprüft...",
|
"settings.updates.action.checking": "Wird geprüft...",
|
||||||
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
|
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
|
||||||
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
|
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Keine",
|
"sound.option.none": "Keine",
|
||||||
"sound.option.alert01": "Alarm 01",
|
"sound.option.alert01": "Alarm 01",
|
||||||
"sound.option.alert02": "Alarm 02",
|
"sound.option.alert02": "Alarm 02",
|
||||||
@@ -732,6 +743,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Eine Fähigkeit nach Namen laden",
|
"settings.permissions.tool.skill.description": "Eine Fähigkeit nach Namen laden",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Language-Server-Abfragen ausführen",
|
"settings.permissions.tool.lsp.description": "Language-Server-Abfragen ausführen",
|
||||||
|
"settings.permissions.tool.todoread.title": "Todo lesen",
|
||||||
|
"settings.permissions.tool.todoread.description": "Die Todo-Liste lesen",
|
||||||
"settings.permissions.tool.todowrite.title": "Todo schreiben",
|
"settings.permissions.tool.todowrite.title": "Todo schreiben",
|
||||||
"settings.permissions.tool.todowrite.description": "Die Todo-Liste aktualisieren",
|
"settings.permissions.tool.todowrite.description": "Die Todo-Liste aktualisieren",
|
||||||
"settings.permissions.tool.webfetch.title": "Web-Abruf",
|
"settings.permissions.tool.webfetch.title": "Web-Abruf",
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Toggle sidebar",
|
"command.sidebar.toggle": "Toggle sidebar",
|
||||||
"command.project.open": "Open project",
|
"command.project.open": "Open project",
|
||||||
"command.project.previous": "Previous project",
|
|
||||||
"command.project.next": "Next project",
|
|
||||||
"command.provider.connect": "Connect provider",
|
"command.provider.connect": "Connect provider",
|
||||||
"command.server.switch": "Switch server",
|
"command.server.switch": "Switch server",
|
||||||
"command.settings.open": "Open settings",
|
"command.settings.open": "Open settings",
|
||||||
@@ -276,7 +274,7 @@ export const dict = {
|
|||||||
"prompt.context.includeActiveFile": "Include active file",
|
"prompt.context.includeActiveFile": "Include active file",
|
||||||
"prompt.context.removeActiveFile": "Remove active file from context",
|
"prompt.context.removeActiveFile": "Remove active file from context",
|
||||||
"prompt.context.removeFile": "Remove file from context",
|
"prompt.context.removeFile": "Remove file from context",
|
||||||
"prompt.action.attachFile": "Add files",
|
"prompt.action.attachFile": "Add file",
|
||||||
"prompt.attachment.remove": "Remove attachment",
|
"prompt.attachment.remove": "Remove attachment",
|
||||||
"prompt.action.send": "Send",
|
"prompt.action.send": "Send",
|
||||||
"prompt.action.stop": "Stop",
|
"prompt.action.stop": "Stop",
|
||||||
@@ -729,10 +727,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Choose whether OpenCode follows the system, light, or dark theme",
|
"settings.general.row.colorScheme.description": "Choose whether OpenCode follows the system, light, or dark theme",
|
||||||
"settings.general.row.theme.title": "Theme",
|
"settings.general.row.theme.title": "Theme",
|
||||||
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
|
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
|
||||||
"settings.general.row.font.title": "Code Font",
|
"settings.general.row.font.title": "Font",
|
||||||
"settings.general.row.font.description": "Customise the font used in code blocks and terminals",
|
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
||||||
"settings.general.row.uiFont.title": "UI Font",
|
|
||||||
"settings.general.row.uiFont.description": "Customise the font used throughout the interface",
|
|
||||||
"settings.general.row.followup.title": "Follow-up behavior",
|
"settings.general.row.followup.title": "Follow-up behavior",
|
||||||
"settings.general.row.followup.description": "Choose whether follow-up prompts steer immediately or wait in a queue",
|
"settings.general.row.followup.description": "Choose whether follow-up prompts steer immediately or wait in a queue",
|
||||||
"settings.general.row.followup.option.queue": "Queue",
|
"settings.general.row.followup.option.queue": "Queue",
|
||||||
@@ -762,6 +758,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Checking...",
|
"settings.updates.action.checking": "Checking...",
|
||||||
"settings.updates.toast.latest.title": "You're up to date",
|
"settings.updates.toast.latest.title": "You're up to date",
|
||||||
"settings.updates.toast.latest.description": "You're running the latest version of OpenCode.",
|
"settings.updates.toast.latest.description": "You're running the latest version of OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "None",
|
"sound.option.none": "None",
|
||||||
"sound.option.alert01": "Alert 01",
|
"sound.option.alert01": "Alert 01",
|
||||||
"sound.option.alert02": "Alert 02",
|
"sound.option.alert02": "Alert 02",
|
||||||
@@ -889,6 +898,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Load a skill by name",
|
"settings.permissions.tool.skill.description": "Load a skill by name",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Run language server queries",
|
"settings.permissions.tool.lsp.description": "Run language server queries",
|
||||||
|
"settings.permissions.tool.todoread.title": "Todo Read",
|
||||||
|
"settings.permissions.tool.todoread.description": "Read the todo list",
|
||||||
"settings.permissions.tool.todowrite.title": "Todo Write",
|
"settings.permissions.tool.todowrite.title": "Todo Write",
|
||||||
"settings.permissions.tool.todowrite.description": "Update the todo list",
|
"settings.permissions.tool.todowrite.description": "Update the todo list",
|
||||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||||
|
|||||||
@@ -639,10 +639,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Elige si OpenCode sigue el tema del sistema, claro u oscuro",
|
"settings.general.row.colorScheme.description": "Elige si OpenCode sigue el tema del sistema, claro u oscuro",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
|
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
|
||||||
"settings.general.row.font.title": "Fuente de código",
|
"settings.general.row.font.title": "Fuente",
|
||||||
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código y terminales",
|
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
|
||||||
"settings.general.row.uiFont.title": "Fuente de la interfaz",
|
|
||||||
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
|
|
||||||
"settings.general.row.followup.title": "Comportamiento de seguimiento",
|
"settings.general.row.followup.title": "Comportamiento de seguimiento",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
|
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
|
||||||
@@ -674,6 +672,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Buscando...",
|
"settings.updates.action.checking": "Buscando...",
|
||||||
"settings.updates.toast.latest.title": "Estás al día",
|
"settings.updates.toast.latest.title": "Estás al día",
|
||||||
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
|
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Ninguno",
|
"sound.option.none": "Ninguno",
|
||||||
"sound.option.alert01": "Alerta 01",
|
"sound.option.alert01": "Alerta 01",
|
||||||
"sound.option.alert02": "Alerta 02",
|
"sound.option.alert02": "Alerta 02",
|
||||||
@@ -802,6 +813,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Cargar una habilidad por nombre",
|
"settings.permissions.tool.skill.description": "Cargar una habilidad por nombre",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Ejecutar consultas de servidor de lenguaje",
|
"settings.permissions.tool.lsp.description": "Ejecutar consultas de servidor de lenguaje",
|
||||||
|
"settings.permissions.tool.todoread.title": "Leer Todo",
|
||||||
|
"settings.permissions.tool.todoread.description": "Leer la lista de tareas",
|
||||||
"settings.permissions.tool.todowrite.title": "Escribir Todo",
|
"settings.permissions.tool.todowrite.title": "Escribir Todo",
|
||||||
"settings.permissions.tool.todowrite.description": "Actualizar la lista de tareas",
|
"settings.permissions.tool.todowrite.description": "Actualizar la lista de tareas",
|
||||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||||
|
|||||||
@@ -578,10 +578,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Choisissez si OpenCode suit le thème système, clair ou sombre",
|
"settings.general.row.colorScheme.description": "Choisissez si OpenCode suit le thème système, clair ou sombre",
|
||||||
"settings.general.row.theme.title": "Thème",
|
"settings.general.row.theme.title": "Thème",
|
||||||
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
||||||
"settings.general.row.font.title": "Police de code",
|
"settings.general.row.font.title": "Police",
|
||||||
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code et les terminaux",
|
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
|
||||||
"settings.general.row.uiFont.title": "Police de l'interface",
|
|
||||||
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
|
|
||||||
"settings.general.row.followup.title": "Comportement de suivi",
|
"settings.general.row.followup.title": "Comportement de suivi",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
|
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
|
||||||
@@ -610,6 +608,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Vérification...",
|
"settings.updates.action.checking": "Vérification...",
|
||||||
"settings.updates.toast.latest.title": "Vous êtes à jour",
|
"settings.updates.toast.latest.title": "Vous êtes à jour",
|
||||||
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
|
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Aucun",
|
"sound.option.none": "Aucun",
|
||||||
"sound.option.alert01": "Alerte 01",
|
"sound.option.alert01": "Alerte 01",
|
||||||
"sound.option.alert02": "Alerte 02",
|
"sound.option.alert02": "Alerte 02",
|
||||||
@@ -730,6 +741,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Charger une compétence par son nom",
|
"settings.permissions.tool.skill.description": "Charger une compétence par son nom",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Exécuter des requêtes de serveur de langage",
|
"settings.permissions.tool.lsp.description": "Exécuter des requêtes de serveur de langage",
|
||||||
|
"settings.permissions.tool.todoread.title": "Lire Todo",
|
||||||
|
"settings.permissions.tool.todoread.description": "Lire la liste de tâches",
|
||||||
"settings.permissions.tool.todowrite.title": "Écrire Todo",
|
"settings.permissions.tool.todowrite.title": "Écrire Todo",
|
||||||
"settings.permissions.tool.todowrite.description": "Mettre à jour la liste de tâches",
|
"settings.permissions.tool.todowrite.description": "Mettre à jour la liste de tâches",
|
||||||
"settings.permissions.tool.webfetch.title": "Récupération Web",
|
"settings.permissions.tool.webfetch.title": "Récupération Web",
|
||||||
|
|||||||
@@ -568,10 +568,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "OpenCodeがシステム、ライト、またはダークテーマに従うかを選択します",
|
"settings.general.row.colorScheme.description": "OpenCodeがシステム、ライト、またはダークテーマに従うかを選択します",
|
||||||
"settings.general.row.theme.title": "テーマ",
|
"settings.general.row.theme.title": "テーマ",
|
||||||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||||
"settings.general.row.font.title": "コードフォント",
|
"settings.general.row.font.title": "フォント",
|
||||||
"settings.general.row.font.description": "コードブロックとターミナルで使用するフォントをカスタマイズします",
|
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
|
||||||
"settings.general.row.uiFont.title": "UIフォント",
|
|
||||||
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
|
|
||||||
"settings.general.row.followup.title": "フォローアップの動作",
|
"settings.general.row.followup.title": "フォローアップの動作",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
|
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
|
||||||
@@ -599,6 +597,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "確認中...",
|
"settings.updates.action.checking": "確認中...",
|
||||||
"settings.updates.toast.latest.title": "最新です",
|
"settings.updates.toast.latest.title": "最新です",
|
||||||
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "なし",
|
"sound.option.none": "なし",
|
||||||
"sound.option.alert01": "アラート 01",
|
"sound.option.alert01": "アラート 01",
|
||||||
"sound.option.alert02": "アラート 02",
|
"sound.option.alert02": "アラート 02",
|
||||||
@@ -716,6 +727,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "名前によるスキルの読み込み",
|
"settings.permissions.tool.skill.description": "名前によるスキルの読み込み",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "言語サーバークエリの実行",
|
"settings.permissions.tool.lsp.description": "言語サーバークエリの実行",
|
||||||
|
"settings.permissions.tool.todoread.title": "Todo読み込み",
|
||||||
|
"settings.permissions.tool.todoread.description": "Todoリストの読み込み",
|
||||||
"settings.permissions.tool.todowrite.title": "Todo書き込み",
|
"settings.permissions.tool.todowrite.title": "Todo書き込み",
|
||||||
"settings.permissions.tool.todowrite.description": "Todoリストの更新",
|
"settings.permissions.tool.todowrite.description": "Todoリストの更新",
|
||||||
"settings.permissions.tool.webfetch.title": "Web取得",
|
"settings.permissions.tool.webfetch.title": "Web取得",
|
||||||
|
|||||||
@@ -569,10 +569,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "OpenCode가 시스템, 라이트 또는 다크 테마를 따를지 선택하세요",
|
"settings.general.row.colorScheme.description": "OpenCode가 시스템, 라이트 또는 다크 테마를 따를지 선택하세요",
|
||||||
"settings.general.row.theme.title": "테마",
|
"settings.general.row.theme.title": "테마",
|
||||||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||||
"settings.general.row.font.title": "코드 글꼴",
|
"settings.general.row.font.title": "글꼴",
|
||||||
"settings.general.row.font.description": "코드 블록과 터미널에 사용되는 글꼴을 사용자 지정",
|
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
|
||||||
"settings.general.row.uiFont.title": "UI 글꼴",
|
|
||||||
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
|
|
||||||
"settings.general.row.followup.title": "후속 조치 동작",
|
"settings.general.row.followup.title": "후속 조치 동작",
|
||||||
"settings.general.row.followup.description": "후속 프롬프트를 즉시 실행할지 대기열에 넣을지 선택하세요",
|
"settings.general.row.followup.description": "후속 프롬프트를 즉시 실행할지 대기열에 넣을지 선택하세요",
|
||||||
"settings.general.row.followup.option.queue": "대기열",
|
"settings.general.row.followup.option.queue": "대기열",
|
||||||
@@ -599,6 +597,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "확인 중...",
|
"settings.updates.action.checking": "확인 중...",
|
||||||
"settings.updates.toast.latest.title": "최신 상태입니다",
|
"settings.updates.toast.latest.title": "최신 상태입니다",
|
||||||
"settings.updates.toast.latest.description": "현재 최신 버전의 OpenCode를 사용 중입니다.",
|
"settings.updates.toast.latest.description": "현재 최신 버전의 OpenCode를 사용 중입니다.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "없음",
|
"sound.option.none": "없음",
|
||||||
"sound.option.alert01": "알림 01",
|
"sound.option.alert01": "알림 01",
|
||||||
"sound.option.alert02": "알림 02",
|
"sound.option.alert02": "알림 02",
|
||||||
@@ -715,6 +726,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "이름으로 기술 로드",
|
"settings.permissions.tool.skill.description": "이름으로 기술 로드",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "언어 서버 쿼리 실행",
|
"settings.permissions.tool.lsp.description": "언어 서버 쿼리 실행",
|
||||||
|
"settings.permissions.tool.todoread.title": "할 일 읽기",
|
||||||
|
"settings.permissions.tool.todoread.description": "할 일 목록 읽기",
|
||||||
"settings.permissions.tool.todowrite.title": "할 일 쓰기",
|
"settings.permissions.tool.todowrite.title": "할 일 쓰기",
|
||||||
"settings.permissions.tool.todowrite.description": "할 일 목록 업데이트",
|
"settings.permissions.tool.todowrite.description": "할 일 목록 업데이트",
|
||||||
"settings.permissions.tool.webfetch.title": "웹 가져오기",
|
"settings.permissions.tool.webfetch.title": "웹 가져오기",
|
||||||
|
|||||||
@@ -639,10 +639,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Velg om OpenCode skal følge systemets, lyst eller mørkt tema",
|
"settings.general.row.colorScheme.description": "Velg om OpenCode skal følge systemets, lyst eller mørkt tema",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
|
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
|
||||||
"settings.general.row.font.title": "Kodefont",
|
"settings.general.row.font.title": "Skrift",
|
||||||
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker og terminaler",
|
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
|
||||||
"settings.general.row.uiFont.title": "UI-skrift",
|
|
||||||
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
|
|
||||||
"settings.general.row.followup.title": "Oppfølgingsadferd",
|
"settings.general.row.followup.title": "Oppfølgingsadferd",
|
||||||
"settings.general.row.followup.description": "Velg om oppfølgingsspørsmål skal kjøres umiddelbart eller vente i kø",
|
"settings.general.row.followup.description": "Velg om oppfølgingsspørsmål skal kjøres umiddelbart eller vente i kø",
|
||||||
"settings.general.row.followup.option.queue": "Kø",
|
"settings.general.row.followup.option.queue": "Kø",
|
||||||
@@ -670,6 +668,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Sjekker...",
|
"settings.updates.action.checking": "Sjekker...",
|
||||||
"settings.updates.toast.latest.title": "Du er oppdatert",
|
"settings.updates.toast.latest.title": "Du er oppdatert",
|
||||||
"settings.updates.toast.latest.description": "Du bruker den nyeste versjonen av OpenCode.",
|
"settings.updates.toast.latest.description": "Du bruker den nyeste versjonen av OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Ingen",
|
"sound.option.none": "Ingen",
|
||||||
"sound.option.alert01": "Varsel 01",
|
"sound.option.alert01": "Varsel 01",
|
||||||
"sound.option.alert02": "Varsel 02",
|
"sound.option.alert02": "Varsel 02",
|
||||||
@@ -796,6 +807,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Last en ferdighet etter navn",
|
"settings.permissions.tool.skill.description": "Last en ferdighet etter navn",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Kjør språkserverforespørsler",
|
"settings.permissions.tool.lsp.description": "Kjør språkserverforespørsler",
|
||||||
|
"settings.permissions.tool.todoread.title": "Les gjøremål",
|
||||||
|
"settings.permissions.tool.todoread.description": "Les gjøremålslisten",
|
||||||
"settings.permissions.tool.todowrite.title": "Skriv gjøremål",
|
"settings.permissions.tool.todowrite.title": "Skriv gjøremål",
|
||||||
"settings.permissions.tool.todowrite.description": "Oppdater gjøremålslisten",
|
"settings.permissions.tool.todowrite.description": "Oppdater gjøremålslisten",
|
||||||
"settings.permissions.tool.webfetch.title": "Webhenting",
|
"settings.permissions.tool.webfetch.title": "Webhenting",
|
||||||
|
|||||||
@@ -570,10 +570,8 @@ export const dict = {
|
|||||||
"Wybierz, czy OpenCode ma używać motywu systemowego, jasnego czy ciemnego",
|
"Wybierz, czy OpenCode ma używać motywu systemowego, jasnego czy ciemnego",
|
||||||
"settings.general.row.theme.title": "Motyw",
|
"settings.general.row.theme.title": "Motyw",
|
||||||
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
||||||
"settings.general.row.font.title": "Czcionka kodu",
|
"settings.general.row.font.title": "Czcionka",
|
||||||
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu i terminalach",
|
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
|
||||||
"settings.general.row.uiFont.title": "Czcionka interfejsu",
|
|
||||||
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
|
|
||||||
"settings.general.row.followup.title": "Zachowanie kontynuacji",
|
"settings.general.row.followup.title": "Zachowanie kontynuacji",
|
||||||
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
|
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
|
||||||
"settings.general.row.followup.option.queue": "Kolejka",
|
"settings.general.row.followup.option.queue": "Kolejka",
|
||||||
@@ -600,6 +598,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Sprawdzanie...",
|
"settings.updates.action.checking": "Sprawdzanie...",
|
||||||
"settings.updates.toast.latest.title": "Masz najnowszą wersję",
|
"settings.updates.toast.latest.title": "Masz najnowszą wersję",
|
||||||
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
|
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Brak",
|
"sound.option.none": "Brak",
|
||||||
"sound.option.alert01": "Alert 01",
|
"sound.option.alert01": "Alert 01",
|
||||||
"sound.option.alert02": "Alert 02",
|
"sound.option.alert02": "Alert 02",
|
||||||
@@ -718,6 +729,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Ładowanie umiejętności według nazwy",
|
"settings.permissions.tool.skill.description": "Ładowanie umiejętności według nazwy",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Uruchamianie zapytań serwera językowego",
|
"settings.permissions.tool.lsp.description": "Uruchamianie zapytań serwera językowego",
|
||||||
|
"settings.permissions.tool.todoread.title": "Odczyt Todo",
|
||||||
|
"settings.permissions.tool.todoread.description": "Odczyt listy zadań",
|
||||||
"settings.permissions.tool.todowrite.title": "Zapis Todo",
|
"settings.permissions.tool.todowrite.title": "Zapis Todo",
|
||||||
"settings.permissions.tool.todowrite.description": "Aktualizacja listy zadań",
|
"settings.permissions.tool.todowrite.description": "Aktualizacja listy zadań",
|
||||||
"settings.permissions.tool.webfetch.title": "Pobieranie z sieci",
|
"settings.permissions.tool.webfetch.title": "Pobieranie z sieci",
|
||||||
|
|||||||
@@ -636,10 +636,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "Выберите, следует ли OpenCode системной, светлой или тёмной теме",
|
"settings.general.row.colorScheme.description": "Выберите, следует ли OpenCode системной, светлой или тёмной теме",
|
||||||
"settings.general.row.theme.title": "Тема",
|
"settings.general.row.theme.title": "Тема",
|
||||||
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
|
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
|
||||||
"settings.general.row.font.title": "Шрифт кода",
|
"settings.general.row.font.title": "Шрифт",
|
||||||
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода и терминалах",
|
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
|
||||||
"settings.general.row.uiFont.title": "Шрифт интерфейса",
|
|
||||||
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
|
|
||||||
"settings.general.row.followup.title": "Поведение уточняющих вопросов",
|
"settings.general.row.followup.title": "Поведение уточняющих вопросов",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
|
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
|
||||||
@@ -670,6 +668,19 @@ export const dict = {
|
|||||||
"settings.updates.action.checking": "Проверка...",
|
"settings.updates.action.checking": "Проверка...",
|
||||||
"settings.updates.toast.latest.title": "У вас последняя версия",
|
"settings.updates.toast.latest.title": "У вас последняя версия",
|
||||||
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
|
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "Нет",
|
"sound.option.none": "Нет",
|
||||||
"sound.option.alert01": "Alert 01",
|
"sound.option.alert01": "Alert 01",
|
||||||
"sound.option.alert02": "Alert 02",
|
"sound.option.alert02": "Alert 02",
|
||||||
@@ -797,6 +808,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Загрузка навыка по имени",
|
"settings.permissions.tool.skill.description": "Загрузка навыка по имени",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Запросы к языковому серверу",
|
"settings.permissions.tool.lsp.description": "Запросы к языковому серверу",
|
||||||
|
"settings.permissions.tool.todoread.title": "Todo Read",
|
||||||
|
"settings.permissions.tool.todoread.description": "Чтение списка задач",
|
||||||
"settings.permissions.tool.todowrite.title": "Todo Write",
|
"settings.permissions.tool.todowrite.title": "Todo Write",
|
||||||
"settings.permissions.tool.todowrite.description": "Обновление списка задач",
|
"settings.permissions.tool.todowrite.description": "Обновление списка задач",
|
||||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||||
|
|||||||
@@ -630,10 +630,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "เลือกว่าจะให้ OpenCode ใช้ธีมตามระบบ สว่าง หรือมืด",
|
"settings.general.row.colorScheme.description": "เลือกว่าจะให้ OpenCode ใช้ธีมตามระบบ สว่าง หรือมืด",
|
||||||
"settings.general.row.theme.title": "ธีม",
|
"settings.general.row.theme.title": "ธีม",
|
||||||
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
||||||
"settings.general.row.font.title": "ฟอนต์โค้ด",
|
"settings.general.row.font.title": "ฟอนต์",
|
||||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ดและเทอร์มินัล",
|
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
||||||
"settings.general.row.uiFont.title": "ฟอนต์ UI",
|
|
||||||
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
|
|
||||||
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
|
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
|
||||||
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
|
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
|
||||||
"settings.general.row.followup.option.queue": "คิว",
|
"settings.general.row.followup.option.queue": "คิว",
|
||||||
@@ -661,6 +659,19 @@ export const dict = {
|
|||||||
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
||||||
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
||||||
|
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "ไม่มี",
|
"sound.option.none": "ไม่มี",
|
||||||
"sound.option.alert01": "เสียงเตือน 01",
|
"sound.option.alert01": "เสียงเตือน 01",
|
||||||
"sound.option.alert02": "เสียงเตือน 02",
|
"sound.option.alert02": "เสียงเตือน 02",
|
||||||
@@ -785,6 +796,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ",
|
"settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา",
|
"settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา",
|
||||||
|
"settings.permissions.tool.todoread.title": "อ่านรายการงาน",
|
||||||
|
"settings.permissions.tool.todoread.description": "อ่านรายการงาน",
|
||||||
"settings.permissions.tool.todowrite.title": "เขียนรายการงาน",
|
"settings.permissions.tool.todowrite.title": "เขียนรายการงาน",
|
||||||
"settings.permissions.tool.todowrite.description": "อัปเดตรายการงาน",
|
"settings.permissions.tool.todowrite.description": "อัปเดตรายการงาน",
|
||||||
"settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ",
|
"settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ",
|
||||||
|
|||||||
@@ -643,10 +643,8 @@ export const dict = {
|
|||||||
"OpenCode'un sistem, açık veya koyu temayı takip etip etmeyeceğini seçin",
|
"OpenCode'un sistem, açık veya koyu temayı takip etip etmeyeceğini seçin",
|
||||||
"settings.general.row.theme.title": "Tema",
|
"settings.general.row.theme.title": "Tema",
|
||||||
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
|
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
|
||||||
"settings.general.row.font.title": "Kod Yazı Tipi",
|
"settings.general.row.font.title": "Yazı Tipi",
|
||||||
"settings.general.row.font.description": "Kod bloklarında ve terminallerde kullanılan yazı tipini özelleştirin",
|
"settings.general.row.font.description": "Kod bloklarında kullanılan monospace yazı tipini özelleştirin",
|
||||||
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
|
|
||||||
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
|
|
||||||
"settings.general.row.followup.title": "Takip davranışı",
|
"settings.general.row.followup.title": "Takip davranışı",
|
||||||
"settings.general.row.followup.description":
|
"settings.general.row.followup.description":
|
||||||
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
|
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
|
||||||
@@ -679,6 +677,20 @@ export const dict = {
|
|||||||
"settings.updates.toast.latest.title": "Güncelsiniz",
|
"settings.updates.toast.latest.title": "Güncelsiniz",
|
||||||
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
|
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
|
||||||
|
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
|
|
||||||
"sound.option.none": "Yok",
|
"sound.option.none": "Yok",
|
||||||
"sound.option.alert01": "Uyarı 01",
|
"sound.option.alert01": "Uyarı 01",
|
||||||
"sound.option.alert02": "Uyarı 02",
|
"sound.option.alert02": "Uyarı 02",
|
||||||
@@ -804,6 +816,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "Ada göre bir beceri yükle",
|
"settings.permissions.tool.skill.description": "Ada göre bir beceri yükle",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır",
|
"settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır",
|
||||||
|
"settings.permissions.tool.todoread.title": "Görev Oku",
|
||||||
|
"settings.permissions.tool.todoread.description": "Görev listesini oku",
|
||||||
"settings.permissions.tool.todowrite.title": "Görev Yaz",
|
"settings.permissions.tool.todowrite.title": "Görev Yaz",
|
||||||
"settings.permissions.tool.todowrite.description": "Görev listesini güncelle",
|
"settings.permissions.tool.todowrite.description": "Görev listesini güncelle",
|
||||||
"settings.permissions.tool.webfetch.title": "Web Getir",
|
"settings.permissions.tool.webfetch.title": "Web Getir",
|
||||||
|
|||||||
@@ -630,10 +630,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "选择 OpenCode 跟随系统、浅色或深色主题",
|
"settings.general.row.colorScheme.description": "选择 OpenCode 跟随系统、浅色或深色主题",
|
||||||
"settings.general.row.theme.title": "主题",
|
"settings.general.row.theme.title": "主题",
|
||||||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||||
"settings.general.row.font.title": "代码字体",
|
"settings.general.row.font.title": "字体",
|
||||||
"settings.general.row.font.description": "自定义代码块和终端使用的字体",
|
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
||||||
"settings.general.row.uiFont.title": "界面字体",
|
|
||||||
"settings.general.row.uiFont.description": "自定义整个界面使用的字体",
|
|
||||||
"settings.general.row.followup.title": "跟进消息行为",
|
"settings.general.row.followup.title": "跟进消息行为",
|
||||||
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
|
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
|
||||||
"settings.general.row.followup.option.queue": "排队",
|
"settings.general.row.followup.option.queue": "排队",
|
||||||
@@ -659,6 +657,20 @@ export const dict = {
|
|||||||
"settings.updates.toast.latest.title": "已是最新版本",
|
"settings.updates.toast.latest.title": "已是最新版本",
|
||||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||||
|
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
|
|
||||||
"sound.option.none": "无",
|
"sound.option.none": "无",
|
||||||
"sound.option.alert01": "警报 01",
|
"sound.option.alert01": "警报 01",
|
||||||
"sound.option.alert02": "警报 02",
|
"sound.option.alert02": "警报 02",
|
||||||
@@ -783,6 +795,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "按名称加载技能",
|
"settings.permissions.tool.skill.description": "按名称加载技能",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "运行语言服务器查询",
|
"settings.permissions.tool.lsp.description": "运行语言服务器查询",
|
||||||
|
"settings.permissions.tool.todoread.title": "读取待办",
|
||||||
|
"settings.permissions.tool.todoread.description": "读取待办列表",
|
||||||
"settings.permissions.tool.todowrite.title": "更新待办",
|
"settings.permissions.tool.todowrite.title": "更新待办",
|
||||||
"settings.permissions.tool.todowrite.description": "更新待办列表",
|
"settings.permissions.tool.todowrite.description": "更新待办列表",
|
||||||
"settings.permissions.tool.webfetch.title": "网页获取",
|
"settings.permissions.tool.webfetch.title": "网页获取",
|
||||||
|
|||||||
@@ -625,10 +625,8 @@ export const dict = {
|
|||||||
"settings.general.row.colorScheme.description": "選擇 OpenCode 要跟隨系統、淺色或深色主題",
|
"settings.general.row.colorScheme.description": "選擇 OpenCode 要跟隨系統、淺色或深色主題",
|
||||||
"settings.general.row.theme.title": "主題",
|
"settings.general.row.theme.title": "主題",
|
||||||
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
|
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
|
||||||
"settings.general.row.font.title": "程式碼字型",
|
"settings.general.row.font.title": "字型",
|
||||||
"settings.general.row.font.description": "自訂程式碼區塊和終端機使用的字型",
|
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
|
||||||
"settings.general.row.uiFont.title": "介面字型",
|
|
||||||
"settings.general.row.uiFont.description": "自訂整個介面使用的字型",
|
|
||||||
"settings.general.row.followup.title": "後續追問行為",
|
"settings.general.row.followup.title": "後續追問行為",
|
||||||
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
|
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
|
||||||
"settings.general.row.followup.option.queue": "佇列",
|
"settings.general.row.followup.option.queue": "佇列",
|
||||||
@@ -656,6 +654,19 @@ export const dict = {
|
|||||||
"settings.updates.toast.latest.title": "已是最新版本",
|
"settings.updates.toast.latest.title": "已是最新版本",
|
||||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||||
|
|
||||||
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||||
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
|
"font.option.firaCode": "Fira Code",
|
||||||
|
"font.option.hack": "Hack",
|
||||||
|
"font.option.inconsolata": "Inconsolata",
|
||||||
|
"font.option.intelOneMono": "Intel One Mono",
|
||||||
|
"font.option.iosevka": "Iosevka",
|
||||||
|
"font.option.jetbrainsMono": "JetBrains Mono",
|
||||||
|
"font.option.mesloLgs": "Meslo LGS",
|
||||||
|
"font.option.robotoMono": "Roboto Mono",
|
||||||
|
"font.option.sourceCodePro": "Source Code Pro",
|
||||||
|
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||||
|
"font.option.geistMono": "Geist Mono",
|
||||||
"sound.option.none": "無",
|
"sound.option.none": "無",
|
||||||
"sound.option.alert01": "警報 01",
|
"sound.option.alert01": "警報 01",
|
||||||
"sound.option.alert02": "警報 02",
|
"sound.option.alert02": "警報 02",
|
||||||
@@ -779,6 +790,8 @@ export const dict = {
|
|||||||
"settings.permissions.tool.skill.description": "按名稱載入技能",
|
"settings.permissions.tool.skill.description": "按名稱載入技能",
|
||||||
"settings.permissions.tool.lsp.title": "LSP",
|
"settings.permissions.tool.lsp.title": "LSP",
|
||||||
"settings.permissions.tool.lsp.description": "執行語言伺服器查詢",
|
"settings.permissions.tool.lsp.description": "執行語言伺服器查詢",
|
||||||
|
"settings.permissions.tool.todoread.title": "讀取待辦",
|
||||||
|
"settings.permissions.tool.todoread.description": "讀取待辦清單",
|
||||||
"settings.permissions.tool.todowrite.title": "更新待辦",
|
"settings.permissions.tool.todowrite.title": "更新待辦",
|
||||||
"settings.permissions.tool.todowrite.description": "更新待辦清單",
|
"settings.permissions.tool.todowrite.description": "更新待辦清單",
|
||||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export { AppBaseProviders, AppInterface } from "./app"
|
export { AppBaseProviders, AppInterface } from "./app"
|
||||||
export { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, filePickerFilters } from "./constants/file-picker"
|
export { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, filePickerFilters } from "./constants/file-picker"
|
||||||
export { useCommand } from "./context/command"
|
export { useCommand } from "./context/command"
|
||||||
export { loadLocaleDict, normalizeLocale, type Locale } from "./context/language"
|
|
||||||
export { type DisplayBackend, type Platform, PlatformProvider } from "./context/platform"
|
export { type DisplayBackend, type Platform, PlatformProvider } from "./context/platform"
|
||||||
export { ServerConnection } from "./context/server"
|
export { ServerConnection } from "./context/server"
|
||||||
export { handleNotificationClick } from "./utils/notification-click"
|
export { handleNotificationClick } from "./utils/notification-click"
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { DataProvider } from "@opencode-ai/ui/context"
|
|||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { createEffect, createMemo, type ParentProps, Show } from "solid-js"
|
import { createMemo, createResource, type ParentProps, Show } from "solid-js"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { LocalProvider } from "@/context/local"
|
import { LocalProvider } from "@/context/local"
|
||||||
import { SDKProvider } from "@/context/sdk"
|
import { SDKProvider } from "@/context/sdk"
|
||||||
@@ -10,25 +11,10 @@ import { SyncProvider, useSync } from "@/context/sync"
|
|||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
|
|
||||||
function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
||||||
const location = useLocation()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const params = useParams()
|
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const slug = createMemo(() => base64Encode(props.directory))
|
const slug = createMemo(() => base64Encode(props.directory))
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const next = sync.data.path.directory
|
|
||||||
if (!next || next === props.directory) return
|
|
||||||
const path = location.pathname.slice(slug().length + 1)
|
|
||||||
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const id = params.id
|
|
||||||
if (!id) return
|
|
||||||
void sync.session.sync(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataProvider
|
<DataProvider
|
||||||
data={sync.data}
|
data={sync.data}
|
||||||
@@ -43,31 +29,50 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
|||||||
|
|
||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const location = useLocation()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const globalSDK = useGlobalSDK()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
let invalid = ""
|
let invalid = ""
|
||||||
|
|
||||||
const resolved = createMemo(() => {
|
const [resolved] = createResource(
|
||||||
if (!params.dir) return ""
|
() => {
|
||||||
return decode64(params.dir) ?? ""
|
if (params.dir) return [location.pathname, params.dir] as const
|
||||||
})
|
},
|
||||||
|
async ([pathname, b64Dir]) => {
|
||||||
|
const directory = decode64(b64Dir)
|
||||||
|
|
||||||
createEffect(() => {
|
if (!directory) {
|
||||||
const dir = params.dir
|
if (invalid === params.dir) return
|
||||||
if (!dir) return
|
invalid = b64Dir
|
||||||
if (resolved()) {
|
|
||||||
invalid = ""
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (invalid === dir) return
|
|
||||||
invalid = dir
|
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: language.t("common.requestFailed"),
|
title: language.t("common.requestFailed"),
|
||||||
description: language.t("directory.error.invalidUrl"),
|
description: language.t("directory.error.invalidUrl"),
|
||||||
})
|
})
|
||||||
navigate("/", { replace: true })
|
navigate("/", { replace: true })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return await globalSDK
|
||||||
|
.createClient({
|
||||||
|
directory,
|
||||||
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
.path.get()
|
||||||
|
.then((x) => {
|
||||||
|
const next = x.data?.directory ?? directory
|
||||||
|
invalid = ""
|
||||||
|
if (next === directory) return next
|
||||||
|
const path = pathname.slice(b64Dir.length + 1)
|
||||||
|
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
invalid = ""
|
||||||
|
return directory
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={resolved()} keyed>
|
<Show when={resolved()} keyed>
|
||||||
|
|||||||
@@ -113,14 +113,6 @@ export default function Home() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!sync.ready}>
|
|
||||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
|
||||||
<div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
|
|
||||||
<Button class="px-3" onClick={chooseProject}>
|
|
||||||
{language.t("command.project.open")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
<Icon name="folder-add-left" size="large" />
|
<Icon name="folder-add-left" size="large" />
|
||||||
|
|||||||
@@ -49,16 +49,21 @@ import { useNotification } from "@/context/notification"
|
|||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
import { retry } from "@opencode-ai/util/retry"
|
import { retry } from "@opencode-ai/util/retry"
|
||||||
import { playSoundById } from "@/utils/sound"
|
import { playSound, soundSrc } from "@/utils/sound"
|
||||||
import { createAim } from "@/utils/aim"
|
import { createAim } from "@/utils/aim"
|
||||||
import { setNavigate } from "@/utils/notification-click"
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
import { Worktree as WorktreeState } from "@/utils/worktree"
|
import { Worktree as WorktreeState } from "@/utils/worktree"
|
||||||
import { setSessionHandoff } from "@/pages/session/handoff"
|
import { setSessionHandoff } from "@/pages/session/handoff"
|
||||||
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
|
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
||||||
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
|
import { DialogSettings } from "@/components/dialog-settings"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
|
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||||
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
|
import { DialogEditProject } from "@/components/dialog-edit-project"
|
||||||
import { DebugBar } from "@/components/debug-bar"
|
import { DebugBar } from "@/components/debug-bar"
|
||||||
import { Titlebar } from "@/components/titlebar"
|
import { Titlebar } from "@/components/titlebar"
|
||||||
import { useServer } from "@/context/server"
|
import { useServer } from "@/context/server"
|
||||||
@@ -105,8 +110,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
const pageReady = createMemo(() => ready())
|
const pageReady = createMemo(() => ready())
|
||||||
|
|
||||||
let scrollContainerRef: HTMLDivElement | undefined
|
let scrollContainerRef: HTMLDivElement | undefined
|
||||||
let dialogRun = 0
|
|
||||||
let dialogDead = false
|
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
@@ -136,7 +139,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir,
|
dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const))
|
const availableThemeEntries = createMemo(() => Object.entries(theme.themes()))
|
||||||
const colorSchemeOrder: ColorScheme[] = ["system", "light", "dark"]
|
const colorSchemeOrder: ColorScheme[] = ["system", "light", "dark"]
|
||||||
const colorSchemeKey: Record<ColorScheme, "theme.scheme.system" | "theme.scheme.light" | "theme.scheme.dark"> = {
|
const colorSchemeKey: Record<ColorScheme, "theme.scheme.system" | "theme.scheme.light" | "theme.scheme.dark"> = {
|
||||||
system: "theme.scheme.system",
|
system: "theme.scheme.system",
|
||||||
@@ -198,8 +201,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
dialogDead = true
|
|
||||||
dialogRun += 1
|
|
||||||
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
|
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
|
||||||
clearTimeout(sortNowTimeout)
|
clearTimeout(sortNowTimeout)
|
||||||
if (sortNowInterval) clearInterval(sortNowInterval)
|
if (sortNowInterval) clearInterval(sortNowInterval)
|
||||||
@@ -210,22 +211,13 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const stop = () => setState("sizing", false)
|
const stop = () => setState("sizing", false)
|
||||||
const blur = () => reset()
|
|
||||||
const hide = () => {
|
|
||||||
if (document.visibilityState !== "hidden") return
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
window.addEventListener("pointerup", stop)
|
window.addEventListener("pointerup", stop)
|
||||||
window.addEventListener("pointercancel", stop)
|
window.addEventListener("pointercancel", stop)
|
||||||
window.addEventListener("blur", stop)
|
window.addEventListener("blur", stop)
|
||||||
window.addEventListener("blur", blur)
|
|
||||||
document.addEventListener("visibilitychange", hide)
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
window.removeEventListener("pointerup", stop)
|
window.removeEventListener("pointerup", stop)
|
||||||
window.removeEventListener("pointercancel", stop)
|
window.removeEventListener("pointercancel", stop)
|
||||||
window.removeEventListener("blur", stop)
|
window.removeEventListener("blur", stop)
|
||||||
window.removeEventListener("blur", blur)
|
|
||||||
document.removeEventListener("visibilitychange", hide)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -245,12 +237,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
navLeave.current = undefined
|
navLeave.current = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
disarm()
|
|
||||||
setState("hoverSession", undefined)
|
|
||||||
setHoverProject(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
const arm = () => {
|
const arm = () => {
|
||||||
if (layout.sidebar.opened()) return
|
if (layout.sidebar.opened()) return
|
||||||
if (state.hoverProject === undefined) return
|
if (state.hoverProject === undefined) return
|
||||||
@@ -319,7 +305,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
const clearSidebarHoverState = () => {
|
const clearSidebarHoverState = () => {
|
||||||
if (layout.sidebar.opened()) return
|
if (layout.sidebar.opened()) return
|
||||||
reset()
|
setState("hoverSession", undefined)
|
||||||
|
setHoverProject(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateWithSidebarReset = (href: string) => {
|
const navigateWithSidebarReset = (href: string) => {
|
||||||
@@ -335,9 +322,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + ids.length) % ids.length
|
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + direction + ids.length) % ids.length
|
||||||
const nextThemeId = ids[nextIndex]
|
const nextThemeId = ids[nextIndex]
|
||||||
theme.setTheme(nextThemeId)
|
theme.setTheme(nextThemeId)
|
||||||
|
const nextTheme = theme.themes()[nextThemeId]
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("toast.theme.title"),
|
title: language.t("toast.theme.title"),
|
||||||
description: theme.name(nextThemeId),
|
description: nextTheme?.name ?? nextThemeId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +480,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
if (e.details.type === "permission.asked") {
|
if (e.details.type === "permission.asked") {
|
||||||
if (settings.sounds.permissionsEnabled()) {
|
if (settings.sounds.permissionsEnabled()) {
|
||||||
void playSoundById(settings.sounds.permissions())
|
playSound(soundSrc(settings.sounds.permissions()))
|
||||||
}
|
}
|
||||||
if (settings.notifications.permissions()) {
|
if (settings.notifications.permissions()) {
|
||||||
void platform.notify(title, description, href)
|
void platform.notify(title, description, href)
|
||||||
@@ -948,28 +936,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
navigateToSession(session)
|
navigateToSession(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateProjectByOffset(offset: number) {
|
|
||||||
const projects = layout.projects.list()
|
|
||||||
if (projects.length === 0) return
|
|
||||||
|
|
||||||
const current = currentProject()?.worktree
|
|
||||||
const fallback = currentDir() ? projectRoot(currentDir()) : undefined
|
|
||||||
const active = current ?? fallback
|
|
||||||
const index = active ? projects.findIndex((project) => project.worktree === active) : -1
|
|
||||||
|
|
||||||
const target =
|
|
||||||
index === -1
|
|
||||||
? offset > 0
|
|
||||||
? projects[0]
|
|
||||||
: projects[projects.length - 1]
|
|
||||||
: projects[(index + offset + projects.length) % projects.length]
|
|
||||||
if (!target) return
|
|
||||||
|
|
||||||
// warm up child store to prevent flicker
|
|
||||||
globalSync.child(target.worktree)
|
|
||||||
openProject(target.worktree)
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateSessionByUnseen(offset: number) {
|
function navigateSessionByUnseen(offset: number) {
|
||||||
const sessions = currentSessions()
|
const sessions = currentSessions()
|
||||||
if (sessions.length === 0) return
|
if (sessions.length === 0) return
|
||||||
@@ -1036,20 +1002,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
keybind: "mod+o",
|
keybind: "mod+o",
|
||||||
onSelect: () => chooseProject(),
|
onSelect: () => chooseProject(),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "project.previous",
|
|
||||||
title: language.t("command.project.previous"),
|
|
||||||
category: language.t("command.category.project"),
|
|
||||||
keybind: "mod+alt+arrowup",
|
|
||||||
onSelect: () => navigateProjectByOffset(-1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "project.next",
|
|
||||||
title: language.t("command.project.next"),
|
|
||||||
category: language.t("command.category.project"),
|
|
||||||
keybind: "mod+alt+arrowdown",
|
|
||||||
onSelect: () => navigateProjectByOffset(1),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "provider.connect",
|
id: "provider.connect",
|
||||||
title: language.t("command.provider.connect"),
|
title: language.t("command.provider.connect"),
|
||||||
@@ -1152,10 +1104,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const [id] of availableThemeEntries()) {
|
for (const [id, definition] of availableThemeEntries()) {
|
||||||
commands.push({
|
commands.push({
|
||||||
id: `theme.set.${id}`,
|
id: `theme.set.${id}`,
|
||||||
title: language.t("command.theme.set", { theme: theme.name(id) }),
|
title: language.t("command.theme.set", { theme: definition.name ?? id }),
|
||||||
category: language.t("command.category.theme"),
|
category: language.t("command.category.theme"),
|
||||||
onSelect: () => theme.commitPreview(),
|
onSelect: () => theme.commitPreview(),
|
||||||
onHighlight: () => {
|
onHighlight: () => {
|
||||||
@@ -1206,27 +1158,15 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function connectProvider() {
|
function connectProvider() {
|
||||||
const run = ++dialogRun
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
void import("@/components/dialog-select-provider").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(() => <x.DialogSelectProvider />)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openServer() {
|
function openServer() {
|
||||||
const run = ++dialogRun
|
dialog.show(() => <DialogSelectServer />)
|
||||||
void import("@/components/dialog-select-server").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(() => <x.DialogSelectServer />)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSettings() {
|
function openSettings() {
|
||||||
const run = ++dialogRun
|
dialog.show(() => <DialogSettings />)
|
||||||
void import("@/components/dialog-settings").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(() => <x.DialogSettings />)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectRoot(directory: string) {
|
function projectRoot(directory: string) {
|
||||||
@@ -1453,13 +1393,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
layout.sidebar.toggleWorkspaces(project.worktree)
|
layout.sidebar.toggleWorkspaces(project.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
const showEditProjectDialog = (project: LocalProject) => {
|
const showEditProjectDialog = (project: LocalProject) => dialog.show(() => <DialogEditProject project={project} />)
|
||||||
const run = ++dialogRun
|
|
||||||
void import("@/components/dialog-edit-project").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(() => <x.DialogEditProject project={project} />)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function chooseProject() {
|
async function chooseProject() {
|
||||||
function resolve(result: string | string[] | null) {
|
function resolve(result: string | string[] | null) {
|
||||||
@@ -1480,14 +1414,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
resolve(result)
|
resolve(result)
|
||||||
} else {
|
} else {
|
||||||
const run = ++dialogRun
|
|
||||||
void import("@/components/dialog-select-directory").then((x) => {
|
|
||||||
if (dialogDead || dialogRun !== run) return
|
|
||||||
dialog.show(
|
dialog.show(
|
||||||
() => <x.DialogSelectDirectory multiple={true} onSelect={resolve} />,
|
() => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
|
||||||
() => resolve(null),
|
() => resolve(null),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1820,9 +1750,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
|
document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const side = createMemo(() => Math.max(layout.sidebar.width(), 244))
|
|
||||||
const panel = createMemo(() => Math.max(side() - 64, 0))
|
|
||||||
|
|
||||||
const loadedSessionDirs = new Set<string>()
|
const loadedSessionDirs = new Set<string>()
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -2014,10 +1941,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
onProjectMouseEnter: (worktree, event) => aim.enter(worktree, event),
|
onProjectMouseEnter: (worktree, event) => aim.enter(worktree, event),
|
||||||
onProjectMouseLeave: (worktree) => aim.leave(worktree),
|
onProjectMouseLeave: (worktree) => aim.leave(worktree),
|
||||||
onProjectFocus: (worktree) => aim.activate(worktree),
|
onProjectFocus: (worktree) => aim.activate(worktree),
|
||||||
onHoverOpenChanged: (worktree, hoverOpen) => {
|
|
||||||
if (!hoverOpen && state.hoverProject && state.hoverProject !== worktree) return
|
|
||||||
setState("hoverProject", hoverOpen ? worktree : undefined)
|
|
||||||
},
|
|
||||||
navigateToProject,
|
navigateToProject,
|
||||||
openSidebar: () => layout.sidebar.open(),
|
openSidebar: () => layout.sidebar.open(),
|
||||||
closeProject,
|
closeProject,
|
||||||
@@ -2099,7 +2022,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
"max-w-full overflow-hidden": panelProps.mobile,
|
"max-w-full overflow-hidden": panelProps.mobile,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: panelProps.mobile ? undefined : `${panel()}px`,
|
width: panelProps.mobile ? undefined : `${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
@@ -2162,11 +2085,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
data-action="project-menu"
|
data-action="project-menu"
|
||||||
data-project={slug()}
|
data-project={slug()}
|
||||||
class="shrink-0 size-6 rounded-md transition-opacity data-[expanded]:bg-surface-base-active"
|
class="shrink-0 size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
||||||
classList={{
|
classList={{
|
||||||
"opacity-100": panelProps.mobile || merged(),
|
"opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100": !panelProps.mobile,
|
||||||
"opacity-0 group-hover/project:opacity-100 group-focus-within/project:opacity-100 data-[expanded]:opacity-100":
|
|
||||||
!panelProps.mobile && !merged(),
|
|
||||||
}}
|
}}
|
||||||
aria-label={language.t("common.moreOptions")}
|
aria-label={language.t("common.moreOptions")}
|
||||||
/>
|
/>
|
||||||
@@ -2391,7 +2312,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
"absolute inset-y-0 left-0": true,
|
"absolute inset-y-0 left-0": true,
|
||||||
"z-10": true,
|
"z-10": true,
|
||||||
}}
|
}}
|
||||||
style={{ width: `${side()}px` }}
|
style={{ width: `${Math.max(layout.sidebar.width(), 244)}px` }}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
setState("nav", el)
|
setState("nav", el)
|
||||||
}}
|
}}
|
||||||
@@ -2406,28 +2327,25 @@ export default function Layout(props: ParentProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
||||||
</nav>
|
|
||||||
|
|
||||||
<Show when={layout.sidebar.opened()}>
|
<Show when={layout.sidebar.opened()}>
|
||||||
<div
|
<div onPointerDown={() => setState("sizing", true)}>
|
||||||
class="hidden xl:block absolute inset-y-0 z-30 w-0 overflow-visible"
|
|
||||||
style={{ left: `${side()}px` }}
|
|
||||||
onPointerDown={() => setState("sizing", true)}
|
|
||||||
>
|
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
size={layout.sidebar.width()}
|
size={layout.sidebar.width()}
|
||||||
min={244}
|
min={244}
|
||||||
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
|
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
|
||||||
|
collapseThreshold={244}
|
||||||
onResize={(w) => {
|
onResize={(w) => {
|
||||||
setState("sizing", true)
|
setState("sizing", true)
|
||||||
if (sizet !== undefined) clearTimeout(sizet)
|
if (sizet !== undefined) clearTimeout(sizet)
|
||||||
sizet = window.setTimeout(() => setState("sizing", false), 120)
|
sizet = window.setTimeout(() => setState("sizing", false), 120)
|
||||||
layout.sidebar.resize(w)
|
layout.sidebar.resize(w)
|
||||||
}}
|
}}
|
||||||
|
onCollapse={layout.sidebar.close}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="hidden xl:block pointer-events-none absolute top-0 right-0 z-0 border-t border-border-weaker-base"
|
class="hidden xl:block pointer-events-none absolute top-0 right-0 z-0 border-t border-border-weaker-base"
|
||||||
@@ -2468,7 +2386,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
!state.sizing,
|
!state.sizing,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"--main-left": layout.sidebar.opened() ? `${side()}px` : "4rem",
|
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<main
|
<main
|
||||||
@@ -2515,7 +2433,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||||
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||||
}}
|
}}
|
||||||
style={{ left: `calc(4rem + ${panel()}px)` }}
|
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
||||||
>
|
>
|
||||||
<div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} />
|
<div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const SessionRow = (props: {
|
|||||||
}): JSX.Element => (
|
}): JSX.Element => (
|
||||||
<A
|
<A
|
||||||
href={`/${props.slug}/session/${props.session.id}`}
|
href={`/${props.slug}/session/${props.session.id}`}
|
||||||
class={`flex items-center gap-1 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
||||||
onPointerDown={props.warmPress}
|
onPointerDown={props.warmPress}
|
||||||
onPointerEnter={props.warmHover}
|
onPointerEnter={props.warmHover}
|
||||||
onPointerLeave={props.cancelHoverPrefetch}
|
onPointerLeave={props.cancelHoverPrefetch}
|
||||||
@@ -115,6 +115,7 @@ const SessionRow = (props: {
|
|||||||
props.clearHoverProjectSoon()
|
props.clearHoverProjectSoon()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div class="flex items-center gap-1 w-full">
|
||||||
<div
|
<div
|
||||||
class="shrink-0 size-6 flex items-center justify-center"
|
class="shrink-0 size-6 flex items-center justify-center"
|
||||||
style={{ color: props.tint() ?? "var(--icon-interactive-base)" }}
|
style={{ color: props.tint() ?? "var(--icon-interactive-base)" }}
|
||||||
@@ -134,7 +135,10 @@ const SessionRow = (props: {
|
|||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-14-regular text-text-strong min-w-0 flex-1 truncate">{props.session.title}</span>
|
<span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
|
||||||
|
{props.session.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</A>
|
</A>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -153,30 +157,16 @@ const SessionHoverPreview = (props: {
|
|||||||
messageLabel: (message: Message) => string | undefined
|
messageLabel: (message: Message) => string | undefined
|
||||||
onMessageSelect: (message: Message) => void
|
onMessageSelect: (message: Message) => void
|
||||||
trigger: JSX.Element
|
trigger: JSX.Element
|
||||||
}): JSX.Element => {
|
}): JSX.Element => (
|
||||||
let ref: HTMLDivElement | undefined
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HoverCard
|
<HoverCard
|
||||||
openDelay={1000}
|
openDelay={1000}
|
||||||
closeDelay={props.sidebarHovering() ? 600 : 0}
|
closeDelay={props.sidebarHovering() ? 600 : 0}
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={16}
|
gutter={16}
|
||||||
shift={-2}
|
shift={-2}
|
||||||
trigger={
|
trigger={props.trigger}
|
||||||
<div ref={ref} class="min-w-0 w-full">
|
|
||||||
{props.trigger}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
open={props.hoverSession() === props.session.id}
|
open={props.hoverSession() === props.session.id}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => props.setHoverSession(open ? props.session.id : undefined)}
|
||||||
if (!open) {
|
|
||||||
props.setHoverSession(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!ref?.matches(":hover")) return
|
|
||||||
props.setHoverSession(props.session.id)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={props.hoverReady()}
|
when={props.hoverReady()}
|
||||||
@@ -195,7 +185,6 @@ const SessionHoverPreview = (props: {
|
|||||||
</Show>
|
</Show>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@@ -309,20 +298,13 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-session-id={props.session.id}
|
data-session-id={props.session.id}
|
||||||
class="group/session relative w-full min-w-0 rounded-md cursor-default pl-2 pr-3 transition-colors
|
class="group/session relative w-full rounded-md cursor-default pl-2 pr-3 transition-colors
|
||||||
hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[[data-expanded]]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
|
hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[[data-expanded]]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
|
||||||
>
|
>
|
||||||
<div class="flex min-w-0 items-center gap-1">
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<Show
|
<Show
|
||||||
when={hoverEnabled()}
|
when={hoverEnabled()}
|
||||||
fallback={
|
fallback={
|
||||||
<Tooltip
|
<Tooltip placement={props.mobile ? "bottom" : "right"} value={props.session.title} gutter={10}>
|
||||||
placement={props.mobile ? "bottom" : "right"}
|
|
||||||
value={props.session.title}
|
|
||||||
gutter={10}
|
|
||||||
class="min-w-0 w-full"
|
|
||||||
>
|
|
||||||
{item}
|
{item}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
@@ -349,15 +331,14 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
trigger={item}
|
trigger={item}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="shrink-0 overflow-hidden transition-[width,opacity]"
|
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
|
||||||
classList={{
|
classList={{
|
||||||
"w-6 opacity-100 pointer-events-auto": !!props.mobile,
|
"opacity-100 pointer-events-auto": !!props.mobile,
|
||||||
"w-0 opacity-0 pointer-events-none": !props.mobile,
|
"opacity-0 pointer-events-none": !props.mobile,
|
||||||
"group-hover/session:w-6 group-hover/session:opacity-100 group-hover/session:pointer-events-auto": true,
|
"group-hover/session:opacity-100 group-hover/session:pointer-events-auto": true,
|
||||||
"group-focus-within/session:w-6 group-focus-within/session:opacity-100 group-focus-within/session:pointer-events-auto": true,
|
"group-focus-within/session:opacity-100 group-focus-within/session:pointer-events-auto": true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip value={language.t("common.archive")} placement="top">
|
<Tooltip value={language.t("common.archive")} placement="top">
|
||||||
@@ -375,7 +356,6 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,26 +375,30 @@ export const NewSessionItem = (props: {
|
|||||||
<A
|
<A
|
||||||
href={`/${props.slug}/session`}
|
href={`/${props.slug}/session`}
|
||||||
end
|
end
|
||||||
class={`flex items-center gap-1 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.setHoverSession(undefined)
|
props.setHoverSession(undefined)
|
||||||
if (layout.sidebar.opened()) return
|
if (layout.sidebar.opened()) return
|
||||||
props.clearHoverProjectSoon()
|
props.clearHoverProjectSoon()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div class="flex items-center gap-1 w-full">
|
||||||
<div class="shrink-0 size-6 flex items-center justify-center">
|
<div class="shrink-0 size-6 flex items-center justify-center">
|
||||||
<Icon name="new-session" size="small" class="text-icon-weak" />
|
<Icon name="new-session" size="small" class="text-icon-weak" />
|
||||||
</div>
|
</div>
|
||||||
<span class="text-14-regular text-text-strong min-w-0 flex-1 truncate">{label}</span>
|
<span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</A>
|
</A>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="group/session relative w-full min-w-0 rounded-md cursor-default transition-colors pl-2 pr-3 hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active">
|
<div class="group/session relative w-full rounded-md cursor-default transition-colors pl-2 pr-3 hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active">
|
||||||
<Show
|
<Show
|
||||||
when={!tooltip()}
|
when={!tooltip()}
|
||||||
fallback={
|
fallback={
|
||||||
<Tooltip placement={props.mobile ? "bottom" : "right"} value={label} gutter={10} class="min-w-0 w-full">
|
<Tooltip placement={props.mobile ? "bottom" : "right"} value={label} gutter={10}>
|
||||||
{item}
|
{item}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export type ProjectSidebarContext = {
|
|||||||
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
|
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
|
||||||
onProjectMouseLeave: (worktree: string) => void
|
onProjectMouseLeave: (worktree: string) => void
|
||||||
onProjectFocus: (worktree: string) => void
|
onProjectFocus: (worktree: string) => void
|
||||||
onHoverOpenChanged: (worktree: string, hovered: boolean) => void
|
|
||||||
navigateToProject: (directory: string) => void
|
navigateToProject: (directory: string) => void
|
||||||
openSidebar: () => void
|
openSidebar: () => void
|
||||||
closeProject: (directory: string) => void
|
closeProject: (directory: string) => void
|
||||||
@@ -110,14 +109,8 @@ const ProjectTile = (props: {
|
|||||||
"bg-surface-base-hover border border-border-weak-base": !props.selected() && props.active(),
|
"bg-surface-base-hover border border-border-weak-base": !props.selected() && props.active(),
|
||||||
}}
|
}}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
if (event.button === 0 && !event.ctrlKey) {
|
|
||||||
props.setOpen(false)
|
|
||||||
props.setSuppressHover(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!props.overlay()) return
|
if (!props.overlay()) return
|
||||||
if (event.button !== 2 && !(event.button === 0 && event.ctrlKey)) return
|
if (event.button !== 2 && !(event.button === 0 && event.ctrlKey)) return
|
||||||
props.setOpen(false)
|
|
||||||
props.setSuppressHover(true)
|
props.setSuppressHover(true)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}}
|
}}
|
||||||
@@ -137,11 +130,12 @@ const ProjectTile = (props: {
|
|||||||
props.onProjectFocus(props.project.worktree)
|
props.onProjectFocus(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.setOpen(false)
|
|
||||||
if (props.selected()) {
|
if (props.selected()) {
|
||||||
|
props.setSuppressHover(true)
|
||||||
layout.sidebar.toggle()
|
layout.sidebar.toggle()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
props.setSuppressHover(false)
|
||||||
props.navigateToProject(props.project.worktree)
|
props.navigateToProject(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
onBlur={() => props.setOpen(false)}
|
onBlur={() => props.setOpen(false)}
|
||||||
@@ -198,6 +192,7 @@ const ProjectPreviewPanel = (props: {
|
|||||||
projectChildren: Accessor<Map<string, string[]>>
|
projectChildren: Accessor<Map<string, string[]>>
|
||||||
workspaceSessions: (directory: string) => ReturnType<typeof sortedRootSessions>
|
workspaceSessions: (directory: string) => ReturnType<typeof sortedRootSessions>
|
||||||
workspaceChildren: (directory: string) => Map<string, string[]>
|
workspaceChildren: (directory: string) => Map<string, string[]>
|
||||||
|
setOpen: (value: boolean) => void
|
||||||
ctx: ProjectSidebarContext
|
ctx: ProjectSidebarContext
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
}): JSX.Element => (
|
}): JSX.Element => (
|
||||||
@@ -264,7 +259,7 @@ const ProjectPreviewPanel = (props: {
|
|||||||
class="flex w-full text-left justify-start text-text-base px-2 hover:bg-transparent active:bg-transparent"
|
class="flex w-full text-left justify-start text-text-base px-2 hover:bg-transparent active:bg-transparent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.ctx.openSidebar()
|
props.ctx.openSidebar()
|
||||||
props.ctx.onHoverOpenChanged(props.project.worktree, false)
|
props.setOpen(false)
|
||||||
if (props.selected()) return
|
if (props.selected()) return
|
||||||
props.ctx.navigateToProject(props.project.worktree)
|
props.ctx.navigateToProject(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
@@ -289,16 +284,28 @@ export const SortableProject = (props: {
|
|||||||
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
|
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
|
||||||
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
|
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
|
open: false,
|
||||||
menu: false,
|
menu: false,
|
||||||
suppressHover: false,
|
suppressHover: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isHoverProject = () => props.ctx.hoverProject() === props.project.worktree
|
|
||||||
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
|
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
|
||||||
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
|
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
|
||||||
const active = createMemo(() => state.menu || (preview() ? isHoverProject() : overlay() && isHoverProject()))
|
const active = createMemo(
|
||||||
|
() => state.menu || (preview() ? state.open : overlay() && props.ctx.hoverProject() === props.project.worktree),
|
||||||
|
)
|
||||||
|
|
||||||
const hoverOpen = () => isHoverProject() && preview() && !selected() && !state.menu
|
createEffect(() => {
|
||||||
|
if (preview()) return
|
||||||
|
if (!state.open) return
|
||||||
|
setState("open", false)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!selected()) return
|
||||||
|
if (!state.open) return
|
||||||
|
setState("open", false)
|
||||||
|
})
|
||||||
|
|
||||||
const label = (directory: string) => {
|
const label = (directory: string) => {
|
||||||
const [data] = globalSync.child(directory, { bootstrap: false })
|
const [data] = globalSync.child(directory, { bootstrap: false })
|
||||||
@@ -339,7 +346,7 @@ export const SortableProject = (props: {
|
|||||||
workspacesEnabled={props.ctx.workspacesEnabled}
|
workspacesEnabled={props.ctx.workspacesEnabled}
|
||||||
closeProject={props.ctx.closeProject}
|
closeProject={props.ctx.closeProject}
|
||||||
setMenu={(value) => setState("menu", value)}
|
setMenu={(value) => setState("menu", value)}
|
||||||
setOpen={(value) => props.ctx.onHoverOpenChanged(props.project.worktree, value)}
|
setOpen={(value) => setState("open", value)}
|
||||||
setSuppressHover={(value) => setState("suppressHover", value)}
|
setSuppressHover={(value) => setState("suppressHover", value)}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
@@ -350,7 +357,7 @@ export const SortableProject = (props: {
|
|||||||
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
||||||
<Show when={preview() && !selected()} fallback={tile()}>
|
<Show when={preview() && !selected()} fallback={tile()}>
|
||||||
<HoverCard
|
<HoverCard
|
||||||
open={!state.suppressHover && hoverOpen() && !state.menu}
|
open={!state.suppressHover && state.open && !state.menu}
|
||||||
openDelay={0}
|
openDelay={0}
|
||||||
closeDelay={0}
|
closeDelay={0}
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
@@ -359,7 +366,7 @@ export const SortableProject = (props: {
|
|||||||
onOpenChange={(value) => {
|
onOpenChange={(value) => {
|
||||||
if (state.menu) return
|
if (state.menu) return
|
||||||
if (value && state.suppressHover) return
|
if (value && state.suppressHover) return
|
||||||
props.ctx.onHoverOpenChanged(props.project.worktree, value)
|
setState("open", value)
|
||||||
if (value) props.ctx.setHoverSession(undefined)
|
if (value) props.ctx.setHoverSession(undefined)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -374,6 +381,7 @@ export const SortableProject = (props: {
|
|||||||
projectChildren={projectChildren}
|
projectChildren={projectChildren}
|
||||||
workspaceSessions={workspaceSessions}
|
workspaceSessions={workspaceSessions}
|
||||||
workspaceChildren={workspaceChildren}
|
workspaceChildren={workspaceChildren}
|
||||||
|
setOpen={(value) => setState("open", value)}
|
||||||
ctx={props.ctx}
|
ctx={props.ctx}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+110
-125
@@ -1,6 +1,5 @@
|
|||||||
import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
|
import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import {
|
import {
|
||||||
batch,
|
batch,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
@@ -26,8 +25,8 @@ import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
|||||||
import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { checksum } from "@opencode-ai/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/util/encode"
|
||||||
import { useSearchParams } from "@solidjs/router"
|
import { useNavigate, useSearchParams } from "@solidjs/router"
|
||||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch"
|
import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch"
|
||||||
@@ -41,13 +40,7 @@ import { useSync } from "@/context/sync"
|
|||||||
import { useTerminal } from "@/context/terminal"
|
import { useTerminal } from "@/context/terminal"
|
||||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||||
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
|
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
|
||||||
import {
|
import { createOpenReviewFile, createSessionTabs, createSizing, focusTerminalById } from "@/pages/session/helpers"
|
||||||
createOpenReviewFile,
|
|
||||||
createSessionTabs,
|
|
||||||
createSizing,
|
|
||||||
focusTerminalById,
|
|
||||||
shouldFocusTerminalOnKeyDown,
|
|
||||||
} from "@/pages/session/helpers"
|
|
||||||
import { MessageTimeline } from "@/pages/session/message-timeline"
|
import { MessageTimeline } from "@/pages/session/message-timeline"
|
||||||
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
|
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
@@ -57,15 +50,12 @@ import { TerminalPanel } from "@/pages/session/terminal-panel"
|
|||||||
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
||||||
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
|
||||||
import { extractPromptFromParts } from "@/utils/prompt"
|
import { extractPromptFromParts } from "@/utils/prompt"
|
||||||
import { same } from "@/utils/same"
|
import { same } from "@/utils/same"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
const emptyUserMessages: UserMessage[] = []
|
const emptyUserMessages: UserMessage[] = []
|
||||||
type FollowupItem = FollowupDraft & { id: string }
|
const emptyFollowups: (FollowupDraft & { id: string })[] = []
|
||||||
type FollowupEdit = Pick<FollowupItem, "id" | "prompt" | "context">
|
|
||||||
const emptyFollowups: FollowupItem[] = []
|
|
||||||
|
|
||||||
type SessionHistoryWindowInput = {
|
type SessionHistoryWindowInput = {
|
||||||
sessionID: () => string | undefined
|
sessionID: () => string | undefined
|
||||||
@@ -249,19 +239,14 @@ function createSessionHistoryWindow(input: SessionHistoryWindowInput) {
|
|||||||
|
|
||||||
if (added <= 0) return
|
if (added <= 0) return
|
||||||
if (growth <= 0) return
|
if (growth <= 0) return
|
||||||
|
|
||||||
if (opts?.prefetch) {
|
|
||||||
const current = turnStart()
|
|
||||||
preserveScroll(() => setTurnStart(current + growth))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turnStart() !== start) return
|
if (turnStart() !== start) return
|
||||||
|
|
||||||
|
const reveal = !opts?.prefetch
|
||||||
const currentRendered = renderedUserMessages().length
|
const currentRendered = renderedUserMessages().length
|
||||||
const base = Math.max(beforeRendered, currentRendered)
|
const base = Math.max(beforeRendered, currentRendered)
|
||||||
const target = Math.min(afterVisible, base + turnBatch)
|
const target = reveal ? Math.min(afterVisible, base + turnBatch) : base
|
||||||
preserveScroll(() => setTurnStart(Math.max(0, afterVisible - target)))
|
const nextStart = Math.max(0, afterVisible - target)
|
||||||
|
preserveScroll(() => setTurnStart(nextStart))
|
||||||
}
|
}
|
||||||
|
|
||||||
const onScrollerScroll = () => {
|
const onScrollerScroll = () => {
|
||||||
@@ -320,6 +305,7 @@ export default function Page() {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const navigate = useNavigate()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
@@ -341,7 +327,10 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const [ui, setUi] = createStore({
|
const [ui, setUi] = createStore({
|
||||||
|
git: false,
|
||||||
pendingMessage: undefined as string | undefined,
|
pendingMessage: undefined as string | undefined,
|
||||||
|
restoring: undefined as string | undefined,
|
||||||
|
reverting: false,
|
||||||
reviewSnap: false,
|
reviewSnap: false,
|
||||||
scrollGesture: 0,
|
scrollGesture: 0,
|
||||||
scroll: {
|
scroll: {
|
||||||
@@ -515,20 +504,16 @@ export default function Page() {
|
|||||||
deferRender: false,
|
deferRender: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [followup, setFollowup] = persisted(
|
const [followup, setFollowup] = createStore({
|
||||||
Persist.workspace(sdk.directory, "followup", ["followup.v1"]),
|
items: {} as Record<string, (FollowupDraft & { id: string })[] | undefined>,
|
||||||
createStore<{
|
sending: {} as Record<string, string | undefined>,
|
||||||
items: Record<string, FollowupItem[] | undefined>
|
failed: {} as Record<string, string | undefined>,
|
||||||
failed: Record<string, string | undefined>
|
paused: {} as Record<string, boolean | undefined>,
|
||||||
paused: Record<string, boolean | undefined>
|
edit: {} as Record<
|
||||||
edit: Record<string, FollowupEdit | undefined>
|
string,
|
||||||
}>({
|
{ id: string; prompt: FollowupDraft["prompt"]; context: FollowupDraft["context"] } | undefined
|
||||||
items: {},
|
>,
|
||||||
failed: {},
|
})
|
||||||
paused: {},
|
|
||||||
edit: {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
createComputed((prev) => {
|
createComputed((prev) => {
|
||||||
const key = sessionKey()
|
const key = sessionKey()
|
||||||
@@ -659,24 +644,25 @@ export default function Page() {
|
|||||||
globalSync.set("project", [...list, next])
|
globalSync.set("project", [...list, next])
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitMutation = useMutation(() => ({
|
function initGit() {
|
||||||
mutationFn: () => sdk.client.project.initGit(),
|
if (ui.git) return
|
||||||
onSuccess: (x) => {
|
setUi("git", true)
|
||||||
|
void sdk.client.project
|
||||||
|
.initGit()
|
||||||
|
.then((x) => {
|
||||||
if (!x.data) return
|
if (!x.data) return
|
||||||
upsert(x.data)
|
upsert(x.data)
|
||||||
},
|
})
|
||||||
onError: (err) => {
|
.catch((err) => {
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: language.t("common.requestFailed"),
|
title: language.t("common.requestFailed"),
|
||||||
description: formatServerError(err, language.t),
|
description: formatServerError(err, language.t),
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
}))
|
.finally(() => {
|
||||||
|
setUi("git", false)
|
||||||
function initGit() {
|
})
|
||||||
if (gitMutation.isPending) return
|
|
||||||
gitMutation.mutate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputRef!: HTMLDivElement
|
let inputRef!: HTMLDivElement
|
||||||
@@ -719,6 +705,7 @@ export default function Page() {
|
|||||||
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
||||||
})()
|
})()
|
||||||
const todos = untrack(() => sync.data.todo[id] !== undefined || globalSync.data.session_todo[id] !== undefined)
|
const todos = untrack(() => sync.data.todo[id] !== undefined || globalSync.data.session_todo[id] !== undefined)
|
||||||
|
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
void sync.session.sync(id)
|
void sync.session.sync(id)
|
||||||
})
|
})
|
||||||
@@ -867,7 +854,7 @@ export default function Page() {
|
|||||||
// Prefer the open terminal over the composer when it can take focus
|
// Prefer the open terminal over the composer when it can take focus
|
||||||
if (view().terminal.opened()) {
|
if (view().terminal.opened()) {
|
||||||
const id = terminal.active()
|
const id = terminal.active()
|
||||||
if (id && shouldFocusTerminalOnKeyDown(event) && focusTerminalById(id)) return
|
if (id && focusTerminalById(id)) return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only treat explicit scroll keys as potential "user scroll" gestures.
|
// Only treat explicit scroll keys as potential "user scroll" gestures.
|
||||||
@@ -974,8 +961,8 @@ export default function Page() {
|
|||||||
{language.t("session.review.noVcs.createGit.description")}
|
{language.t("session.review.noVcs.createGit.description")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button size="large" disabled={gitMutation.isPending} onClick={initGit}>
|
<Button size="large" disabled={ui.git} onClick={initGit}>
|
||||||
{gitMutation.isPending
|
{ui.git
|
||||||
? language.t("session.review.noVcs.createGit.actionLoading")
|
? language.t("session.review.noVcs.createGit.actionLoading")
|
||||||
: language.t("session.review.noVcs.createGit.action")}
|
: language.t("session.review.noVcs.createGit.action")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1190,6 +1177,8 @@ export default function Page() {
|
|||||||
on(
|
on(
|
||||||
() => sdk.directory,
|
() => sdk.directory,
|
||||||
() => {
|
() => {
|
||||||
|
void file.tree.list("")
|
||||||
|
|
||||||
const tab = activeFileTab()
|
const tab = activeFileTab()
|
||||||
if (!tab) return
|
if (!tab) return
|
||||||
const path = file.pathFromTab(tab)
|
const path = file.pathFromTab(tab)
|
||||||
@@ -1390,40 +1379,10 @@ export default function Page() {
|
|||||||
return followup.edit[id]
|
return followup.edit[id]
|
||||||
})
|
})
|
||||||
|
|
||||||
const followupMutation = useMutation(() => ({
|
|
||||||
mutationFn: async (input: { sessionID: string; id: string; manual?: boolean }) => {
|
|
||||||
const item = (followup.items[input.sessionID] ?? []).find((entry) => entry.id === input.id)
|
|
||||||
if (!item) return
|
|
||||||
|
|
||||||
if (input.manual) setFollowup("paused", input.sessionID, undefined)
|
|
||||||
setFollowup("failed", input.sessionID, undefined)
|
|
||||||
|
|
||||||
const ok = await sendFollowupDraft({
|
|
||||||
client: sdk.client,
|
|
||||||
sync,
|
|
||||||
globalSync,
|
|
||||||
draft: item,
|
|
||||||
optimisticBusy: item.sessionDirectory === sdk.directory,
|
|
||||||
}).catch((err) => {
|
|
||||||
setFollowup("failed", input.sessionID, input.id)
|
|
||||||
fail(err)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (!ok) return
|
|
||||||
|
|
||||||
setFollowup("items", input.sessionID, (items) => (items ?? []).filter((entry) => entry.id !== input.id))
|
|
||||||
if (input.manual) resumeScroll()
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const followupBusy = (sessionID: string) =>
|
|
||||||
followupMutation.isPending && followupMutation.variables?.sessionID === sessionID
|
|
||||||
|
|
||||||
const sendingFollowup = createMemo(() => {
|
const sendingFollowup = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
if (!followupBusy(id)) return
|
return followup.sending[id]
|
||||||
return followupMutation.variables?.id
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const queueEnabled = createMemo(() => {
|
const queueEnabled = createMemo(() => {
|
||||||
@@ -1463,15 +1422,37 @@ export default function Page() {
|
|||||||
const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
|
const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
|
||||||
const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
|
const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
|
||||||
if (!item) return Promise.resolve()
|
if (!item) return Promise.resolve()
|
||||||
if (followupBusy(sessionID)) return Promise.resolve()
|
if (followup.sending[sessionID]) return Promise.resolve()
|
||||||
|
|
||||||
return followupMutation.mutateAsync({ sessionID, id, manual: opts?.manual })
|
if (opts?.manual) setFollowup("paused", sessionID, undefined)
|
||||||
|
setFollowup("sending", sessionID, id)
|
||||||
|
setFollowup("failed", sessionID, undefined)
|
||||||
|
|
||||||
|
return sendFollowupDraft({
|
||||||
|
client: sdk.client,
|
||||||
|
sync,
|
||||||
|
globalSync,
|
||||||
|
draft: item,
|
||||||
|
optimisticBusy: item.sessionDirectory === sdk.directory,
|
||||||
|
})
|
||||||
|
.then((ok) => {
|
||||||
|
if (ok === false) return
|
||||||
|
setFollowup("items", sessionID, (items) => (items ?? []).filter((entry) => entry.id !== id))
|
||||||
|
if (opts?.manual) resumeScroll()
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setFollowup("failed", sessionID, id)
|
||||||
|
fail(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setFollowup("sending", sessionID, (value) => (value === id ? undefined : value))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const editFollowup = (id: string) => {
|
const editFollowup = (id: string) => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
if (followupBusy(sessionID)) return
|
if (followup.sending[sessionID]) return
|
||||||
|
|
||||||
const item = queuedFollowups().find((entry) => entry.id === id)
|
const item = queuedFollowups().find((entry) => entry.id === id)
|
||||||
if (!item) return
|
if (!item) return
|
||||||
@@ -1494,16 +1475,37 @@ export default function Page() {
|
|||||||
const halt = (sessionID: string) =>
|
const halt = (sessionID: string) =>
|
||||||
busy(sessionID) ? sdk.client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve()
|
busy(sessionID) ? sdk.client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve()
|
||||||
|
|
||||||
const revertMutation = useMutation(() => ({
|
const fork = (input: { sessionID: string; messageID: string }) => {
|
||||||
mutationFn: async (input: { sessionID: string; messageID: string }) => {
|
const value = draft(input.messageID)
|
||||||
|
const dir = base64Encode(sdk.directory)
|
||||||
|
return sdk.client.session
|
||||||
|
.fork(input)
|
||||||
|
.then((result) => {
|
||||||
|
const next = result.data
|
||||||
|
if (!next) {
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: language.t("common.requestFailed"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prompt.set(value, undefined, { dir, id: next.id })
|
||||||
|
navigate(`/${dir}/session/${next.id}`)
|
||||||
|
})
|
||||||
|
.catch(fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
const revert = (input: { sessionID: string; messageID: string }) => {
|
||||||
|
if (ui.reverting || ui.restoring) return
|
||||||
const prev = prompt.current().slice()
|
const prev = prompt.current().slice()
|
||||||
const last = info()?.revert
|
const last = info()?.revert
|
||||||
const value = draft(input.messageID)
|
const value = draft(input.messageID)
|
||||||
batch(() => {
|
batch(() => {
|
||||||
|
setUi("reverting", true)
|
||||||
roll(input.sessionID, { messageID: input.messageID })
|
roll(input.sessionID, { messageID: input.messageID })
|
||||||
prompt.set(value)
|
prompt.set(value)
|
||||||
})
|
})
|
||||||
await halt(input.sessionID)
|
return halt(input.sessionID)
|
||||||
.then(() => sdk.client.session.revert(input))
|
.then(() => sdk.client.session.revert(input))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.data) merge(result.data)
|
if (result.data) merge(result.data)
|
||||||
@@ -1515,19 +1517,22 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
fail(err)
|
fail(err)
|
||||||
})
|
})
|
||||||
},
|
.finally(() => {
|
||||||
}))
|
setUi("reverting", false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const restoreMutation = useMutation(() => ({
|
const restore = (id: string) => {
|
||||||
mutationFn: async (id: string) => {
|
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID || ui.restoring || ui.reverting) return
|
||||||
|
|
||||||
const next = userMessages().find((item) => item.id > id)
|
const next = userMessages().find((item) => item.id > id)
|
||||||
const prev = prompt.current().slice()
|
const prev = prompt.current().slice()
|
||||||
const last = info()?.revert
|
const last = info()?.revert
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
|
setUi("restoring", id)
|
||||||
|
setUi("reverting", true)
|
||||||
roll(sessionID, next ? { messageID: next.id } : undefined)
|
roll(sessionID, next ? { messageID: next.id } : undefined)
|
||||||
if (next) {
|
if (next) {
|
||||||
prompt.set(draft(next.id))
|
prompt.set(draft(next.id))
|
||||||
@@ -1545,7 +1550,7 @@ export default function Page() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
await task
|
return task
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.data) merge(result.data)
|
if (result.data) merge(result.data)
|
||||||
})
|
})
|
||||||
@@ -1556,20 +1561,12 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
fail(err)
|
fail(err)
|
||||||
})
|
})
|
||||||
},
|
.finally(() => {
|
||||||
}))
|
batch(() => {
|
||||||
|
setUi("restoring", (value) => (value === id ? undefined : value))
|
||||||
const reverting = createMemo(() => revertMutation.isPending || restoreMutation.isPending)
|
setUi("reverting", false)
|
||||||
const restoring = createMemo(() => (restoreMutation.isPending ? restoreMutation.variables : undefined))
|
})
|
||||||
|
})
|
||||||
const revert = (input: { sessionID: string; messageID: string }) => {
|
|
||||||
if (reverting()) return
|
|
||||||
return revertMutation.mutateAsync(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
const restore = (id: string) => {
|
|
||||||
if (!params.id || reverting()) return
|
|
||||||
return restoreMutation.mutateAsync(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rolled = createMemo(() => {
|
const rolled = createMemo(() => {
|
||||||
@@ -1580,7 +1577,7 @@ export default function Page() {
|
|||||||
.map((item) => ({ id: item.id, text: line(item.id) }))
|
.map((item) => ({ id: item.id, text: line(item.id) }))
|
||||||
})
|
})
|
||||||
|
|
||||||
const actions = { revert }
|
const actions = { fork, revert }
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
@@ -1588,7 +1585,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const item = queuedFollowups()[0]
|
const item = queuedFollowups()[0]
|
||||||
if (!item) return
|
if (!item) return
|
||||||
if (followupBusy(sessionID)) return
|
if (followup.sending[sessionID]) return
|
||||||
if (followup.failed[sessionID] === item.id) return
|
if (followup.failed[sessionID] === item.id) return
|
||||||
if (followup.paused[sessionID]) return
|
if (followup.paused[sessionID]) return
|
||||||
if (composer.blocked()) return
|
if (composer.blocked()) return
|
||||||
@@ -1624,9 +1621,6 @@ export default function Page() {
|
|||||||
sessionID: () => params.id,
|
sessionID: () => params.id,
|
||||||
messagesReady,
|
messagesReady,
|
||||||
visibleUserMessages,
|
visibleUserMessages,
|
||||||
historyMore,
|
|
||||||
historyLoading,
|
|
||||||
loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
|
|
||||||
turnStart: historyWindow.turnStart,
|
turnStart: historyWindow.turnStart,
|
||||||
currentMessageId: () => store.messageId,
|
currentMessageId: () => store.messageId,
|
||||||
pendingMessage: () => ui.pendingMessage,
|
pendingMessage: () => ui.pendingMessage,
|
||||||
@@ -1640,15 +1634,6 @@ export default function Page() {
|
|||||||
consumePendingMessage: layout.pendingMessage.consume,
|
consumePendingMessage: layout.pendingMessage.consume,
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => params.id,
|
|
||||||
(id) => {
|
|
||||||
if (!id) requestAnimationFrame(() => inputRef?.focus())
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
document.addEventListener("keydown", handleKeyDown)
|
document.addEventListener("keydown", handleKeyDown)
|
||||||
})
|
})
|
||||||
@@ -1707,7 +1692,7 @@ export default function Page() {
|
|||||||
<div class="flex-1 min-h-0 overflow-hidden">
|
<div class="flex-1 min-h-0 overflow-hidden">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={params.id}>
|
<Match when={params.id}>
|
||||||
<Show when={messagesReady()}>
|
<Show when={lastUserMessage()}>
|
||||||
<MessageTimeline
|
<MessageTimeline
|
||||||
mobileChanges={mobileChanges()}
|
mobileChanges={mobileChanges()}
|
||||||
mobileFallback={reviewContent({
|
mobileFallback={reviewContent({
|
||||||
@@ -1795,8 +1780,8 @@ export default function Page() {
|
|||||||
rolled().length > 0
|
rolled().length > 0
|
||||||
? {
|
? {
|
||||||
items: rolled(),
|
items: rolled(),
|
||||||
restoring: restoring(),
|
restoring: ui.restoring,
|
||||||
disabled: reverting(),
|
disabled: ui.reverting,
|
||||||
onRestore: restore,
|
onRestore: restore,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
|
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
@@ -25,6 +24,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
custom: cached?.custom ?? ([] as string[]),
|
custom: cached?.custom ?? ([] as string[]),
|
||||||
customOn: cached?.customOn ?? ([] as boolean[]),
|
customOn: cached?.customOn ?? ([] as boolean[]),
|
||||||
editing: false,
|
editing: false,
|
||||||
|
sending: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
let root: HTMLDivElement | undefined
|
let root: HTMLDivElement | undefined
|
||||||
@@ -126,40 +126,36 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
}
|
}
|
||||||
|
|
||||||
const replyMutation = useMutation(() => ({
|
|
||||||
mutationFn: (answers: QuestionAnswer[]) => sdk.client.question.reply({ requestID: props.request.id, answers }),
|
|
||||||
onMutate: () => {
|
|
||||||
props.onSubmit()
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
replied = true
|
|
||||||
cache.delete(props.request.id)
|
|
||||||
},
|
|
||||||
onError: fail,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const rejectMutation = useMutation(() => ({
|
|
||||||
mutationFn: () => sdk.client.question.reject({ requestID: props.request.id }),
|
|
||||||
onMutate: () => {
|
|
||||||
props.onSubmit()
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
replied = true
|
|
||||||
cache.delete(props.request.id)
|
|
||||||
},
|
|
||||||
onError: fail,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const sending = createMemo(() => replyMutation.isPending || rejectMutation.isPending)
|
|
||||||
|
|
||||||
const reply = async (answers: QuestionAnswer[]) => {
|
const reply = async (answers: QuestionAnswer[]) => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
await replyMutation.mutateAsync(answers)
|
|
||||||
|
props.onSubmit()
|
||||||
|
setStore("sending", true)
|
||||||
|
try {
|
||||||
|
await sdk.client.question.reply({ requestID: props.request.id, answers })
|
||||||
|
replied = true
|
||||||
|
cache.delete(props.request.id)
|
||||||
|
} catch (err) {
|
||||||
|
fail(err)
|
||||||
|
} finally {
|
||||||
|
setStore("sending", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const reject = async () => {
|
const reject = async () => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
await rejectMutation.mutateAsync()
|
|
||||||
|
props.onSubmit()
|
||||||
|
setStore("sending", true)
|
||||||
|
try {
|
||||||
|
await sdk.client.question.reject({ requestID: props.request.id })
|
||||||
|
replied = true
|
||||||
|
cache.delete(props.request.id)
|
||||||
|
} catch (err) {
|
||||||
|
fail(err)
|
||||||
|
} finally {
|
||||||
|
setStore("sending", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => void reply(questions().map((_, i) => store.answers[i] ?? []))
|
const submit = () => void reply(questions().map((_, i) => store.answers[i] ?? []))
|
||||||
@@ -179,7 +175,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customToggle = () => {
|
const customToggle = () => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
|
|
||||||
if (!multi()) {
|
if (!multi()) {
|
||||||
setStore("customOn", store.tab, true)
|
setStore("customOn", store.tab, true)
|
||||||
@@ -202,14 +198,14 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customOpen = () => {
|
const customOpen = () => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
if (!on()) setStore("customOn", store.tab, true)
|
if (!on()) setStore("customOn", store.tab, true)
|
||||||
setStore("editing", true)
|
setStore("editing", true)
|
||||||
customUpdate(input(), true)
|
customUpdate(input(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectOption = (optIndex: number) => {
|
const selectOption = (optIndex: number) => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
|
|
||||||
if (optIndex === options().length) {
|
if (optIndex === options().length) {
|
||||||
customOpen()
|
customOpen()
|
||||||
@@ -231,7 +227,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
if (store.editing) commitCustom()
|
if (store.editing) commitCustom()
|
||||||
|
|
||||||
if (store.tab >= total() - 1) {
|
if (store.tab >= total() - 1) {
|
||||||
@@ -244,14 +240,14 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
const back = () => {
|
const back = () => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
if (store.tab <= 0) return
|
if (store.tab <= 0) return
|
||||||
setStore("tab", store.tab - 1)
|
setStore("tab", store.tab - 1)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const jump = (tab: number) => {
|
const jump = (tab: number) => {
|
||||||
if (sending()) return
|
if (store.sending) return
|
||||||
setStore("tab", tab)
|
setStore("tab", tab)
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
}
|
}
|
||||||
@@ -274,7 +270,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
(store.answers[i()]?.length ?? 0) > 0 ||
|
(store.answers[i()]?.length ?? 0) > 0 ||
|
||||||
(store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0)
|
(store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0)
|
||||||
}
|
}
|
||||||
disabled={sending()}
|
disabled={store.sending}
|
||||||
onClick={() => jump(i())}
|
onClick={() => jump(i())}
|
||||||
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
||||||
/>
|
/>
|
||||||
@@ -285,16 +281,16 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
}
|
}
|
||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
<Button variant="ghost" size="large" disabled={sending()} onClick={reject}>
|
<Button variant="ghost" size="large" disabled={store.sending} onClick={reject}>
|
||||||
{language.t("ui.common.dismiss")}
|
{language.t("ui.common.dismiss")}
|
||||||
</Button>
|
</Button>
|
||||||
<div data-slot="question-footer-actions">
|
<div data-slot="question-footer-actions">
|
||||||
<Show when={store.tab > 0}>
|
<Show when={store.tab > 0}>
|
||||||
<Button variant="secondary" size="large" disabled={sending()} onClick={back}>
|
<Button variant="secondary" size="large" disabled={store.sending} onClick={back}>
|
||||||
{language.t("ui.common.back")}
|
{language.t("ui.common.back")}
|
||||||
</Button>
|
</Button>
|
||||||
</Show>
|
</Show>
|
||||||
<Button variant={last() ? "primary" : "secondary"} size="large" disabled={sending()} onClick={next}>
|
<Button variant={last() ? "primary" : "secondary"} size="large" disabled={store.sending} onClick={next}>
|
||||||
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -315,7 +311,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
data-picked={picked()}
|
data-picked={picked()}
|
||||||
role={multi() ? "checkbox" : "radio"}
|
role={multi() ? "checkbox" : "radio"}
|
||||||
aria-checked={picked()}
|
aria-checked={picked()}
|
||||||
disabled={sending()}
|
disabled={store.sending}
|
||||||
onClick={() => selectOption(i())}
|
onClick={() => selectOption(i())}
|
||||||
>
|
>
|
||||||
<span data-slot="question-option-check" aria-hidden="true">
|
<span data-slot="question-option-check" aria-hidden="true">
|
||||||
@@ -349,7 +345,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
data-picked={on()}
|
data-picked={on()}
|
||||||
role={multi() ? "checkbox" : "radio"}
|
role={multi() ? "checkbox" : "radio"}
|
||||||
aria-checked={on()}
|
aria-checked={on()}
|
||||||
disabled={sending()}
|
disabled={store.sending}
|
||||||
onClick={customOpen}
|
onClick={customOpen}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -381,7 +377,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
role={multi() ? "checkbox" : "radio"}
|
role={multi() ? "checkbox" : "radio"}
|
||||||
aria-checked={on()}
|
aria-checked={on()}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
if (sending()) {
|
if (store.sending) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -423,7 +419,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
|||||||
placeholder={language.t("ui.question.custom.placeholder")}
|
placeholder={language.t("ui.question.custom.placeholder")}
|
||||||
value={input()}
|
value={input()}
|
||||||
rows={1}
|
rows={1}
|
||||||
disabled={sending()}
|
disabled={store.sending}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
createSessionTabs,
|
createSessionTabs,
|
||||||
focusTerminalById,
|
focusTerminalById,
|
||||||
getTabReorderIndex,
|
getTabReorderIndex,
|
||||||
shouldFocusTerminalOnKeyDown,
|
|
||||||
} from "./helpers"
|
} from "./helpers"
|
||||||
|
|
||||||
describe("createOpenReviewFile", () => {
|
describe("createOpenReviewFile", () => {
|
||||||
@@ -87,26 +86,6 @@ describe("focusTerminalById", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("shouldFocusTerminalOnKeyDown", () => {
|
|
||||||
test("skips pure modifier keys", () => {
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Meta", metaKey: true }))).toBe(false)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Control", ctrlKey: true }))).toBe(false)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Alt", altKey: true }))).toBe(false)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "Shift", shiftKey: true }))).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("skips shortcut key combos", () => {
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "c", metaKey: true }))).toBe(false)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "c", ctrlKey: true }))).toBe(false)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "ArrowLeft", altKey: true }))).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("keeps plain typing focused on terminal", () => {
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "a" }))).toBe(true)
|
|
||||||
expect(shouldFocusTerminalOnKeyDown(new KeyboardEvent("keydown", { key: "A", shiftKey: true }))).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("getTabReorderIndex", () => {
|
describe("getTabReorderIndex", () => {
|
||||||
test("returns target index for valid drag reorder", () => {
|
test("returns target index for valid drag reorder", () => {
|
||||||
expect(getTabReorderIndex(["a", "b", "c"], "a", "c")).toBe(2)
|
expect(getTabReorderIndex(["a", "b", "c"], "a", "c")).toBe(2)
|
||||||
|
|||||||
@@ -93,13 +93,6 @@ export const focusTerminalById = (id: string) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const skip = new Set(["Alt", "Control", "Meta", "Shift"])
|
|
||||||
|
|
||||||
export const shouldFocusTerminalOnKeyDown = (event: Pick<KeyboardEvent, "key" | "ctrlKey" | "metaKey" | "altKey">) => {
|
|
||||||
if (skip.has(event.key)) return false
|
|
||||||
return !(event.ctrlKey || event.metaKey || event.altKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createOpenReviewFile = (input: {
|
export const createOpenReviewFile = (input: {
|
||||||
showAllFiles: () => void
|
showAllFiles: () => void
|
||||||
tabForPath: (path: string) => string
|
tabForPath: (path: string) => string
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX, createSignal } from "solid-js"
|
import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
@@ -30,7 +29,6 @@ import { useSDK } from "@/context/sdk"
|
|||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { messageAgentColor } from "@/utils/agent"
|
import { messageAgentColor } from "@/utils/agent"
|
||||||
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
||||||
import { makeTimer } from "@solid-primitives/timer"
|
|
||||||
|
|
||||||
type MessageComment = {
|
type MessageComment = {
|
||||||
path: string
|
path: string
|
||||||
@@ -251,21 +249,38 @@ export function MessageTimeline(props: {
|
|||||||
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
|
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
|
||||||
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
|
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
|
||||||
|
|
||||||
const [timeoutDone, setTimeoutDone] = createSignal(true)
|
const [slot, setSlot] = createStore({
|
||||||
|
open: false,
|
||||||
const workingStatus = createMemo<"hidden" | "showing" | "hiding">((prev) => {
|
show: false,
|
||||||
if (working()) return "showing"
|
fade: false,
|
||||||
if (prev === "showing" || !timeoutDone()) return "hiding"
|
|
||||||
return "hidden"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
let f: number | undefined
|
||||||
if (workingStatus() !== "hiding") return
|
const clear = () => {
|
||||||
|
if (f !== undefined) window.clearTimeout(f)
|
||||||
setTimeoutDone(false)
|
f = undefined
|
||||||
makeTimer(() => setTimeoutDone(true), 260, setTimeout)
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
onCleanup(clear)
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
working,
|
||||||
|
(on, prev) => {
|
||||||
|
clear()
|
||||||
|
if (on) {
|
||||||
|
setSlot({ open: true, show: true, fade: false })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (prev) {
|
||||||
|
setSlot({ open: false, show: true, fade: true })
|
||||||
|
f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSlot({ open: false, show: false, fade: false })
|
||||||
|
},
|
||||||
|
{ defer: true },
|
||||||
|
),
|
||||||
|
)
|
||||||
const activeMessageID = createMemo(() => {
|
const activeMessageID = createMemo(() => {
|
||||||
const parentID = pending()?.parentID
|
const parentID = pending()?.parentID
|
||||||
if (parentID) {
|
if (parentID) {
|
||||||
@@ -306,6 +321,7 @@ export function MessageTimeline(props: {
|
|||||||
const [title, setTitle] = createStore({
|
const [title, setTitle] = createStore({
|
||||||
draft: "",
|
draft: "",
|
||||||
editing: false,
|
editing: false,
|
||||||
|
saving: false,
|
||||||
menuOpen: false,
|
menuOpen: false,
|
||||||
pendingRename: false,
|
pendingRename: false,
|
||||||
pendingShare: false,
|
pendingShare: false,
|
||||||
@@ -319,6 +335,38 @@ export function MessageTimeline(props: {
|
|||||||
|
|
||||||
let more: HTMLButtonElement | undefined
|
let more: HTMLButtonElement | undefined
|
||||||
|
|
||||||
|
const [req, setReq] = createStore({ share: false, unshare: false })
|
||||||
|
|
||||||
|
const shareSession = () => {
|
||||||
|
const id = sessionID()
|
||||||
|
if (!id || req.share) return
|
||||||
|
if (!shareEnabled()) return
|
||||||
|
setReq("share", true)
|
||||||
|
globalSDK.client.session
|
||||||
|
.share({ sessionID: id, directory: sdk.directory })
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
console.error("Failed to share session", err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setReq("share", false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const unshareSession = () => {
|
||||||
|
const id = sessionID()
|
||||||
|
if (!id || req.unshare) return
|
||||||
|
if (!shareEnabled()) return
|
||||||
|
setReq("unshare", true)
|
||||||
|
globalSDK.client.session
|
||||||
|
.unshare({ sessionID: id, directory: sdk.directory })
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
console.error("Failed to unshare session", err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setReq("unshare", false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const viewShare = () => {
|
const viewShare = () => {
|
||||||
const url = shareUrl()
|
const url = shareUrl()
|
||||||
if (!url) return
|
if (!url) return
|
||||||
@@ -334,54 +382,6 @@ export function MessageTimeline(props: {
|
|||||||
return language.t("common.requestFailed")
|
return language.t("common.requestFailed")
|
||||||
}
|
}
|
||||||
|
|
||||||
const shareMutation = useMutation(() => ({
|
|
||||||
mutationFn: (id: string) => globalSDK.client.session.share({ sessionID: id, directory: sdk.directory }),
|
|
||||||
onError: (err) => {
|
|
||||||
console.error("Failed to share session", err)
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const unshareMutation = useMutation(() => ({
|
|
||||||
mutationFn: (id: string) => globalSDK.client.session.unshare({ sessionID: id, directory: sdk.directory }),
|
|
||||||
onError: (err) => {
|
|
||||||
console.error("Failed to unshare session", err)
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const titleMutation = useMutation(() => ({
|
|
||||||
mutationFn: (input: { id: string; title: string }) =>
|
|
||||||
sdk.client.session.update({ sessionID: input.id, title: input.title }),
|
|
||||||
onSuccess: (_, input) => {
|
|
||||||
sync.set(
|
|
||||||
produce((draft) => {
|
|
||||||
const index = draft.session.findIndex((s) => s.id === input.id)
|
|
||||||
if (index !== -1) draft.session[index].title = input.title
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
setTitle("editing", false)
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
showToast({
|
|
||||||
title: language.t("common.requestFailed"),
|
|
||||||
description: errorMessage(err),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const shareSession = () => {
|
|
||||||
const id = sessionID()
|
|
||||||
if (!id || shareMutation.isPending) return
|
|
||||||
if (!shareEnabled()) return
|
|
||||||
shareMutation.mutate(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const unshareSession = () => {
|
|
||||||
const id = sessionID()
|
|
||||||
if (!id || unshareMutation.isPending) return
|
|
||||||
if (!shareEnabled()) return
|
|
||||||
unshareMutation.mutate(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
sessionKey,
|
sessionKey,
|
||||||
@@ -389,6 +389,7 @@ export function MessageTimeline(props: {
|
|||||||
setTitle({
|
setTitle({
|
||||||
draft: "",
|
draft: "",
|
||||||
editing: false,
|
editing: false,
|
||||||
|
saving: false,
|
||||||
menuOpen: false,
|
menuOpen: false,
|
||||||
pendingRename: false,
|
pendingRename: false,
|
||||||
pendingShare: false,
|
pendingShare: false,
|
||||||
@@ -407,22 +408,40 @@ export function MessageTimeline(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeTitleEditor = () => {
|
const closeTitleEditor = () => {
|
||||||
if (titleMutation.isPending) return
|
if (title.saving) return
|
||||||
setTitle("editing", false)
|
setTitle({ editing: false, saving: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveTitleEditor = () => {
|
const saveTitleEditor = async () => {
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!id) return
|
if (!id) return
|
||||||
if (titleMutation.isPending) return
|
if (title.saving) return
|
||||||
|
|
||||||
const next = title.draft.trim()
|
const next = title.draft.trim()
|
||||||
if (!next || next === (titleValue() ?? "")) {
|
if (!next || next === (titleValue() ?? "")) {
|
||||||
setTitle("editing", false)
|
setTitle({ editing: false, saving: false })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
titleMutation.mutate({ id, title: next })
|
setTitle("saving", true)
|
||||||
|
await sdk.client.session
|
||||||
|
.update({ sessionID: id, title: next })
|
||||||
|
.then(() => {
|
||||||
|
sync.set(
|
||||||
|
produce((draft) => {
|
||||||
|
const index = draft.session.findIndex((s) => s.id === id)
|
||||||
|
if (index !== -1) draft.session[index].title = next
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setTitle({ editing: false, saving: false })
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setTitle("saving", false)
|
||||||
|
showToast({
|
||||||
|
title: language.t("common.requestFailed"),
|
||||||
|
description: errorMessage(err),
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => {
|
const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => {
|
||||||
@@ -660,15 +679,17 @@ export function MessageTimeline(props: {
|
|||||||
<div
|
<div
|
||||||
class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||||
style={{
|
style={{
|
||||||
width: working() ? "16px" : "0px",
|
width: slot.open ? "16px" : "0px",
|
||||||
"margin-right": working() ? "8px" : "0px",
|
"margin-right": slot.open ? "8px" : "0px",
|
||||||
}}
|
}}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<Show when={workingStatus() !== "hidden"}>
|
<Show when={slot.show}>
|
||||||
<div
|
<div
|
||||||
class="transition-opacity duration-200 ease-out"
|
class="transition-opacity duration-200 ease-out"
|
||||||
classList={{ "opacity-0": workingStatus() === "hiding" }}
|
classList={{
|
||||||
|
"opacity-0": slot.fade,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Spinner class="size-4" style={{ color: tint() ?? "var(--icon-interactive-base)" }} />
|
<Spinner class="size-4" style={{ color: tint() ?? "var(--icon-interactive-base)" }} />
|
||||||
</div>
|
</div>
|
||||||
@@ -691,7 +712,7 @@ export function MessageTimeline(props: {
|
|||||||
titleRef = el
|
titleRef = el
|
||||||
}}
|
}}
|
||||||
value={title.draft}
|
value={title.draft}
|
||||||
disabled={titleMutation.isPending}
|
disabled={title.saving}
|
||||||
class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]"
|
class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]"
|
||||||
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
||||||
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
||||||
@@ -842,9 +863,9 @@ export function MessageTimeline(props: {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
onClick={shareSession}
|
onClick={shareSession}
|
||||||
disabled={shareMutation.isPending}
|
disabled={req.share}
|
||||||
>
|
>
|
||||||
{shareMutation.isPending
|
{req.share
|
||||||
? language.t("session.share.action.publishing")
|
? language.t("session.share.action.publishing")
|
||||||
: language.t("session.share.action.publish")}
|
: language.t("session.share.action.publish")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -865,9 +886,9 @@ export function MessageTimeline(props: {
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
class="w-full shadow-none border border-border-weak-base"
|
class="w-full shadow-none border border-border-weak-base"
|
||||||
onClick={unshareSession}
|
onClick={unshareSession}
|
||||||
disabled={unshareMutation.isPending}
|
disabled={req.unshare}
|
||||||
>
|
>
|
||||||
{unshareMutation.isPending
|
{req.unshare
|
||||||
? language.t("session.share.action.unpublishing")
|
? language.t("session.share.action.unpublishing")
|
||||||
: language.t("session.share.action.unpublish")}
|
: language.t("session.share.action.unpublish")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -876,7 +897,7 @@ export function MessageTimeline(props: {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
onClick={viewShare}
|
onClick={viewShare}
|
||||||
disabled={unshareMutation.isPending}
|
disabled={req.unshare}
|
||||||
>
|
>
|
||||||
{language.t("session.share.action.view")}
|
{language.t("session.share.action.view")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -894,10 +915,10 @@ export function MessageTimeline(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
role="log"
|
role="log"
|
||||||
data-slot="session-turn-list"
|
class="flex flex-col gap-12 items-start justify-start pb-16 transition-[margin]"
|
||||||
class="flex flex-col items-start justify-start pb-16 transition-[margin]"
|
|
||||||
classList={{
|
classList={{
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
||||||
@@ -924,15 +945,7 @@ export function MessageTimeline(props: {
|
|||||||
{(messageID) => {
|
{(messageID) => {
|
||||||
const active = createMemo(() => activeMessageID() === messageID)
|
const active = createMemo(() => activeMessageID() === messageID)
|
||||||
const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], {
|
const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], {
|
||||||
equals: (a, b) =>
|
equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
|
||||||
a.length === b.length &&
|
|
||||||
a.every(
|
|
||||||
(c, i) =>
|
|
||||||
c.path === b[i].path &&
|
|
||||||
c.comment === b[i].comment &&
|
|
||||||
c.selection?.startLine === b[i].selection?.startLine &&
|
|
||||||
c.selection?.endLine === b[i].selection?.endLine,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
const commentCount = createMemo(() => comments().length)
|
const commentCount = createMemo(() => comments().length)
|
||||||
return (
|
return (
|
||||||
@@ -943,10 +956,7 @@ export function MessageTimeline(props: {
|
|||||||
"min-w-0 w-full max-w-full": true,
|
"min-w-0 w-full max-w-full": true,
|
||||||
"md:max-w-200 2xl:max-w-[1000px]": props.centered,
|
"md:max-w-200 2xl:max-w-[1000px]": props.centered,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{ "content-visibility": "auto", "contain-intrinsic-size": "auto 500px" }}
|
||||||
"content-visibility": active() ? undefined : "auto",
|
|
||||||
"contain-intrinsic-size": active() ? undefined : "auto 500px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Show when={commentCount() > 0}>
|
<Show when={commentCount() > 0}>
|
||||||
<div class="w-full px-4 md:px-5 pb-2">
|
<div class="w-full px-4 md:px-5 pb-2">
|
||||||
@@ -991,7 +1001,6 @@ export function MessageTimeline(props: {
|
|||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={sessionID() ?? ""}
|
sessionID={sessionID() ?? ""}
|
||||||
messageID={messageID}
|
messageID={messageID}
|
||||||
messages={sessionMessages()}
|
|
||||||
actions={props.actions}
|
actions={props.actions}
|
||||||
active={active()}
|
active={active()}
|
||||||
status={active() ? sessionStatus() : undefined}
|
status={active() ? sessionStatus() : undefined}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
|
|
||||||
import FileTree from "@/components/file-tree"
|
import FileTree from "@/components/file-tree"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||||
|
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||||
import { SessionContextTab, SortableTab, FileVisual } from "@/components/session"
|
import { SessionContextTab, SortableTab, FileVisual } from "@/components/session"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { useFile, type SelectedLineRange } from "@/context/file"
|
import { useFile, type SelectedLineRange } from "@/context/file"
|
||||||
@@ -292,11 +293,9 @@ export function SessionSidePanel(props: {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
iconSize="large"
|
iconSize="large"
|
||||||
class="!rounded-md"
|
class="!rounded-md"
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
void import("@/components/dialog-select-file").then((x) => {
|
dialog.show(() => <DialogSelectFile mode="files" onOpenFile={showAllFiles} />)
|
||||||
dialog.show(() => <x.DialogSelectFile mode="files" onOpenFile={showAllFiles} />)
|
}
|
||||||
})
|
|
||||||
}}
|
|
||||||
aria-label={language.t("command.file.open")}
|
aria-label={language.t("command.file.open")}
|
||||||
/>
|
/>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
@@ -439,10 +438,12 @@ export function SessionSidePanel(props: {
|
|||||||
size={layout.fileTree.width()}
|
size={layout.fileTree.width()}
|
||||||
min={200}
|
min={200}
|
||||||
max={480}
|
max={480}
|
||||||
|
collapseThreshold={160}
|
||||||
onResize={(width) => {
|
onResize={(width) => {
|
||||||
props.size.touch()
|
props.size.touch()
|
||||||
layout.fileTree.resize(width)
|
layout.fileTree.resize(width)
|
||||||
}}
|
}}
|
||||||
|
onCollapse={layout.fileTree.close}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { usePrompt } from "@/context/prompt"
|
|||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useTerminal } from "@/context/terminal"
|
import { useTerminal } from "@/context/terminal"
|
||||||
|
import { DialogSelectFile } from "@/components/dialog-select-file"
|
||||||
|
import { DialogSelectModel } from "@/components/dialog-select-model"
|
||||||
|
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
||||||
|
import { DialogFork } from "@/components/dialog-fork"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { findLast } from "@opencode-ai/util/array"
|
import { findLast } from "@opencode-ai/util/array"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
@@ -251,13 +255,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
id: "file.open",
|
id: "file.open",
|
||||||
title: language.t("command.file.open"),
|
title: language.t("command.file.open"),
|
||||||
description: language.t("palette.search.placeholder"),
|
description: language.t("palette.search.placeholder"),
|
||||||
keybind: "mod+k,mod+p",
|
keybind: "mod+p",
|
||||||
slash: "open",
|
slash: "open",
|
||||||
onSelect: () => {
|
onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={showAllFiles} />),
|
||||||
void import("@/components/dialog-select-file").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectFile onOpenFile={showAllFiles} />)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
fileCommand({
|
fileCommand({
|
||||||
id: "tab.close",
|
id: "tab.close",
|
||||||
@@ -333,7 +333,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
id: "message.previous",
|
id: "message.previous",
|
||||||
title: language.t("command.message.previous"),
|
title: language.t("command.message.previous"),
|
||||||
description: language.t("command.message.previous.description"),
|
description: language.t("command.message.previous.description"),
|
||||||
keybind: "mod+alt+[",
|
keybind: "mod+arrowup",
|
||||||
disabled: !params.id,
|
disabled: !params.id,
|
||||||
onSelect: () => navigateMessageByOffset(-1),
|
onSelect: () => navigateMessageByOffset(-1),
|
||||||
}),
|
}),
|
||||||
@@ -341,7 +341,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
id: "message.next",
|
id: "message.next",
|
||||||
title: language.t("command.message.next"),
|
title: language.t("command.message.next"),
|
||||||
description: language.t("command.message.next.description"),
|
description: language.t("command.message.next.description"),
|
||||||
keybind: "mod+alt+]",
|
keybind: "mod+arrowdown",
|
||||||
disabled: !params.id,
|
disabled: !params.id,
|
||||||
onSelect: () => navigateMessageByOffset(1),
|
onSelect: () => navigateMessageByOffset(1),
|
||||||
}),
|
}),
|
||||||
@@ -351,11 +351,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
description: language.t("command.model.choose.description"),
|
description: language.t("command.model.choose.description"),
|
||||||
keybind: "mod+'",
|
keybind: "mod+'",
|
||||||
slash: "model",
|
slash: "model",
|
||||||
onSelect: () => {
|
onSelect: () => dialog.show(() => <DialogSelectModel model={local.model} />),
|
||||||
void import("@/components/dialog-select-model").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectModel model={local.model} />)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
mcpCommand({
|
mcpCommand({
|
||||||
id: "mcp.toggle",
|
id: "mcp.toggle",
|
||||||
@@ -363,11 +359,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
description: language.t("command.mcp.toggle.description"),
|
description: language.t("command.mcp.toggle.description"),
|
||||||
keybind: "mod+;",
|
keybind: "mod+;",
|
||||||
slash: "mcp",
|
slash: "mcp",
|
||||||
onSelect: () => {
|
onSelect: () => dialog.show(() => <DialogSelectMcp />),
|
||||||
void import("@/components/dialog-select-mcp").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogSelectMcp />)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
agentCommand({
|
agentCommand({
|
||||||
id: "agent.cycle",
|
id: "agent.cycle",
|
||||||
@@ -495,11 +487,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
description: language.t("command.session.fork.description"),
|
description: language.t("command.session.fork.description"),
|
||||||
slash: "fork",
|
slash: "fork",
|
||||||
disabled: !params.id || visibleUserMessages().length === 0,
|
disabled: !params.id || visibleUserMessages().length === 0,
|
||||||
onSelect: () => {
|
onSelect: () => dialog.show(() => <DialogFork />),
|
||||||
void import("@/components/dialog-fork").then((x) => {
|
|
||||||
dialog.show(() => <x.DialogFork />)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
...share,
|
...share,
|
||||||
]
|
]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user