Compare commits
5 commits
dev
...
desktop-v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6768da519 |
||
|
|
6fb752ad69 |
||
|
|
e96d0b3426 |
||
|
|
e8326c70e6 |
||
|
|
3292e6e5b2 |
255 changed files with 2249 additions and 8939 deletions
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
|
|
@ -339,7 +339,8 @@ jobs:
|
||||||
env:
|
env:
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
CSC_KEYCHAIN: build.keychain
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
|
|
|
||||||
157
bun.lock
157
bun.lock
|
|
@ -29,7 +29,7 @@
|
||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@corvu/drawer": "catalog:",
|
"@corvu/drawer": "catalog:",
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"bin": {
|
"bin": {
|
||||||
"lildax": "./bin/lildax.cjs",
|
"lildax": "./bin/lildax.cjs",
|
||||||
},
|
},
|
||||||
|
|
@ -144,7 +144,7 @@
|
||||||
},
|
},
|
||||||
"packages/codemode": {
|
"packages/codemode": {
|
||||||
"name": "@opencode-ai/codemode",
|
"name": "@opencode-ai/codemode",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
|
@ -158,7 +158,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
|
|
@ -194,7 +194,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"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",
|
||||||
|
|
@ -221,7 +221,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.82",
|
"@ai-sdk/anthropic": "3.0.82",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
|
|
@ -243,7 +243,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"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",
|
||||||
|
|
@ -267,7 +267,7 @@
|
||||||
},
|
},
|
||||||
"packages/console/support": {
|
"packages/console/support": {
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
|
|
@ -287,7 +287,7 @@
|
||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
|
|
@ -339,7 +339,7 @@
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"gitlab-ai-provider": "6.12.1",
|
"gitlab-ai-provider": "6.12.0",
|
||||||
"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",
|
||||||
|
|
@ -381,16 +381,16 @@
|
||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"drizzle-orm": "catalog:",
|
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"electron-context-menu": "4.1.2",
|
"electron-context-menu": "4.1.2",
|
||||||
"electron-log": "^5",
|
"electron-log": "^5",
|
||||||
"electron-store": "11.0.2",
|
"electron-store": "11.0.2",
|
||||||
"electron-updater": "6.8.9",
|
"electron-updater": "6.8.9",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
|
"marked": "^15",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/artifact": "4.0.0",
|
"@actions/artifact": "4.0.0",
|
||||||
|
|
@ -435,7 +435,7 @@
|
||||||
},
|
},
|
||||||
"packages/effect-drizzle-sqlite": {
|
"packages/effect-drizzle-sqlite": {
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
|
@ -449,7 +449,7 @@
|
||||||
},
|
},
|
||||||
"packages/effect-sqlite-node": {
|
"packages/effect-sqlite-node": {
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
|
|
@ -461,7 +461,7 @@
|
||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/standard-validator": "catalog:",
|
"@hono/standard-validator": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
|
|
@ -493,7 +493,7 @@
|
||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
|
|
@ -509,7 +509,7 @@
|
||||||
},
|
},
|
||||||
"packages/http-recorder": {
|
"packages/http-recorder": {
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.83",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||||
|
|
@ -540,7 +540,7 @@
|
||||||
},
|
},
|
||||||
"packages/llm": {
|
"packages/llm": {
|
||||||
"name": "@opencode-ai/llm",
|
"name": "@opencode-ai/llm",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@smithy/eventstream-codec": "4.2.14",
|
"@smithy/eventstream-codec": "4.2.14",
|
||||||
|
|
@ -559,7 +559,7 @@
|
||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
|
|
@ -592,7 +592,7 @@
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@ff-labs/fff-bun": "0.9.4",
|
"@ff-labs/fff-bun": "0.9.4",
|
||||||
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||||
"@modelcontextprotocol/sdk": "1.29.0",
|
"@modelcontextprotocol/client": "2.0.0-beta.5",
|
||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
|
|
@ -632,7 +632,7 @@
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"gitlab-ai-provider": "6.12.1",
|
"gitlab-ai-provider": "6.12.0",
|
||||||
"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",
|
||||||
|
|
@ -666,6 +666,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.4",
|
"@babel/core": "7.28.4",
|
||||||
|
"@modelcontextprotocol/server": "2.0.0-beta.5",
|
||||||
"@octokit/webhooks-types": "7.6.1",
|
"@octokit/webhooks-types": "7.6.1",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/http-recorder": "workspace:*",
|
"@opencode-ai/http-recorder": "workspace:*",
|
||||||
|
|
@ -690,7 +691,7 @@
|
||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
|
@ -766,7 +767,7 @@
|
||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
|
|
@ -781,7 +782,7 @@
|
||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@opencode-ai/server",
|
"name": "@opencode-ai/server",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
|
|
@ -796,7 +797,7 @@
|
||||||
},
|
},
|
||||||
"packages/session-ui": {
|
"packages/session-ui": {
|
||||||
"name": "@opencode-ai/session-ui",
|
"name": "@opencode-ai/session-ui",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
|
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||||
|
|
@ -811,6 +812,7 @@
|
||||||
"@solid-primitives/media": "2.3.3",
|
"@solid-primitives/media": "2.3.3",
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
|
"@solidjs/router": "catalog:",
|
||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
|
|
@ -840,7 +842,7 @@
|
||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
|
|
@ -853,7 +855,7 @@
|
||||||
},
|
},
|
||||||
"packages/stats/app": {
|
"packages/stats/app": {
|
||||||
"name": "@opencode-ai/stats-app",
|
"name": "@opencode-ai/stats-app",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
|
|
@ -887,7 +889,7 @@
|
||||||
},
|
},
|
||||||
"packages/stats/core": {
|
"packages/stats/core": {
|
||||||
"name": "@opencode-ai/stats-core",
|
"name": "@opencode-ai/stats-core",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-athena": "3.933.0",
|
"@aws-sdk/client-athena": "3.933.0",
|
||||||
"@planetscale/database": "1.19.0",
|
"@planetscale/database": "1.19.0",
|
||||||
|
|
@ -906,7 +908,7 @@
|
||||||
},
|
},
|
||||||
"packages/stats/server": {
|
"packages/stats/server": {
|
||||||
"name": "@opencode-ai/stats-server",
|
"name": "@opencode-ai/stats-server",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-firehose": "3.933.0",
|
"@aws-sdk/client-firehose": "3.933.0",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
|
|
@ -948,7 +950,7 @@
|
||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@opencode-ai/tui",
|
"name": "@opencode-ai/tui",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
|
|
@ -975,7 +977,7 @@
|
||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
|
|
@ -1001,7 +1003,6 @@
|
||||||
"remend": "catalog:",
|
"remend": "catalog:",
|
||||||
"shiki": "catalog:",
|
"shiki": "catalog:",
|
||||||
"solid-list": "catalog:",
|
"solid-list": "catalog:",
|
||||||
"solid-sonner": "catalog:",
|
|
||||||
"strip-ansi": "7.1.2",
|
"strip-ansi": "7.1.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -1027,7 +1028,7 @@
|
||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
|
|
@ -1070,9 +1071,9 @@
|
||||||
],
|
],
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
||||||
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
|
"@modelcontextprotocol/client@2.0.0-beta.5": "patches/@modelcontextprotocol%2Fclient@2.0.0-beta.5.patch",
|
||||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
|
||||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.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",
|
||||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
||||||
|
|
@ -1082,7 +1083,6 @@
|
||||||
"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/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||||
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
|
|
@ -1146,7 +1146,6 @@
|
||||||
"shiki": "4.2.0",
|
"shiki": "4.2.0",
|
||||||
"solid-js": "1.9.10",
|
"solid-js": "1.9.10",
|
||||||
"solid-list": "0.3.0",
|
"solid-list": "0.3.0",
|
||||||
"solid-sonner": "0.3.1",
|
|
||||||
"sst": "4.13.1",
|
"sst": "4.13.1",
|
||||||
"tailwindcss": "4.1.11",
|
"tailwindcss": "4.1.11",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
|
|
@ -1676,8 +1675,6 @@
|
||||||
|
|
||||||
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
||||||
|
|
||||||
"@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
|
|
||||||
|
|
||||||
"@hono/standard-validator": ["@hono/standard-validator@0.2.0", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-pFq0UVAnjzXcDAgqFpDeVL3MOUPrlIh/kPqBDvbCYoThVhhS+Vf37VcdsakdOFFGiqoiYVxp3LifXFhGhp/rgQ=="],
|
"@hono/standard-validator": ["@hono/standard-validator@0.2.0", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-pFq0UVAnjzXcDAgqFpDeVL3MOUPrlIh/kPqBDvbCYoThVhhS+Vf37VcdsakdOFFGiqoiYVxp3LifXFhGhp/rgQ=="],
|
||||||
|
|
||||||
"@ibm/plex": ["@ibm/plex@6.4.1", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.1" } }, "sha512-fnsipQywHt3zWvsnlyYKMikcVI7E2fEwpiPnIHFqlbByXVfQfANAAeJk1IV4mNnxhppUIDlhU0TzwYwL++Rn2g=="],
|
"@ibm/plex": ["@ibm/plex@6.4.1", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.1" } }, "sha512-fnsipQywHt3zWvsnlyYKMikcVI7E2fEwpiPnIHFqlbByXVfQfANAAeJk1IV4mNnxhppUIDlhU0TzwYwL++Rn2g=="],
|
||||||
|
|
@ -1832,7 +1829,11 @@
|
||||||
|
|
||||||
"@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.29.0", "", { "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-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
|
"@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0-beta.5", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "jose": "^6.1.3", "pkce-challenge": "^5.0.0", "zod": "^4.2.0" } }, "sha512-YuuNm5f2TMoFQRje1UqVP8TJRjijCXMz4ckvoVpx1cUXuBEmykWQ2d8R536pek6UKcXT41T5nWc4qR1JFIbEmg=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0-beta.5", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-HKbY9XTbsDy1Y6r2I55TGE3JEapM0vg96e1MUmBIF9LGjos5gjhcIrTz1yvBPLg2aFKHjwhUAQfRdrCEnPxNew=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.0.0-beta.5", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.5", "zod": "^4.2.0" } }, "sha512-i1E5l75rQKsgY/AKAIspgMBH1vEL7dqiK7tHr0L+raYcb0SWOziqNGJXGIG6NY4AlXDWIKGJQGB7Nqfs3oUi5g=="],
|
||||||
|
|
||||||
"@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=="],
|
||||||
|
|
||||||
|
|
@ -3392,7 +3393,7 @@
|
||||||
|
|
||||||
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
||||||
|
|
||||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
"content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||||
|
|
||||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
|
|
@ -3404,8 +3405,6 @@
|
||||||
|
|
||||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||||
|
|
||||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
|
||||||
|
|
||||||
"crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="],
|
"crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="],
|
||||||
|
|
||||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||||
|
|
@ -3710,8 +3709,6 @@
|
||||||
|
|
||||||
"express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "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.15.1", "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-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
|
"express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "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.15.1", "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-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
|
||||||
|
|
||||||
"express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
|
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
|
||||||
|
|
@ -3856,7 +3853,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.12.1", "", { "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-Qn5iHqvjG8yktI5MWaUgdRR94l7O4WtYW0CAbhsCh1Tj0Fei/DeprOYPVyf4Nht1Ix6U2PXSYM32QOHI6Z2TDw=="],
|
"gitlab-ai-provider": ["gitlab-ai-provider@6.12.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-uGan7MQaNfmV5SH9wiMJkZ7D5QxM4KVPRcjIHtPCM5icZjgXl5mrHMjr3/e7TAHa9OjEJKT0a2FxzPgwRtN3PQ=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
|
|
@ -4096,8 +4093,6 @@
|
||||||
|
|
||||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||||
|
|
||||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
|
||||||
|
|
||||||
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
"is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
|
||||||
|
|
||||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||||
|
|
@ -5006,8 +5001,6 @@
|
||||||
|
|
||||||
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
|
||||||
|
|
||||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
|
||||||
|
|
||||||
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
|
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
|
||||||
|
|
||||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||||
|
|
@ -5128,8 +5121,6 @@
|
||||||
|
|
||||||
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
|
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
|
||||||
|
|
||||||
"solid-sonner": ["solid-sonner@0.3.1", "", { "peerDependencies": { "solid-js": "^1.6.0" } }, "sha512-F/+zi9yKJTHh5hX1UGJfkDvyC+F34Vi3jgy44NJwOKCgic1QtAon0b1iT9OsDO77RTgR+PCil+3Y5B8T2Owy1Q=="],
|
|
||||||
|
|
||||||
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
||||||
|
|
||||||
"solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="],
|
"solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="],
|
||||||
|
|
@ -5958,15 +5949,13 @@
|
||||||
|
|
||||||
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
"@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/client/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="],
|
"@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
"@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
"@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/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
|
||||||
|
|
||||||
"@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
"@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||||
|
|
||||||
|
|
@ -6044,6 +6033,8 @@
|
||||||
|
|
||||||
"@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
|
"@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
|
||||||
|
|
||||||
|
"@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||||
|
|
||||||
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
||||||
|
|
||||||
"@opencode-ai/llm/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
"@opencode-ai/llm/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
||||||
|
|
@ -6240,6 +6231,8 @@
|
||||||
|
|
||||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||||
|
|
||||||
|
"body-parser/content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||||
|
|
||||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||||
|
|
@ -6322,6 +6315,8 @@
|
||||||
|
|
||||||
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
||||||
|
|
||||||
|
"express/content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||||
|
|
||||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||||
|
|
||||||
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
@ -6470,8 +6465,6 @@
|
||||||
|
|
||||||
"roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
"roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||||
|
|
||||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
|
||||||
|
|
||||||
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||||
|
|
@ -6800,58 +6793,28 @@
|
||||||
|
|
||||||
"@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
"@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
|
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
"@octokit/auth-app/@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/auth-oauth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-app/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
"@octokit/auth-oauth-app/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-app/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/auth-oauth-app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/auth-oauth-app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-device/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/auth-oauth-device/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-device/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
"@octokit/auth-oauth-device/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-device/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/auth-oauth-device/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/auth-oauth-device/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-user/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/auth-oauth-user/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-user/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
"@octokit/auth-oauth-user/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
|
||||||
|
|
||||||
"@octokit/auth-oauth-user/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/auth-oauth-user/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/auth-oauth-user/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||||
|
|
@ -6864,14 +6827,10 @@
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
"@octokit/graphql/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
"@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="],
|
||||||
|
|
||||||
"@octokit/oauth-methods/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/oauth-methods/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/oauth-methods/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
||||||
|
|
@ -7380,10 +7339,6 @@
|
||||||
|
|
||||||
"@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
"@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
@ -7392,20 +7347,14 @@
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/plugin-paginate-rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
|
||||||
|
|
@ -288,12 +288,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||||
server: {
|
server: {
|
||||||
placement: { region: "aws:us-east-2" },
|
placement: { region: "aws:us-east-2" },
|
||||||
transform: {
|
transform: {
|
||||||
worker: (args) => {
|
worker: {
|
||||||
args.compatibilityFlags = $resolve(args.compatibilityFlags).apply((flags) => [
|
tailConsumers: [{ service: logProcessor.nodes.worker.scriptName }],
|
||||||
...(flags ?? []),
|
|
||||||
"global_fetch_strictly_public",
|
|
||||||
])
|
|
||||||
args.tailConsumers = [{ service: logProcessor.nodes.worker.scriptName }]
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-a7NyYa9vRUEqDfZNDPXXmFO58RDEgioyuGSl5CPBvxo=",
|
"x86_64-linux": "sha256-RFek0QoEEjsgbqmTE/SxQAmPtYyzs0IPR2ugFn5Okrs=",
|
||||||
"aarch64-linux": "sha256-l4OJtSEllHvRhktjcaJYwkXBSaJvsIoyoLusbZfYMcM=",
|
"aarch64-linux": "sha256-BmAxapY1YrAFn7mVq3/6A9+6Au5UIvSqBboHMkyJH3I=",
|
||||||
"aarch64-darwin": "sha256-IIl0BQGs1/HLFh0auiQjiwfSQ2nfHcK2G2BAphYW59c=",
|
"aarch64-darwin": "sha256-Sx3bGWQqLlgoa/RudJxanjSzhFRNklckT2ffnO2I5F4=",
|
||||||
"x86_64-darwin": "sha256-vVeuPyd4ZIRYrHouphTuEb4rkRZLKKTAHe840jNh9rU="
|
"x86_64-darwin": "sha256-CMOhiisHNowg06qadvgg4K+60zrynglwiT0qKYQ4NiA="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@
|
||||||
"@sentry/solid": "10.36.0",
|
"@sentry/solid": "10.36.0",
|
||||||
"@sentry/vite-plugin": "4.6.0",
|
"@sentry/vite-plugin": "4.6.0",
|
||||||
"solid-js": "1.9.10",
|
"solid-js": "1.9.10",
|
||||||
"solid-sonner": "0.3.1",
|
|
||||||
"vite-plugin-solid": "2.11.10",
|
"vite-plugin-solid": "2.11.10",
|
||||||
"@lydell/node-pty": "1.2.0-beta.12"
|
"@lydell/node-pty": "1.2.0-beta.12"
|
||||||
}
|
}
|
||||||
|
|
@ -156,8 +155,8 @@
|
||||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||||
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
||||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
|
||||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
||||||
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch"
|
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
|
||||||
|
"@modelcontextprotocol/client@2.0.0-beta.5": "patches/@modelcontextprotocol%2Fclient@2.0.0-beta.5.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { expect, test } from "@playwright/test"
|
|
||||||
import type { Page } from "@playwright/test"
|
|
||||||
import { fixture, pageMessages } from "../smoke/session-timeline.fixture"
|
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
|
||||||
import { expectAppVisible } from "../utils/waits"
|
|
||||||
|
|
||||||
const NAMES = ["alpha-service", "bravo-web", "charlie-api", "delta-tools", "echo-infra", "foxtrot-docs"]
|
|
||||||
const worktrees = NAMES.map((name) => `/opencode-demo/${name}`)
|
|
||||||
|
|
||||||
// The sixth project sits outside the five-item recent cap, so it is only reachable if the
|
|
||||||
// dialog hands every recent project to the list filter instead of a pre-truncated slice.
|
|
||||||
const OUTSIDE_CAP = "foxtrot-docs"
|
|
||||||
|
|
||||||
// Dialog rows carry data-directory-path; the sidebar project list does not, so this
|
|
||||||
// scopes assertions to the picker instead of matching the sidebar entry of the same name.
|
|
||||||
const rows = (page: Page) => page.locator("[data-directory-path]")
|
|
||||||
const row = (page: Page, name: string) => page.locator(`[data-directory-path*="${name}"]`)
|
|
||||||
|
|
||||||
async function openProjectDialog(page: Page) {
|
|
||||||
await mockOpenCodeServer(page, {
|
|
||||||
sessions: fixture.sessions,
|
|
||||||
provider: fixture.provider,
|
|
||||||
directory: fixture.directory,
|
|
||||||
project: fixture.project,
|
|
||||||
pageMessages,
|
|
||||||
fileList: () => [],
|
|
||||||
findFiles: () => [],
|
|
||||||
})
|
|
||||||
await page.addInitScript((dirs) => {
|
|
||||||
localStorage.setItem(
|
|
||||||
"opencode.global.dat:server",
|
|
||||||
JSON.stringify({
|
|
||||||
projects: { local: dirs.map((worktree: string) => ({ worktree, expanded: false })) },
|
|
||||||
lastProject: {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}, worktrees)
|
|
||||||
await page.goto("/")
|
|
||||||
const add = page.getByRole("button", { name: "Add project" }).first()
|
|
||||||
await expectAppVisible(add)
|
|
||||||
await add.click()
|
|
||||||
await expect(rows(page)).toHaveCount(5)
|
|
||||||
return page.getByRole("textbox").last()
|
|
||||||
}
|
|
||||||
|
|
||||||
test("searches every recent project, not just the five most recent", async ({ page }) => {
|
|
||||||
const search = await openProjectDialog(page)
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(0)
|
|
||||||
|
|
||||||
await search.fill("foxtrot")
|
|
||||||
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("still caps the idle recent list at five projects", async ({ page }) => {
|
|
||||||
await openProjectDialog(page)
|
|
||||||
|
|
||||||
await expect(row(page, NAMES[4])).toHaveCount(1)
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(0)
|
|
||||||
})
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test": "bun run test:unit && bun run test:browser",
|
"test": "bun run test:unit && bun run test:browser",
|
||||||
"test:unit": "bun test --conditions=solid --only-failures --preload ./happydom.ts ./src",
|
"test:unit": "bun test --only-failures --preload ./happydom.ts ./src",
|
||||||
"test:browser": "bun test --conditions=browser --preload ./happydom.ts ./test-browser",
|
"test:browser": "bun test --conditions=browser --preload ./happydom.ts ./test-browser",
|
||||||
"test:unit:watch": "bun test --conditions=solid --watch --preload ./happydom.ts ./src",
|
"test:unit:watch": "bun test --watch --preload ./happydom.ts ./src",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test:e2e:local": "playwright test",
|
"test:e2e:local": "playwright test",
|
||||||
"test:e2e:ui": "playwright test --ui",
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
|
|
||||||
|
|
@ -66,18 +66,11 @@ function Cell(props: {
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"flex min-w-0 items-center": true,
|
"flex min-w-0 items-center": true,
|
||||||
"min-h-[20px] w-fit justify-start px-1.5 py-0.5 text-left": !!props.inline,
|
"min-h-[20px] w-fit flex-row justify-start gap-1.5 px-1.5 py-0.5 text-left": !!props.inline,
|
||||||
"justify-center text-center": !props.inline,
|
"justify-center text-center": !props.inline,
|
||||||
"min-h-[42px] w-full flex-col rounded-[8px] px-0.5 py-1": !props.inline,
|
"min-h-[42px] w-full flex-col rounded-[8px] px-0.5 py-1": !props.inline,
|
||||||
"col-span-2": !!props.wide && !props.inline,
|
"col-span-2": !!props.wide && !props.inline,
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<div
|
|
||||||
classList={{
|
|
||||||
"flex min-w-0": true,
|
|
||||||
"-translate-y-px items-baseline gap-1.5": !!props.inline,
|
|
||||||
"flex-col items-center": !props.inline,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
|
|
@ -98,7 +91,6 @@ function Cell(props: {
|
||||||
{props.value}
|
{props.value}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (props.inline) {
|
if (props.inline) {
|
||||||
|
|
@ -124,26 +116,16 @@ function FocusCell(props: { active: boolean; inline?: boolean; onClick: () => vo
|
||||||
aria-pressed={props.active}
|
aria-pressed={props.active}
|
||||||
classList={{
|
classList={{
|
||||||
"flex min-w-0 items-center font-mono uppercase hover:bg-surface-raised-base focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-border-focus": true,
|
"flex min-w-0 items-center font-mono uppercase hover:bg-surface-raised-base focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-border-focus": true,
|
||||||
"min-h-[20px] w-fit justify-start rounded px-1.5 py-0.5 text-left": !!props.inline,
|
"min-h-[20px] w-fit flex-row justify-start gap-1.5 rounded px-1.5 py-0.5 text-left": !!props.inline,
|
||||||
"min-h-[42px] w-full flex-col justify-center rounded-[8px] px-0.5 py-1 text-center": !props.inline,
|
"min-h-[42px] w-full flex-col justify-center rounded-[8px] px-0.5 py-1 text-center": !props.inline,
|
||||||
"bg-surface-raised-base text-text-strong": props.active,
|
"bg-surface-raised-base text-text-strong": props.active,
|
||||||
}}
|
}}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
|
||||||
<span
|
|
||||||
classList={{
|
|
||||||
flex: true,
|
|
||||||
"-translate-y-px items-baseline gap-1.5": !!props.inline,
|
|
||||||
"flex-col items-center": !props.inline,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span class="text-[10px] leading-none font-black tracking-[0.04em] opacity-70">FOCUS</span>
|
<span class="text-[10px] leading-none font-black tracking-[0.04em] opacity-70">FOCUS</span>
|
||||||
<span
|
<span classList={{ "leading-none font-bold": true, "text-[11px]": !!props.inline, "text-[13px]": !props.inline }}>
|
||||||
classList={{ "leading-none font-bold": true, "text-[11px]": !!props.inline, "text-[13px]": !props.inline }}
|
|
||||||
>
|
|
||||||
{props.active ? "ON" : "OFF"}
|
{props.active ? "ON" : "OFF"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import {
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { ExternalLink } from "@/components/external-link"
|
import { Link } from "@/components/link"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSync } from "@/context/server-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
@ -836,12 +836,12 @@ function ProviderConnection(props: {
|
||||||
<div>{language.t("provider.connect.opencodeZen.line2")}</div>
|
<div>{language.t("provider.connect.opencodeZen.line2")}</div>
|
||||||
<div>
|
<div>
|
||||||
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
||||||
<ExternalLink
|
<Link
|
||||||
href="https://opencode.ai/zen"
|
href="https://opencode.ai/zen"
|
||||||
class="text-v2-text-text-base focus-visible:rounded-xs focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
class="text-v2-text-text-base focus-visible:rounded-xs focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
||||||
>
|
>
|
||||||
{language.t("provider.connect.opencodeZen.visit.link")}
|
{language.t("provider.connect.opencodeZen.visit.link")}
|
||||||
</ExternalLink>
|
</Link>
|
||||||
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -886,9 +886,9 @@ function ProviderConnection(props: {
|
||||||
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
|
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
||||||
<ExternalLink href="https://opencode.ai/zen" tabIndex={-1}>
|
<Link href="https://opencode.ai/zen" tabIndex={-1}>
|
||||||
{language.t("provider.connect.opencodeZen.visit.link")}
|
{language.t("provider.connect.opencodeZen.visit.link")}
|
||||||
</ExternalLink>
|
</Link>
|
||||||
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -967,9 +967,9 @@ function ProviderConnection(props: {
|
||||||
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||||
<div>
|
<div>
|
||||||
{language.t("provider.connect.oauth.code.visit.prefix")}
|
{language.t("provider.connect.oauth.code.visit.prefix")}
|
||||||
<ExternalLink href={store.authorization!.url} class="text-v2-text-text-base">
|
<Link href={store.authorization!.url} class="text-v2-text-text-base">
|
||||||
{language.t("provider.connect.oauth.code.visit.link")}
|
{language.t("provider.connect.oauth.code.visit.link")}
|
||||||
</ExternalLink>
|
</Link>
|
||||||
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
||||||
|
|
@ -1006,9 +1006,7 @@ function ProviderConnection(props: {
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.oauth.code.visit.prefix")}
|
{language.t("provider.connect.oauth.code.visit.prefix")}
|
||||||
<ExternalLink href={store.authorization!.url}>
|
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.code.visit.link")}</Link>
|
||||||
{language.t("provider.connect.oauth.code.visit.link")}
|
|
||||||
</ExternalLink>
|
|
||||||
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
||||||
|
|
@ -1079,9 +1077,7 @@ function ProviderConnection(props: {
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.oauth.auto.visit.prefix")}
|
{language.t("provider.connect.oauth.auto.visit.prefix")}
|
||||||
<ExternalLink href={store.authorization!.url}>
|
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.auto.visit.link")}</Link>
|
||||||
{language.t("provider.connect.oauth.auto.visit.link")}
|
|
||||||
</ExternalLink>
|
|
||||||
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { batch, For } from "solid-js"
|
import { batch, For } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { ExternalLink } from "@/components/external-link"
|
import { Link } from "@/components/link"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSync } from "@/context/server-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
@ -185,9 +185,9 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
||||||
<form onSubmit={save} class="px-2.5 pb-6 flex flex-col gap-6">
|
<form onSubmit={save} class="px-2.5 pb-6 flex flex-col gap-6">
|
||||||
<p class="text-14-regular text-text-base">
|
<p class="text-14-regular text-text-base">
|
||||||
{language.t("provider.custom.description.prefix")}
|
{language.t("provider.custom.description.prefix")}
|
||||||
<ExternalLink href="https://opencode.ai/docs/providers/#custom-provider" tabIndex={-1}>
|
<Link href="https://opencode.ai/docs/providers/#custom-provider" tabIndex={-1}>
|
||||||
{language.t("provider.custom.description.link")}
|
{language.t("provider.custom.description.link")}
|
||||||
</ExternalLink>
|
</Link>
|
||||||
{language.t("provider.custom.description.suffix")}
|
{language.t("provider.custom.description.suffix")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ interface DialogSelectDirectoryProps {
|
||||||
server: ServerConnection.Any
|
server: ServerConnection.Any
|
||||||
}
|
}
|
||||||
|
|
||||||
const RECENT_PROJECT_LIMIT = 5
|
|
||||||
|
|
||||||
type Row = {
|
type Row = {
|
||||||
absolute: string
|
absolute: string
|
||||||
search: string
|
search: string
|
||||||
|
|
@ -59,9 +57,9 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
const [filter, setFilter] = createSignal("")
|
const [filter, setFilter] = createSignal("")
|
||||||
let list: ListRef | undefined
|
let list: ListRef | undefined
|
||||||
|
|
||||||
const missingHome = createMemo(() => !sync.data.path.home)
|
const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory))
|
||||||
const [fallbackPath] = createResource(
|
const [fallbackPath] = createResource(
|
||||||
() => (missingHome() ? true : undefined),
|
() => (missingBase() ? true : undefined),
|
||||||
async (): Promise<Path | undefined> => {
|
async (): Promise<Path | undefined> => {
|
||||||
if ((await sdk.protocol) !== "v1") return
|
if ((await sdk.protocol) !== "v1") return
|
||||||
return sdk.client.path
|
return sdk.client.path
|
||||||
|
|
@ -104,6 +102,7 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
return projects
|
return projects
|
||||||
.map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
|
.map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
|
||||||
.sort((a, b) => b.at - a.at || a.index - b.index)
|
.sort((a, b) => b.at - a.at || a.index - b.index)
|
||||||
|
.slice(0, 5)
|
||||||
.map(({ project }) => {
|
.map(({ project }) => {
|
||||||
const row = toRow(project.worktree, home(), "recent")
|
const row = toRow(project.worktree, home(), "recent")
|
||||||
const name = project.name || getFilename(project.worktree)
|
const name = project.name || getFilename(project.worktree)
|
||||||
|
|
@ -117,10 +116,7 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
const items = async (value: string) => {
|
const items = async (value: string) => {
|
||||||
const results = await directories(value)
|
const results = await directories(value)
|
||||||
const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
|
const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
|
||||||
// Cap the idle list only. Once a query narrows the results, every project stays searchable.
|
return uniqueRows([...recentProjects(), ...directoryRows])
|
||||||
const recent = recentProjects()
|
|
||||||
const visible = value ? recent : recent.slice(0, RECENT_PROJECT_LIMIT)
|
|
||||||
return uniqueRows([...visible, ...directoryRows])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve(absolute: string) {
|
function resolve(absolute: string) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function DialogUsageExceeded(props: DialogGoUpsellProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
|
||||||
const runAction = () => {
|
const runAction = () => {
|
||||||
if (props.link) platform.openExternal(props.link)
|
if (props.link) platform.openLink(props.link)
|
||||||
props.onClose?.()
|
props.onClose?.()
|
||||||
dialog.close()
|
dialog.close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { ComponentProps, splitProps } from "solid-js"
|
|
||||||
|
|
||||||
export interface ExternalLinkProps extends Omit<ComponentProps<"a">, "href"> {
|
|
||||||
href: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ExternalLink(props: ExternalLinkProps) {
|
|
||||||
const [local, rest] = splitProps(props, ["href", "children", "class", "target", "rel"])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={local.href}
|
|
||||||
class={`text-text-strong underline ${local.class ?? ""}`}
|
|
||||||
target={local.target ?? "_blank"}
|
|
||||||
rel={local.rel ?? "noopener noreferrer"}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{local.children}
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
26
packages/app/src/components/link.tsx
Normal file
26
packages/app/src/components/link.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { ComponentProps, splitProps } from "solid-js"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
|
||||||
|
export interface LinkProps extends Omit<ComponentProps<"a">, "href"> {
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link(props: LinkProps) {
|
||||||
|
const platform = usePlatform()
|
||||||
|
const [local, rest] = splitProps(props, ["href", "children", "class"])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={local.href}
|
||||||
|
class={`text-text-strong underline ${local.class ?? ""}`}
|
||||||
|
onClick={(event) => {
|
||||||
|
if (!local.href) return
|
||||||
|
event.preventDefault()
|
||||||
|
platform.openLink(local.href)
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,6 @@ export function PromptInputV2Composer(props: PromptInputV2ComposerProps) {
|
||||||
controller={props.controller}
|
controller={props.controller}
|
||||||
borderUnderlay={props.borderUnderlay}
|
borderUnderlay={props.borderUnderlay}
|
||||||
class={props.class}
|
class={props.class}
|
||||||
variantControlVisible={!props.controller.model.loading}
|
|
||||||
attachKeybind={command.keybindParts("file.attach")}
|
attachKeybind={command.keybindParts("file.attach")}
|
||||||
attachShortcut={command.keybind("file.attach")}
|
attachShortcut={command.keybind("file.attach")}
|
||||||
modelControl={
|
modelControl={
|
||||||
|
|
@ -344,7 +343,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
||||||
if (item?.commentID) comments.remove(item.path, item.commentID)
|
if (item?.commentID) comments.remove(item.path, item.commentID)
|
||||||
},
|
},
|
||||||
openAttachment: (attachment) =>
|
openAttachment: (attachment) =>
|
||||||
dialog.show(() => <ImagePreview src={attachment.blob.url} alt={attachment.filename} />),
|
dialog.show(() => <ImagePreview src={attachment.dataUrl} alt={attachment.filename} />),
|
||||||
openContext(key) {
|
openContext(key) {
|
||||||
const item = controller.contextItem(key)
|
const item = controller.contextItem(key)
|
||||||
if (item) openComment(item, props, sync, layout, files, comments)
|
if (item) openComment(item, props, sync, layout, files, comments)
|
||||||
|
|
@ -368,7 +367,6 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
||||||
title: language.t("prompt.toast.pasteUnsupported.title"),
|
title: language.t("prompt.toast.pasteUnsupported.title"),
|
||||||
description: language.t("prompt.toast.pasteUnsupported.description"),
|
description: language.t("prompt.toast.pasteUnsupported.description"),
|
||||||
}),
|
}),
|
||||||
duplicate: () => showToast({ title: language.t("prompt.toast.attachmentDuplicate.title") }),
|
|
||||||
onError: (error) =>
|
onError: (error) =>
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
|
|
@ -377,7 +375,6 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
||||||
}),
|
}),
|
||||||
readClipboardImage: platform.readClipboardImage,
|
readClipboardImage: platform.readClipboardImage,
|
||||||
getPathForFile: platform.getPathForFile,
|
getPathForFile: platform.getPathForFile,
|
||||||
store: platform.draftStore?.putBlob,
|
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
placeholder: designPlaceholder,
|
placeholder: designPlaceholder,
|
||||||
|
|
|
||||||
|
|
@ -1489,7 +1489,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
<PromptImageAttachments
|
<PromptImageAttachments
|
||||||
attachments={imageAttachments()}
|
attachments={imageAttachments()}
|
||||||
onOpen={(attachment) =>
|
onOpen={(attachment) =>
|
||||||
dialog.show(() => <ImagePreview src={attachment.blob.url} alt={attachment.filename} />)
|
dialog.show(() => <ImagePreview src={attachment.dataUrl} alt={attachment.filename} />)
|
||||||
}
|
}
|
||||||
onRemove={removeAttachment}
|
onRemove={removeAttachment}
|
||||||
removeLabel={language.t("prompt.attachment.remove")}
|
removeLabel={language.t("prompt.attachment.remove")}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,28 @@ import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { type ContentPart, type ImageAttachmentPart, type usePrompt } from "@/context/prompt"
|
import { type ContentPart, type ImageAttachmentPart, type usePrompt } from "@/context/prompt"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { uuid } from "@/utils/uuid"
|
import { uuid } from "@/utils/uuid"
|
||||||
import { getCursorPosition } from "./editor-dom"
|
import { getCursorPosition } from "./editor-dom"
|
||||||
import { createBlobReference, type DraftStore } from "@/utils/draft-store"
|
|
||||||
import { attachmentMime } from "./files"
|
import { attachmentMime } from "./files"
|
||||||
import { normalizePaste, pasteMode } from "./paste"
|
import { normalizePaste, pasteMode } from "./paste"
|
||||||
|
|
||||||
|
function dataUrl(file: File, mime: string) {
|
||||||
|
return new Promise<string>((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.addEventListener("error", () => resolve(""))
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
const value = typeof reader.result === "string" ? reader.result : ""
|
||||||
|
const idx = value.indexOf(",")
|
||||||
|
if (idx === -1) {
|
||||||
|
resolve(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(`data:${mime};base64,${value.slice(idx + 1)}`)
|
||||||
|
})
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type PromptTarget = Pick<ReturnType<ReturnType<typeof usePrompt>["capture"]>, "current" | "cursor" | "set">
|
type PromptTarget = Pick<ReturnType<ReturnType<typeof usePrompt>["capture"]>, "current" | "cursor" | "set">
|
||||||
type AttachmentTarget = { prompt: PromptTarget; cursor: number | undefined }
|
type AttachmentTarget = { prompt: PromptTarget; cursor: number | undefined }
|
||||||
|
|
||||||
|
|
@ -21,7 +36,6 @@ type PromptAttachmentsCoreInput = {
|
||||||
warn?: () => void
|
warn?: () => void
|
||||||
readClipboardImage?: () => Promise<File | null>
|
readClipboardImage?: () => Promise<File | null>
|
||||||
getPathForFile?: (file: File) => string
|
getPathForFile?: (file: File) => string
|
||||||
draftStore?: DraftStore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptAttachmentsInput = {
|
export type PromptAttachmentsInput = {
|
||||||
|
|
@ -51,13 +65,16 @@ export function createPromptAttachmentsCore(input: PromptAttachmentsCoreInput) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = await dataUrl(file, mime)
|
||||||
|
if (!url) return false
|
||||||
|
|
||||||
const attachment: ImageAttachmentPart = {
|
const attachment: ImageAttachmentPart = {
|
||||||
type: "image",
|
type: "image",
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
sourcePath: input.getPathForFile?.(file) || undefined,
|
sourcePath: input.getPathForFile?.(file) || undefined,
|
||||||
mime,
|
mime,
|
||||||
blob: input.draftStore ? await input.draftStore.putBlob(file) : await createBlobReference(file),
|
dataUrl: url,
|
||||||
}
|
}
|
||||||
target.prompt.set([...target.prompt.current(), attachment], target.cursor)
|
target.prompt.set([...target.prompt.current(), attachment], target.cursor)
|
||||||
return true
|
return true
|
||||||
|
|
@ -149,10 +166,8 @@ export function createPromptAttachmentsCore(input: PromptAttachmentsCoreInput) {
|
||||||
|
|
||||||
export function createPromptAttachments(input: PromptAttachmentsInput) {
|
export function createPromptAttachments(input: PromptAttachmentsInput) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const platform = usePlatform()
|
|
||||||
const attachments = createPromptAttachmentsCore({
|
const attachments = createPromptAttachmentsCore({
|
||||||
...input,
|
...input,
|
||||||
draftStore: platform.draftStore,
|
|
||||||
capture: input.prompt.capture,
|
capture: input.prompt.capture,
|
||||||
warn: () => {
|
warn: () => {
|
||||||
showToast({
|
showToast({
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ type ContextFile = {
|
||||||
type BuildRequestPartsInput = {
|
type BuildRequestPartsInput = {
|
||||||
prompt: Prompt
|
prompt: Prompt
|
||||||
context: ContextFile[]
|
context: ContextFile[]
|
||||||
images: (Omit<ImageAttachmentPart, "blob"> & { dataUrl: string })[]
|
images: ImageAttachmentPart[]
|
||||||
text: string
|
text: string
|
||||||
messageID: string
|
messageID: string
|
||||||
sessionID: string
|
sessionID: string
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import type { Prompt } from "@/context/prompt"
|
import type { Prompt } from "@/context/prompt"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import {
|
import { prependHistoryEntry, type PromptHistoryComment, type PromptHistoryStoredEntry } from "./history"
|
||||||
clonePromptHistoryComments,
|
|
||||||
clonePromptParts,
|
|
||||||
prependHistoryEntry,
|
|
||||||
type PromptHistoryComment,
|
|
||||||
type PromptHistoryStoredEntry,
|
|
||||||
} from "./history"
|
|
||||||
|
|
||||||
export type PromptInputHistory = {
|
export type PromptInputHistory = {
|
||||||
entries: (mode: "normal" | "shell") => PromptHistoryStoredEntry[]
|
entries: (mode: "normal" | "shell") => PromptHistoryStoredEntry[]
|
||||||
|
|
@ -41,23 +35,13 @@ export function createPromptInputHistory(): PromptInputHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPersistedPromptInputHistory() {
|
export function createPersistedPromptInputHistory() {
|
||||||
const [normal, setNormal, normalInit] = persisted(
|
const [normal, setNormal] = persisted(
|
||||||
Persist.prompt(Persist.global("prompt-history", ["prompt-history.v1"])),
|
Persist.global("prompt-history", ["prompt-history.v1"]),
|
||||||
createStore<PromptHistoryState>({ entries: [] }),
|
createStore<PromptHistoryState>({ entries: [] }),
|
||||||
)
|
)
|
||||||
const [shell, setShell, shellInit] = persisted(
|
const [shell, setShell] = persisted(
|
||||||
Persist.prompt(Persist.global("prompt-history-shell", ["prompt-history-shell.v1"])),
|
Persist.global("prompt-history-shell", ["prompt-history-shell.v1"]),
|
||||||
createStore<PromptHistoryState>({ entries: [] }),
|
createStore<PromptHistoryState>({ entries: [] }),
|
||||||
)
|
)
|
||||||
const history = createPromptInputHistoryStore(normal, setNormal, shell, setShell)
|
return createPromptInputHistoryStore(normal, setNormal, shell, setShell)
|
||||||
return {
|
|
||||||
...history,
|
|
||||||
add(prompt: Prompt, mode: "normal" | "shell", comments: PromptHistoryComment[]) {
|
|
||||||
const ready = mode === "shell" ? shellInit : normalInit
|
|
||||||
if (!(ready instanceof Promise)) return history.add(prompt, mode, comments)
|
|
||||||
const saved = clonePromptParts(prompt)
|
|
||||||
const metadata = clonePromptHistoryComments(comments)
|
|
||||||
void ready.then(() => history.add(saved, mode, metadata))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ describe("prompt-input history", () => {
|
||||||
end: 12,
|
end: 12,
|
||||||
selection: { startLine: 1, startChar: 1, endLine: 2, endChar: 1 },
|
selection: { startLine: 1, startChar: 1, endLine: 2, endChar: 1 },
|
||||||
},
|
},
|
||||||
{ type: "image", id: "1", filename: "img.png", mime: "image/png", blob: { id: "blob", url: "blob:test" } },
|
{ type: "image", id: "1", filename: "img.png", mime: "image/png", dataUrl: "data:image/png;base64,abc" },
|
||||||
]
|
]
|
||||||
const copy = clonePromptParts(original)
|
const copy = clonePromptParts(original)
|
||||||
expect(copy).not.toBe(original)
|
expect(copy).not.toBe(original)
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (p
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={attachment.blob.url}
|
src={attachment.dataUrl}
|
||||||
alt={attachment.filename}
|
alt={attachment.filename}
|
||||||
class={props.newLayoutDesigns ? imageClassV2 : imageClass}
|
class={props.newLayoutDesigns ? imageClassV2 : imageClass}
|
||||||
onClick={() => props.onOpen(attachment)}
|
onClick={() => props.onOpen(attachment)}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import { ScopedKey } from "@/utils/server-scope"
|
||||||
import { createPromptSubmissionState } from "./submission-state"
|
import { createPromptSubmissionState } from "./submission-state"
|
||||||
import { normalizeSessionInfo } from "@/utils/session"
|
import { normalizeSessionInfo } from "@/utils/session"
|
||||||
import { Event } from "@opencode-ai/schema/event"
|
import { Event } from "@opencode-ai/schema/event"
|
||||||
import { blobDataUrl } from "@/utils/draft-store"
|
|
||||||
|
|
||||||
type PendingPrompt = {
|
type PendingPrompt = {
|
||||||
abort: AbortController
|
abort: AbortController
|
||||||
|
|
@ -96,12 +95,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||||
providerID: input.draft.model.providerID,
|
providerID: input.draft.model.providerID,
|
||||||
variant: input.draft.variant,
|
variant: input.draft.variant,
|
||||||
},
|
},
|
||||||
files: await Promise.all(
|
files: images.map((attachment) => ({
|
||||||
images.map(async (attachment) => ({
|
uri: attachment.dataUrl,
|
||||||
uri: await blobDataUrl(attachment.blob, attachment.mime),
|
|
||||||
name: attachment.filename,
|
name: attachment.filename,
|
||||||
})),
|
})),
|
||||||
),
|
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -111,16 +108,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageID = input.messageID ?? Identifier.ascending("message")
|
const messageID = input.messageID ?? Identifier.ascending("message")
|
||||||
const encodedImages = await Promise.all(
|
|
||||||
images.map(async (attachment) => ({
|
|
||||||
...attachment,
|
|
||||||
dataUrl: await blobDataUrl(attachment.blob, attachment.mime),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
const { requestParts, optimisticParts } = buildRequestParts({
|
const { requestParts, optimisticParts } = buildRequestParts({
|
||||||
prompt: input.draft.prompt,
|
prompt: input.draft.prompt,
|
||||||
context: input.draft.context,
|
context: input.draft.context,
|
||||||
images: encodedImages,
|
images,
|
||||||
text,
|
text,
|
||||||
sessionID: input.draft.sessionID,
|
sessionID: input.draft.sessionID,
|
||||||
messageID,
|
messageID,
|
||||||
|
|
@ -525,12 +516,10 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
arguments: args.join(" "),
|
arguments: args.join(" "),
|
||||||
agent,
|
agent,
|
||||||
model: { id: model.modelID, providerID: model.providerID, variant },
|
model: { id: model.modelID, providerID: model.providerID, variant },
|
||||||
files: await Promise.all(
|
files: images.map((attachment) => ({
|
||||||
images.map(async (attachment) => ({
|
uri: attachment.dataUrl,
|
||||||
uri: await blobDataUrl(attachment.blob, attachment.mime),
|
|
||||||
name: attachment.filename,
|
name: attachment.filename,
|
||||||
})),
|
})),
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
serverSync().session.set("session_status", session.id, { type: "idle" })
|
serverSync().session.set("session_status", session.id, { type: "idle" })
|
||||||
|
|
|
||||||
|
|
@ -289,9 +289,9 @@ export function SessionHeader() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={search() && centerMount()} keyed>
|
<Show when={search() && centerMount()}>
|
||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount}>
|
<Portal mount={mount()}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -308,10 +308,10 @@ export function SessionHeader() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={hotkey()} keyed>
|
<Show when={hotkey()}>
|
||||||
{(keybind) => (
|
{(keybind) => (
|
||||||
<Keybind class="shrink-0 !border-0 !bg-transparent !shadow-none px-0 text-text-weaker">
|
<Keybind class="shrink-0 !border-0 !bg-transparent !shadow-none px-0 text-text-weaker">
|
||||||
{keybind}
|
{keybind()}
|
||||||
</Keybind>
|
</Keybind>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -319,9 +319,9 @@ export function SessionHeader() {
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={rightMount()} keyed>
|
<Show when={rightMount()}>
|
||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount}>
|
<Portal mount={mount()}>
|
||||||
<Show
|
<Show
|
||||||
when={isV2}
|
when={isV2}
|
||||||
fallback={
|
fallback={
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import {
|
||||||
} from "@/context/settings"
|
} from "@/context/settings"
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
|
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
|
||||||
import { ExternalLink } from "./external-link"
|
import { Link } from "./link"
|
||||||
import { SettingsList } from "./settings-list"
|
import { SettingsList } from "./settings-list"
|
||||||
|
|
||||||
let demoSoundState = {
|
let demoSoundState = {
|
||||||
|
|
@ -482,7 +482,7 @@ export const SettingsGeneral: Component = () => {
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
{language.t("settings.general.row.theme.description")}{" "}
|
{language.t("settings.general.row.theme.description")}{" "}
|
||||||
<ExternalLink href="https://opencode.ai/docs/themes/">{language.t("common.learnMore")}</ExternalLink>
|
<Link href="https://opencode.ai/docs/themes/">{language.t("common.learnMore")}</Link>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import { onCleanup } from "solid-js"
|
|
||||||
|
|
||||||
export type ShellOption = {
|
|
||||||
path: string
|
|
||||||
name: string
|
|
||||||
acceptable: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShellSelectOption = {
|
|
||||||
id: string
|
|
||||||
value: string
|
|
||||||
name: string
|
|
||||||
terminalOnly: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createShellOptions(input: { shells: ShellOption[]; current: string | undefined }) {
|
|
||||||
const counts = input.shells.reduce((result, shell) => {
|
|
||||||
result.set(shell.name, (result.get(shell.name) ?? 0) + 1)
|
|
||||||
return result
|
|
||||||
}, new Map<string, number>())
|
|
||||||
const options: ShellSelectOption[] = [
|
|
||||||
{ id: "auto", value: "", name: "", terminalOnly: false },
|
|
||||||
...input.shells.map((shell) => {
|
|
||||||
const ambiguous = (counts.get(shell.name) ?? 0) > 1
|
|
||||||
const name = ambiguous ? shell.path : shell.name
|
|
||||||
return {
|
|
||||||
id: shell.path,
|
|
||||||
value: ambiguous ? shell.path : shell.name,
|
|
||||||
name,
|
|
||||||
terminalOnly: !shell.acceptable,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
if (input.current && !options.some((option) => option.value === input.current)) {
|
|
||||||
options.push({ id: input.current, value: input.current, name: input.current, terminalOnly: false })
|
|
||||||
}
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSoundPreviewController(player: (id: string | undefined) => Promise<(() => void) | undefined>) {
|
|
||||||
let cleanup: (() => void) | undefined
|
|
||||||
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
||||||
let run = 0
|
|
||||||
|
|
||||||
const stop = () => {
|
|
||||||
run += 1
|
|
||||||
cleanup?.()
|
|
||||||
clearTimeout(timeout)
|
|
||||||
cleanup = undefined
|
|
||||||
timeout = undefined
|
|
||||||
}
|
|
||||||
const play = (id: string | undefined) => {
|
|
||||||
stop()
|
|
||||||
if (!id) return
|
|
||||||
const current = ++run
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
timeout = undefined
|
|
||||||
void player(id).then((next) => {
|
|
||||||
if (run === current) {
|
|
||||||
cleanup = next
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next?.()
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
onCleanup(stop)
|
|
||||||
return { play, stop }
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import { describe, expect, test, vi } from "bun:test"
|
|
||||||
import { createRoot } from "solid-js"
|
|
||||||
import { createShellOptions, createSoundPreviewController } from "./general-controller-behavior"
|
|
||||||
|
|
||||||
describe("settings v2 controllers", () => {
|
|
||||||
test("normalizes shell names and preserves an unavailable configured shell", () => {
|
|
||||||
expect(
|
|
||||||
createShellOptions({
|
|
||||||
shells: [
|
|
||||||
{ path: "/bin/bash", name: "bash", acceptable: true },
|
|
||||||
{ path: "/opt/bash", name: "bash", acceptable: false },
|
|
||||||
{ path: "/bin/zsh", name: "zsh", acceptable: true },
|
|
||||||
],
|
|
||||||
current: "fish",
|
|
||||||
}),
|
|
||||||
).toEqual([
|
|
||||||
{ id: "auto", value: "", name: "", terminalOnly: false },
|
|
||||||
{ id: "/bin/bash", value: "/bin/bash", name: "/bin/bash", terminalOnly: false },
|
|
||||||
{ id: "/opt/bash", value: "/opt/bash", name: "/opt/bash", terminalOnly: true },
|
|
||||||
{ id: "/bin/zsh", value: "zsh", name: "zsh", terminalOnly: false },
|
|
||||||
{ id: "fish", value: "fish", name: "fish", terminalOnly: false },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("debounces previews and stops owned audio on disposal", async () => {
|
|
||||||
vi.useFakeTimers()
|
|
||||||
try {
|
|
||||||
const played: string[] = []
|
|
||||||
const stopped: string[] = []
|
|
||||||
const owned = createRoot((dispose) => ({
|
|
||||||
dispose,
|
|
||||||
preview: createSoundPreviewController(async (id) => {
|
|
||||||
played.push(id ?? "")
|
|
||||||
return () => stopped.push(id ?? "")
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
owned.preview.play("first")
|
|
||||||
vi.advanceTimersByTime(99)
|
|
||||||
expect(played).toEqual([])
|
|
||||||
|
|
||||||
owned.preview.play("second")
|
|
||||||
vi.advanceTimersByTime(100)
|
|
||||||
await Promise.resolve()
|
|
||||||
expect(played).toEqual(["second"])
|
|
||||||
|
|
||||||
owned.dispose()
|
|
||||||
expect(stopped).toEqual(["second"])
|
|
||||||
} finally {
|
|
||||||
vi.useRealTimers()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
import { createMemo, createResource, onMount, type Accessor } from "solid-js"
|
|
||||||
import type { ColorScheme } from "@opencode-ai/ui/theme/context"
|
|
||||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
|
||||||
import { usePermission } from "@/context/permission"
|
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
|
||||||
import { useServerSync } from "@/context/server-sync"
|
|
||||||
import {
|
|
||||||
monoDefault,
|
|
||||||
monoFontFamily,
|
|
||||||
monoInput,
|
|
||||||
sansDefault,
|
|
||||||
sansFontFamily,
|
|
||||||
sansInput,
|
|
||||||
terminalDefault,
|
|
||||||
terminalFontFamily,
|
|
||||||
terminalInput,
|
|
||||||
useSettings,
|
|
||||||
} from "@/context/settings"
|
|
||||||
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
|
|
||||||
import { createSoundPreviewController, type ShellOption } from "./general-controller-behavior"
|
|
||||||
|
|
||||||
export { createShellOptions, createSoundPreviewController } from "./general-controller-behavior"
|
|
||||||
export type { ShellOption, ShellSelectOption } from "./general-controller-behavior"
|
|
||||||
|
|
||||||
export function createPermissionScopeController(sessionID: Accessor<string | undefined>) {
|
|
||||||
const permission = usePermission()
|
|
||||||
const serverSync = useServerSync()
|
|
||||||
const directory = createMemo(() => {
|
|
||||||
const id = sessionID()
|
|
||||||
if (!id) return undefined
|
|
||||||
return serverSync().session.lineage.peek(id)?.session.directory
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
accepting: createMemo(() => {
|
|
||||||
const id = sessionID()
|
|
||||||
const dir = directory()
|
|
||||||
if (!id || !dir) return false
|
|
||||||
return permission.isAutoAccepting(id, dir)
|
|
||||||
}),
|
|
||||||
enabled: createMemo(() => !!directory()),
|
|
||||||
set: (checked: boolean) => {
|
|
||||||
const id = sessionID()
|
|
||||||
const dir = directory()
|
|
||||||
if (!id || !dir) return
|
|
||||||
if (checked) return permission.enableAutoAccept(id, dir)
|
|
||||||
permission.disableAutoAccept(id, dir)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createShellSettingsController() {
|
|
||||||
const serverSdk = useServerSDK()
|
|
||||||
const serverSync = useServerSync()
|
|
||||||
const [shells] = createResource(
|
|
||||||
async () => {
|
|
||||||
const sdk = serverSdk()
|
|
||||||
if ((await sdk.protocol) === "v1") return (await sdk.client.pty.shells()).data ?? []
|
|
||||||
return [] as ShellOption[]
|
|
||||||
},
|
|
||||||
{ initialValue: [] as ShellOption[] },
|
|
||||||
)
|
|
||||||
const current = createMemo(() => serverSync().data.config.shell ?? "")
|
|
||||||
|
|
||||||
return {
|
|
||||||
shells: () => shells.latest,
|
|
||||||
current,
|
|
||||||
select: (value: string) => {
|
|
||||||
if (value === current()) return
|
|
||||||
void serverSync().updateConfig({ shell: value })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createAppearanceSettingsController() {
|
|
||||||
const settings = useSettings()
|
|
||||||
const theme = useTheme()
|
|
||||||
const themes = createMemo(() => theme.ids().map((id) => ({ id, name: theme.name(id) })))
|
|
||||||
|
|
||||||
onMount(() => void theme.loadThemes())
|
|
||||||
|
|
||||||
return {
|
|
||||||
scheme: {
|
|
||||||
current: theme.colorScheme,
|
|
||||||
select: (value: ColorScheme) => theme.setColorScheme(value),
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
options: themes,
|
|
||||||
current: createMemo(() => themes().find((option) => option.id === theme.themeId())),
|
|
||||||
select: (option: { id: string } | null) => option && theme.setTheme(option.id),
|
|
||||||
},
|
|
||||||
fonts: {
|
|
||||||
ui: createMemo(() => ({
|
|
||||||
value: sansInput(settings.appearance.uiFont()),
|
|
||||||
family: sansFontFamily(settings.appearance.uiFont()),
|
|
||||||
placeholder: sansDefault,
|
|
||||||
})),
|
|
||||||
code: createMemo(() => ({
|
|
||||||
value: monoInput(settings.appearance.font()),
|
|
||||||
family: monoFontFamily(settings.appearance.font()),
|
|
||||||
placeholder: monoDefault,
|
|
||||||
})),
|
|
||||||
terminal: createMemo(() => ({
|
|
||||||
value: terminalInput(settings.appearance.terminalFont()),
|
|
||||||
family: terminalFontFamily(settings.appearance.terminalFont()),
|
|
||||||
placeholder: terminalDefault,
|
|
||||||
})),
|
|
||||||
setUI: (value: string) => settings.appearance.setUIFont(value),
|
|
||||||
setCode: (value: string) => settings.appearance.setFont(value),
|
|
||||||
setTerminal: (value: string) => settings.appearance.setTerminalFont(value),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const noneSound = { id: "none", label: "sound.option.none" } as const
|
|
||||||
export const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
|
||||||
export type SoundSelectOption = (typeof soundOptions)[number]
|
|
||||||
|
|
||||||
export function createSoundSettingsController() {
|
|
||||||
const settings = useSettings()
|
|
||||||
const preview = createSoundPreviewController(playSoundById)
|
|
||||||
const channel = (
|
|
||||||
enabled: Accessor<boolean>,
|
|
||||||
current: Accessor<string>,
|
|
||||||
setEnabled: (value: boolean) => void,
|
|
||||||
set: (id: string) => void,
|
|
||||||
) => ({
|
|
||||||
current: createMemo(() =>
|
|
||||||
enabled() ? (soundOptions.find((option) => option.id === current()) ?? noneSound) : noneSound,
|
|
||||||
),
|
|
||||||
highlight: (option: SoundSelectOption | undefined) => {
|
|
||||||
if (!option) return
|
|
||||||
preview.play(option.id === "none" ? undefined : option.id)
|
|
||||||
},
|
|
||||||
select: (option: SoundSelectOption | null) => {
|
|
||||||
if (!option) return
|
|
||||||
if (option.id === "none") {
|
|
||||||
setEnabled(false)
|
|
||||||
preview.stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setEnabled(true)
|
|
||||||
set(option.id)
|
|
||||||
preview.play(option.id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
agent: channel(
|
|
||||||
settings.sounds.agentEnabled,
|
|
||||||
settings.sounds.agent,
|
|
||||||
(value) => settings.sounds.setAgentEnabled(value),
|
|
||||||
(id) => settings.sounds.setAgent(id),
|
|
||||||
),
|
|
||||||
permissions: channel(
|
|
||||||
settings.sounds.permissionsEnabled,
|
|
||||||
settings.sounds.permissions,
|
|
||||||
(value) => settings.sounds.setPermissionsEnabled(value),
|
|
||||||
(id) => settings.sounds.setPermissions(id),
|
|
||||||
),
|
|
||||||
errors: channel(
|
|
||||||
settings.sounds.errorsEnabled,
|
|
||||||
settings.sounds.errors,
|
|
||||||
(value) => settings.sounds.setErrorsEnabled(value),
|
|
||||||
(id) => settings.sounds.setErrors(id),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PermissionScopeController = ReturnType<typeof createPermissionScopeController>
|
|
||||||
export type ShellSettingsController = ReturnType<typeof createShellSettingsController>
|
|
||||||
export type AppearanceSettingsController = ReturnType<typeof createAppearanceSettingsController>
|
|
||||||
export type SoundSettingsController = ReturnType<typeof createSoundSettingsController>
|
|
||||||
|
|
@ -1,297 +1,182 @@
|
||||||
import { Component, Show, createMemo, createResource } from "solid-js"
|
import { Component, Show, createMemo, createResource, onMount } from "solid-js"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||||
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
import { SelectV2 } from "@opencode-ai/ui/v2/select-v2"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||||
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { usePermission } from "@/context/permission"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { useServerSync } from "@/context/server-sync"
|
||||||
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useUpdaterAction } from "../updater-action"
|
import { useUpdaterAction } from "../updater-action"
|
||||||
import { useSettings } from "@/context/settings"
|
import {
|
||||||
import { ExternalLink } from "../external-link"
|
monoDefault,
|
||||||
|
monoFontFamily,
|
||||||
|
monoInput,
|
||||||
|
sansDefault,
|
||||||
|
sansFontFamily,
|
||||||
|
sansInput,
|
||||||
|
terminalDefault,
|
||||||
|
terminalFontFamily,
|
||||||
|
terminalInput,
|
||||||
|
useSettings,
|
||||||
|
} from "@/context/settings"
|
||||||
|
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
|
||||||
|
import { Link } from "../link"
|
||||||
import { SettingsListV2 } from "./parts/list"
|
import { SettingsListV2 } from "./parts/list"
|
||||||
import { SettingsRowV2 } from "./parts/row"
|
import { SettingsRowV2 } from "./parts/row"
|
||||||
import { LayoutRetirementNotice, LayoutTransitionToggle } from "./interface-transition"
|
import { LayoutRetirementNotice, LayoutTransitionToggle } from "./interface-transition"
|
||||||
import {
|
|
||||||
createAppearanceSettingsController,
|
|
||||||
createPermissionScopeController,
|
|
||||||
createShellOptions,
|
|
||||||
createShellSettingsController,
|
|
||||||
createSoundSettingsController,
|
|
||||||
soundOptions,
|
|
||||||
type AppearanceSettingsController,
|
|
||||||
type PermissionScopeController,
|
|
||||||
type ShellSettingsController,
|
|
||||||
type SoundSettingsController,
|
|
||||||
} from "./general-controllers"
|
|
||||||
import "./settings-v2.css"
|
import "./settings-v2.css"
|
||||||
|
|
||||||
const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]
|
let demoSoundState = {
|
||||||
const fontSettings = {
|
cleanup: undefined as (() => void) | undefined,
|
||||||
ui: {
|
timeout: undefined as NodeJS.Timeout | undefined,
|
||||||
action: "settings-ui-font",
|
run: 0,
|
||||||
title: "settings.general.row.uiFont.title",
|
|
||||||
description: "settings.general.row.uiFont.description",
|
|
||||||
font: "ui",
|
|
||||||
input: "setUI",
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
action: "settings-code-font",
|
|
||||||
title: "settings.general.row.font.title",
|
|
||||||
description: "settings.general.row.font.description",
|
|
||||||
font: "code",
|
|
||||||
input: "setCode",
|
|
||||||
},
|
|
||||||
terminal: {
|
|
||||||
action: "settings-terminal-font",
|
|
||||||
title: "settings.general.row.terminalFont.title",
|
|
||||||
description: "settings.general.row.terminalFont.description",
|
|
||||||
font: "terminal",
|
|
||||||
input: "setTerminal",
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
const soundSettings = {
|
|
||||||
agent: {
|
|
||||||
action: "settings-sounds-agent",
|
|
||||||
title: "settings.general.sounds.agent.title",
|
|
||||||
description: "settings.general.sounds.agent.description",
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
action: "settings-sounds-permissions",
|
|
||||||
title: "settings.general.sounds.permissions.title",
|
|
||||||
description: "settings.general.sounds.permissions.description",
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
action: "settings-sounds-errors",
|
|
||||||
title: "settings.general.sounds.errors.title",
|
|
||||||
description: "settings.general.sounds.errors.description",
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
|
|
||||||
const PermissionScopeSetting: Component<{ controller: PermissionScopeController }> = (props) => {
|
|
||||||
const language = useLanguage()
|
|
||||||
return (
|
|
||||||
<SettingsRowV2
|
|
||||||
title={language.t("command.permissions.autoaccept.enable")}
|
|
||||||
description={language.t("toast.permissions.autoaccept.on.description")}
|
|
||||||
>
|
|
||||||
<div data-action="settings-auto-accept-permissions">
|
|
||||||
<Switch
|
|
||||||
checked={props.controller.accepting()}
|
|
||||||
disabled={!props.controller.enabled()}
|
|
||||||
onChange={props.controller.set}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsRowV2>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShellSetting: Component<{ controller: ShellSettingsController }> = (props) => {
|
type ThemeOption = {
|
||||||
const language = useLanguage()
|
id: string
|
||||||
const options = createMemo(() =>
|
name: string
|
||||||
createShellOptions({
|
|
||||||
shells: props.controller.shells(),
|
|
||||||
current: props.controller.current(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<SettingsRowV2
|
|
||||||
title={language.t("settings.general.row.shell.title")}
|
|
||||||
description={language.t("settings.general.row.shell.description")}
|
|
||||||
>
|
|
||||||
<SelectV2
|
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-shell"
|
|
||||||
options={options()}
|
|
||||||
current={options().find((option) => option.value === props.controller.current()) ?? options()[0]}
|
|
||||||
placement="bottom-end"
|
|
||||||
gutter={6}
|
|
||||||
value={(option) => option.id}
|
|
||||||
label={(option) => {
|
|
||||||
if (option.id === "auto") return language.t("settings.general.row.shell.autoDefault")
|
|
||||||
if (!option.terminalOnly) return option.name
|
|
||||||
return `${option.name} (${language.t("settings.general.row.shell.terminalOnly")})`
|
|
||||||
}}
|
|
||||||
onSelect={(option) => option && props.controller.select(option.value)}
|
|
||||||
/>
|
|
||||||
</SettingsRowV2>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppearanceSection: Component<{ controller: AppearanceSettingsController }> = (props) => {
|
type ShellOption = {
|
||||||
const language = useLanguage()
|
path: string
|
||||||
return (
|
name: string
|
||||||
<div class="settings-v2-section">
|
acceptable: boolean
|
||||||
<h3 class="settings-v2-section-title">{language.t("settings.general.section.appearance")}</h3>
|
}
|
||||||
<SettingsListV2>
|
|
||||||
<SettingsRowV2
|
|
||||||
title={language.t("settings.general.row.colorScheme.title")}
|
|
||||||
description={language.t("settings.general.row.colorScheme.description")}
|
|
||||||
>
|
|
||||||
<SelectV2
|
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-color-scheme"
|
|
||||||
options={schemeOptions}
|
|
||||||
current={schemeOptions.find((option) => option === props.controller.scheme.current())}
|
|
||||||
placement="bottom-end"
|
|
||||||
gutter={6}
|
|
||||||
label={(option) => {
|
|
||||||
if (option === "system") return language.t("theme.scheme.system")
|
|
||||||
if (option === "light") return language.t("theme.scheme.light")
|
|
||||||
return language.t("theme.scheme.dark")
|
|
||||||
}}
|
|
||||||
onSelect={(option) => option && props.controller.scheme.select(option)}
|
|
||||||
/>
|
|
||||||
</SettingsRowV2>
|
|
||||||
|
|
||||||
<SettingsRowV2
|
type ShellSelectOption = {
|
||||||
title={language.t("settings.general.row.theme.title")}
|
id: string
|
||||||
description={
|
value: string
|
||||||
<>
|
label: string
|
||||||
{language.t("settings.general.row.theme.description")}{" "}
|
}
|
||||||
<ExternalLink class="settings-v2-link" href="https://opencode.ai/docs/themes/">
|
|
||||||
{language.t("common.learnMore")}
|
// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
|
||||||
</ExternalLink>
|
// delay the playback by 100ms during quick selection changes and pause existing sounds.
|
||||||
</>
|
const stopDemoSound = () => {
|
||||||
|
demoSoundState.run += 1
|
||||||
|
if (demoSoundState.cleanup) {
|
||||||
|
demoSoundState.cleanup()
|
||||||
}
|
}
|
||||||
>
|
clearTimeout(demoSoundState.timeout)
|
||||||
<SelectV2
|
demoSoundState.cleanup = undefined
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-theme"
|
|
||||||
options={props.controller.theme.options()}
|
|
||||||
current={props.controller.theme.current()}
|
|
||||||
placement="bottom-end"
|
|
||||||
gutter={6}
|
|
||||||
value={(option) => option.id}
|
|
||||||
label={(option) => option.name}
|
|
||||||
onSelect={props.controller.theme.select}
|
|
||||||
/>
|
|
||||||
</SettingsRowV2>
|
|
||||||
|
|
||||||
<FontSetting kind="ui" fonts={props.controller.fonts} />
|
|
||||||
<FontSetting kind="code" fonts={props.controller.fonts} />
|
|
||||||
<FontSetting kind="terminal" fonts={props.controller.fonts} />
|
|
||||||
</SettingsListV2>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FontSetting: Component<{
|
const playDemoSound = (id: string | undefined) => {
|
||||||
kind: "ui" | "code" | "terminal"
|
stopDemoSound()
|
||||||
fonts: AppearanceSettingsController["fonts"]
|
if (!id) return
|
||||||
}> = (props) => {
|
|
||||||
const language = useLanguage()
|
|
||||||
const config = () => fontSettings[props.kind]
|
|
||||||
return (
|
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
|
||||||
<div class="w-full sm:w-[220px]">
|
|
||||||
<TextInputV2
|
|
||||||
data-action={config().action}
|
|
||||||
type="text"
|
|
||||||
appearance="base"
|
|
||||||
value={props.fonts[config().font]().value}
|
|
||||||
onInput={(event) => props.fonts[config().input](event.currentTarget.value)}
|
|
||||||
placeholder={props.fonts[config().font]().placeholder}
|
|
||||||
spellcheck={false}
|
|
||||||
autocorrect="off"
|
|
||||||
autocomplete="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
aria-label={language.t(config().title)}
|
|
||||||
style={{ "font-family": props.fonts[config().font]().family }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SettingsRowV2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SoundsSection: Component<{ controller: SoundSettingsController }> = (props) => {
|
const run = ++demoSoundState.run
|
||||||
const language = useLanguage()
|
demoSoundState.timeout = setTimeout(() => {
|
||||||
return (
|
void playSoundById(id).then((cleanup) => {
|
||||||
<div class="settings-v2-section">
|
if (demoSoundState.run !== run) {
|
||||||
<h3 class="settings-v2-section-title">{language.t("settings.general.section.sounds")}</h3>
|
cleanup?.()
|
||||||
<SettingsListV2>
|
return
|
||||||
<SoundSetting kind="agent" channel={props.controller.agent} />
|
}
|
||||||
<SoundSetting kind="permissions" channel={props.controller.permissions} />
|
demoSoundState.cleanup = cleanup
|
||||||
<SoundSetting kind="errors" channel={props.controller.errors} />
|
})
|
||||||
</SettingsListV2>
|
}, 100)
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SoundSetting: Component<{
|
|
||||||
kind: "agent" | "permissions" | "errors"
|
|
||||||
channel: SoundSettingsController["agent"]
|
|
||||||
}> = (props) => {
|
|
||||||
const language = useLanguage()
|
|
||||||
const config = () => soundSettings[props.kind]
|
|
||||||
return (
|
|
||||||
<SettingsRowV2 title={language.t(config().title)} description={language.t(config().description)}>
|
|
||||||
<SelectV2
|
|
||||||
appearance="inline"
|
|
||||||
data-action={config().action}
|
|
||||||
options={soundOptions}
|
|
||||||
current={props.channel.current()}
|
|
||||||
value={(option) => option.id}
|
|
||||||
label={(option) => language.t(option.label)}
|
|
||||||
onHighlight={props.channel.highlight}
|
|
||||||
onSelect={props.channel.select}
|
|
||||||
placement="bottom-end"
|
|
||||||
gutter={6}
|
|
||||||
/>
|
|
||||||
</SettingsRowV2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LanguageSetting = () => {
|
|
||||||
const language = useLanguage()
|
|
||||||
const options = createMemo(() =>
|
|
||||||
language.locales.map((locale) => ({
|
|
||||||
value: locale,
|
|
||||||
label: language.label(locale),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<SettingsRowV2
|
|
||||||
title={language.t("settings.general.row.language.title")}
|
|
||||||
description={language.t("settings.general.row.language.description")}
|
|
||||||
>
|
|
||||||
<SelectV2
|
|
||||||
appearance="inline"
|
|
||||||
data-action="settings-language"
|
|
||||||
options={options()}
|
|
||||||
placement="bottom-end"
|
|
||||||
gutter={6}
|
|
||||||
current={options().find((option) => option.value === language.locale())}
|
|
||||||
value={(option) => option.value}
|
|
||||||
label={(option) => option.label}
|
|
||||||
onSelect={(option) => option && language.setLocale(option.value)}
|
|
||||||
/>
|
|
||||||
</SettingsRowV2>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsGeneralV2: Component<{
|
export const SettingsGeneralV2: Component<{
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
|
const theme = useTheme()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const permission = usePermission()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
const serverSync = useServerSync()
|
||||||
|
const serverSdk = useServerSDK()
|
||||||
const mobile = createMediaQuery("(max-width: 767px)")
|
const mobile = createMediaQuery("(max-width: 767px)")
|
||||||
|
|
||||||
const updater = useUpdaterAction()
|
const updater = useUpdaterAction()
|
||||||
const permissionScope = createPermissionScopeController(() => props.sessionID)
|
|
||||||
const shell = createShellSettingsController()
|
const dir = createMemo(() => {
|
||||||
const appearance = createAppearanceSettingsController()
|
if (!props.sessionID) return undefined
|
||||||
const sounds = createSoundSettingsController()
|
return serverSync().session.lineage.peek(props.sessionID)?.session.directory
|
||||||
|
})
|
||||||
|
const accepting = createMemo(() => {
|
||||||
|
const value = dir()
|
||||||
|
if (!value || !props.sessionID) return false
|
||||||
|
return permission.isAutoAccepting(props.sessionID, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleAccept = (checked: boolean) => {
|
||||||
|
const value = dir()
|
||||||
|
if (!value || !props.sessionID) return
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
permission.enableAutoAccept(props.sessionID, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
permission.disableAutoAccept(props.sessionID, value)
|
||||||
|
}
|
||||||
const desktop = createMemo(() => platform.platform === "desktop")
|
const desktop = createMemo(() => platform.platform === "desktop")
|
||||||
|
|
||||||
|
const themeOptions = createMemo<ThemeOption[]>(() => theme.ids().map((id) => ({ id, name: theme.name(id) })))
|
||||||
|
|
||||||
|
const [shells] = createResource(
|
||||||
|
async () => {
|
||||||
|
const sdk = serverSdk()
|
||||||
|
if ((await sdk.protocol) === "v1") {
|
||||||
|
return (await sdk.client.pty.shells()).data ?? []
|
||||||
|
}
|
||||||
|
// return (await sdk.api.pty.shells()).data
|
||||||
|
return [] as ShellOption[]
|
||||||
|
},
|
||||||
|
{ initialValue: [] as ShellOption[] },
|
||||||
|
)
|
||||||
|
|
||||||
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
|
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
|
||||||
() => desktop() && "getPinchZoomEnabled" in platform,
|
() => (desktop() && platform.getPinchZoomEnabled ? true : false),
|
||||||
() => Promise.resolve(platform.getPinchZoomEnabled?.() ?? false).catch(() => false),
|
() => Promise.resolve(platform.getPinchZoomEnabled?.() ?? false).catch(() => false),
|
||||||
{ initialValue: false },
|
{ initialValue: false },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
void theme.loadThemes()
|
||||||
|
})
|
||||||
|
|
||||||
|
const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") }
|
||||||
|
const currentShell = createMemo(() => serverSync().data.config.shell ?? "")
|
||||||
|
|
||||||
|
const shellOptions = createMemo<ShellSelectOption[]>(() => {
|
||||||
|
const list = shells.latest
|
||||||
|
const current = serverSync().data.config.shell
|
||||||
|
|
||||||
|
const nameCounts = new Map<string, number>()
|
||||||
|
for (const s of list) {
|
||||||
|
nameCounts.set(s.name, (nameCounts.get(s.name) || 0) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
autoOption,
|
||||||
|
...list.map((s) => {
|
||||||
|
const ambiguousName = (nameCounts.get(s.name) || 0) > 1
|
||||||
|
const text = ambiguousName ? s.path : s.name
|
||||||
|
const label = s.acceptable ? text : `${text} (${language.t("settings.general.row.shell.terminalOnly")})`
|
||||||
|
return {
|
||||||
|
id: s.path,
|
||||||
|
// Prefer name over path - "bash" is much cleaner than the explicit full route even when it may change due to PATH.
|
||||||
|
value: ambiguousName ? s.path : s.name,
|
||||||
|
label,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
if (current && !options.some((o) => o.value === current)) {
|
||||||
|
options.push({ id: current, value: current, label: current })
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
const onPinchZoomChange = (checked: boolean) => {
|
const onPinchZoomChange = (checked: boolean) => {
|
||||||
setPinchZoom(checked)
|
setPinchZoom(checked)
|
||||||
const update = platform.setPinchZoomEnabled?.(checked)
|
const update = platform.setPinchZoomEnabled?.(checked)
|
||||||
|
|
@ -299,6 +184,52 @@ export const SettingsGeneralV2: Component<{
|
||||||
void update.catch(() => setPinchZoom(!checked))
|
void update.catch(() => setPinchZoom(!checked))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
|
||||||
|
{ value: "system", label: language.t("theme.scheme.system") },
|
||||||
|
{ value: "light", label: language.t("theme.scheme.light") },
|
||||||
|
{ value: "dark", label: language.t("theme.scheme.dark") },
|
||||||
|
])
|
||||||
|
|
||||||
|
const languageOptions = createMemo(() =>
|
||||||
|
language.locales.map((locale) => ({
|
||||||
|
value: locale,
|
||||||
|
label: language.label(locale),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const noneSound = { id: "none", label: "sound.option.none" } as const
|
||||||
|
const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
||||||
|
const mono = () => monoInput(settings.appearance.font())
|
||||||
|
const sans = () => sansInput(settings.appearance.uiFont())
|
||||||
|
const terminal = () => terminalInput(settings.appearance.terminalFont())
|
||||||
|
|
||||||
|
const soundSelectProps = (
|
||||||
|
enabled: () => boolean,
|
||||||
|
current: () => string,
|
||||||
|
setEnabled: (value: boolean) => void,
|
||||||
|
set: (id: string) => void,
|
||||||
|
) => ({
|
||||||
|
options: soundOptions,
|
||||||
|
current: enabled() ? (soundOptions.find((o) => o.id === current()) ?? noneSound) : noneSound,
|
||||||
|
value: (o: (typeof soundOptions)[number]) => o.id,
|
||||||
|
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
|
||||||
|
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
|
||||||
|
if (!option) return
|
||||||
|
playDemoSound(option.id === "none" ? undefined : option.id)
|
||||||
|
},
|
||||||
|
onSelect: (option: (typeof soundOptions)[number] | null) => {
|
||||||
|
if (!option) return
|
||||||
|
if (option.id === "none") {
|
||||||
|
setEnabled(false)
|
||||||
|
stopDemoSound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setEnabled(true)
|
||||||
|
set(option.id)
|
||||||
|
playDemoSound(option.id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const InterfaceSection = () => (
|
const InterfaceSection = () => (
|
||||||
<LayoutTransitionToggle
|
<LayoutTransitionToggle
|
||||||
title={language.t("settings.general.row.newInterface.title")}
|
title={language.t("settings.general.row.newInterface.title")}
|
||||||
|
|
@ -320,18 +251,59 @@ export const SettingsGeneralV2: Component<{
|
||||||
title={language.t("settings.general.row.newInterfaceNotice.title")}
|
title={language.t("settings.general.row.newInterfaceNotice.title")}
|
||||||
description={language.t("settings.general.row.newInterfaceNotice.description")}
|
description={language.t("settings.general.row.newInterfaceNotice.description")}
|
||||||
dismiss={language.t("settings.general.row.newInterfaceNotice.dismiss")}
|
dismiss={language.t("settings.general.row.newInterfaceNotice.dismiss")}
|
||||||
onDismiss={() => settings.general.dismissNewInterfaceNotice()}
|
onDismiss={settings.general.dismissNewInterfaceNotice}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const GeneralSection = () => (
|
const GeneralSection = () => (
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<SettingsListV2>
|
<SettingsListV2>
|
||||||
<LanguageSetting />
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.language.title")}
|
||||||
|
description={language.t("settings.general.row.language.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-language"
|
||||||
|
options={languageOptions()}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
current={languageOptions().find((o) => o.value === language.locale())}
|
||||||
|
value={(o) => o.value}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => option && language.setLocale(option.value)}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
<PermissionScopeSetting controller={permissionScope} />
|
<SettingsRowV2
|
||||||
|
title={language.t("command.permissions.autoaccept.enable")}
|
||||||
|
description={language.t("toast.permissions.autoaccept.on.description")}
|
||||||
|
>
|
||||||
|
<div data-action="settings-auto-accept-permissions">
|
||||||
|
<Switch checked={accepting()} disabled={!dir()} onChange={toggleAccept} />
|
||||||
|
</div>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
<ShellSetting controller={shell} />
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.shell.title")}
|
||||||
|
description={language.t("settings.general.row.shell.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-shell"
|
||||||
|
options={shellOptions()}
|
||||||
|
current={shellOptions().find((o) => o.value === currentShell()) ?? autoOption}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
value={(o) => o.id}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
if (option.value === currentShell()) return
|
||||||
|
serverSync().updateConfig({ shell: option.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
<SettingsRowV2
|
<SettingsRowV2
|
||||||
title={language.t("settings.general.row.reasoningSummaries.title")}
|
title={language.t("settings.general.row.reasoningSummaries.title")}
|
||||||
|
|
@ -442,6 +414,124 @@ export const SettingsGeneralV2: Component<{
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AppearanceSection = () => (
|
||||||
|
<div class="settings-v2-section">
|
||||||
|
<h3 class="settings-v2-section-title">{language.t("settings.general.section.appearance")}</h3>
|
||||||
|
|
||||||
|
<SettingsListV2>
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.colorScheme.title")}
|
||||||
|
description={language.t("settings.general.row.colorScheme.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-color-scheme"
|
||||||
|
options={colorSchemeOptions()}
|
||||||
|
current={colorSchemeOptions().find((o) => o.value === theme.colorScheme())}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
value={(o) => o.value}
|
||||||
|
label={(o) => o.label}
|
||||||
|
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.theme.title")}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
{language.t("settings.general.row.theme.description")}{" "}
|
||||||
|
<Link class="settings-v2-link" href="https://opencode.ai/docs/themes/">
|
||||||
|
{language.t("common.learnMore")}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-theme"
|
||||||
|
options={themeOptions()}
|
||||||
|
current={themeOptions().find((o) => o.id === theme.themeId())}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
value={(o) => o.id}
|
||||||
|
label={(o) => o.name}
|
||||||
|
onSelect={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
theme.setTheme(option.id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.uiFont.title")}
|
||||||
|
description={language.t("settings.general.row.uiFont.description")}
|
||||||
|
>
|
||||||
|
<div class="w-full sm:w-[220px]">
|
||||||
|
<TextInputV2
|
||||||
|
data-action="settings-ui-font"
|
||||||
|
type="text"
|
||||||
|
appearance="base"
|
||||||
|
value={sans()}
|
||||||
|
onInput={(event) => settings.appearance.setUIFont(event.currentTarget.value)}
|
||||||
|
placeholder={sansDefault}
|
||||||
|
spellcheck={false}
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
aria-label={language.t("settings.general.row.uiFont.title")}
|
||||||
|
style={{ "font-family": sansFontFamily(settings.appearance.uiFont()) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.font.title")}
|
||||||
|
description={language.t("settings.general.row.font.description")}
|
||||||
|
>
|
||||||
|
<div class="w-full sm:w-[220px]">
|
||||||
|
<TextInputV2
|
||||||
|
data-action="settings-code-font"
|
||||||
|
type="text"
|
||||||
|
appearance="base"
|
||||||
|
value={mono()}
|
||||||
|
onInput={(event) => settings.appearance.setFont(event.currentTarget.value)}
|
||||||
|
placeholder={monoDefault}
|
||||||
|
spellcheck={false}
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
aria-label={language.t("settings.general.row.font.title")}
|
||||||
|
style={{ "font-family": monoFontFamily(settings.appearance.font()) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.row.terminalFont.title")}
|
||||||
|
description={language.t("settings.general.row.terminalFont.description")}
|
||||||
|
>
|
||||||
|
<div class="w-full sm:w-[220px]">
|
||||||
|
<TextInputV2
|
||||||
|
data-action="settings-terminal-font"
|
||||||
|
type="text"
|
||||||
|
appearance="base"
|
||||||
|
value={terminal()}
|
||||||
|
onInput={(event) => settings.appearance.setTerminalFont(event.currentTarget.value)}
|
||||||
|
placeholder={terminalDefault}
|
||||||
|
spellcheck={false}
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
aria-label={language.t("settings.general.row.terminalFont.title")}
|
||||||
|
style={{ "font-family": terminalFontFamily(settings.appearance.terminalFont()) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsRowV2>
|
||||||
|
</SettingsListV2>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const NotificationsSection = () => (
|
const NotificationsSection = () => (
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<h3 class="settings-v2-section-title">{language.t("settings.general.section.notifications")}</h3>
|
<h3 class="settings-v2-section-title">{language.t("settings.general.section.notifications")}</h3>
|
||||||
|
|
@ -486,6 +576,68 @@ export const SettingsGeneralV2: Component<{
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const SoundsSection = () => (
|
||||||
|
<div class="settings-v2-section">
|
||||||
|
<h3 class="settings-v2-section-title">{language.t("settings.general.section.sounds")}</h3>
|
||||||
|
|
||||||
|
<SettingsListV2>
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.sounds.agent.title")}
|
||||||
|
description={language.t("settings.general.sounds.agent.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-sounds-agent"
|
||||||
|
{...soundSelectProps(
|
||||||
|
() => settings.sounds.agentEnabled(),
|
||||||
|
() => settings.sounds.agent(),
|
||||||
|
(value) => settings.sounds.setAgentEnabled(value),
|
||||||
|
(id) => settings.sounds.setAgent(id),
|
||||||
|
)}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.sounds.permissions.title")}
|
||||||
|
description={language.t("settings.general.sounds.permissions.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-sounds-permissions"
|
||||||
|
{...soundSelectProps(
|
||||||
|
() => settings.sounds.permissionsEnabled(),
|
||||||
|
() => settings.sounds.permissions(),
|
||||||
|
(value) => settings.sounds.setPermissionsEnabled(value),
|
||||||
|
(id) => settings.sounds.setPermissions(id),
|
||||||
|
)}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
<SettingsRowV2
|
||||||
|
title={language.t("settings.general.sounds.errors.title")}
|
||||||
|
description={language.t("settings.general.sounds.errors.description")}
|
||||||
|
>
|
||||||
|
<SelectV2
|
||||||
|
appearance="inline"
|
||||||
|
data-action="settings-sounds-errors"
|
||||||
|
{...soundSelectProps(
|
||||||
|
() => settings.sounds.errorsEnabled(),
|
||||||
|
() => settings.sounds.errors(),
|
||||||
|
(value) => settings.sounds.setErrorsEnabled(value),
|
||||||
|
(id) => settings.sounds.setErrors(id),
|
||||||
|
)}
|
||||||
|
placement="bottom-end"
|
||||||
|
gutter={6}
|
||||||
|
/>
|
||||||
|
</SettingsRowV2>
|
||||||
|
</SettingsListV2>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const UpdatesSection = () => (
|
const UpdatesSection = () => (
|
||||||
<div class="settings-v2-section">
|
<div class="settings-v2-section">
|
||||||
<h3 class="settings-v2-section-title">{language.t("settings.general.section.updates")}</h3>
|
<h3 class="settings-v2-section-title">{language.t("settings.general.section.updates")}</h3>
|
||||||
|
|
@ -507,7 +659,7 @@ export const SettingsGeneralV2: Component<{
|
||||||
title={language.t("settings.updates.row.check.title")}
|
title={language.t("settings.updates.row.check.title")}
|
||||||
description={language.t("settings.updates.row.check.description")}
|
description={language.t("settings.updates.row.check.description")}
|
||||||
>
|
>
|
||||||
<ButtonV2 size="normal" variant="neutral" disabled={!updater.action().run} onClick={() => updater.run()}>
|
<ButtonV2 size="normal" variant="neutral" disabled={!updater.action().run} onClick={updater.run}>
|
||||||
{language.t(updater.action().label)}
|
{language.t(updater.action().label)}
|
||||||
</ButtonV2>
|
</ButtonV2>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
|
|
@ -552,11 +704,11 @@ export const SettingsGeneralV2: Component<{
|
||||||
|
|
||||||
<GeneralSection />
|
<GeneralSection />
|
||||||
|
|
||||||
<AppearanceSection controller={appearance} />
|
<AppearanceSection />
|
||||||
|
|
||||||
<NotificationsSection />
|
<NotificationsSection />
|
||||||
|
|
||||||
<SoundsSection controller={sounds} />
|
<SoundsSection />
|
||||||
|
|
||||||
<Show when={desktop()}>
|
<Show when={desktop()}>
|
||||||
<UpdatesSection />
|
<UpdatesSection />
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,9 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||||
import { type Component, For, Show } from "solid-js"
|
import { type Component, For, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useModels } from "@/context/models"
|
import { useModels } from "@/context/models"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
|
||||||
import { popularProviders } from "@/hooks/use-providers"
|
import { popularProviders } from "@/hooks/use-providers"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
|
||||||
import { SettingsListV2 } from "./parts/list"
|
import { SettingsListV2 } from "./parts/list"
|
||||||
import { SettingsRowV2 } from "./parts/row"
|
import { SettingsRowV2 } from "./parts/row"
|
||||||
import "./settings-v2.css"
|
import "./settings-v2.css"
|
||||||
|
|
@ -22,11 +19,6 @@ const PROVIDER_ICON_SIZE = 16
|
||||||
export const SettingsModelsV2: Component = () => {
|
export const SettingsModelsV2: Component = () => {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const models = useModels()
|
const models = useModels()
|
||||||
const serverSdk = useServerSDK()
|
|
||||||
const [store, setStore] = persisted(
|
|
||||||
Persist.serverGlobal(serverSdk().scope, "settings-v2.models.providers"),
|
|
||||||
createStore({ collapsed: {} as Record<string, boolean> }),
|
|
||||||
)
|
|
||||||
|
|
||||||
const list = useFilteredList<ModelItem>({
|
const list = useFilteredList<ModelItem>({
|
||||||
items: (_filter) => models.list(),
|
items: (_filter) => models.list(),
|
||||||
|
|
@ -102,56 +94,17 @@ export const SettingsModelsV2: Component = () => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<For each={list.grouped.latest}>
|
<For each={list.grouped.latest}>
|
||||||
{(group) => {
|
{(group) => (
|
||||||
const searching = () => list.filter().length > 0
|
<div class="settings-v2-section" data-component="settings-models-provider">
|
||||||
const expanded = () => searching() || !store.collapsed[group.category]
|
<div class="settings-v2-models-group-header">
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class="settings-v2-section"
|
|
||||||
data-component="settings-models-provider"
|
|
||||||
data-expanded={expanded() ? "" : undefined}
|
|
||||||
>
|
|
||||||
<h3 class="settings-v2-models-group-header">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="settings-v2-models-group-trigger"
|
|
||||||
aria-expanded={expanded()}
|
|
||||||
disabled={searching()}
|
|
||||||
onClick={() => setStore("collapsed", group.category, expanded())}
|
|
||||||
>
|
|
||||||
<span class="settings-v2-models-group-chevron">
|
|
||||||
<Show
|
|
||||||
when={expanded()}
|
|
||||||
fallback={
|
|
||||||
<svg width="5" height="6" viewBox="0 0 5 6" fill="none" aria-hidden="true">
|
|
||||||
<path
|
|
||||||
d="M0.75194 5.31663C0.41861 5.51103 0 5.27063 0 4.88473V0.500754C0 0.114854 0.41861 -0.125577 0.75194 0.0688635L4.5096 2.26084C4.8404 2.45378 4.8404 2.93168 4.5096 3.12462L0.75194 5.31663Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
||||||
<path
|
|
||||||
d="M5.37624 6.75194C5.18184 6.41861 5.42224 6 5.80814 6H10.1921C10.578 6 10.8184 6.41861 10.624 6.75194L8.43203 10.5096C8.23909 10.8404 7.76119 10.8404 7.56825 10.5096L5.37624 6.75194Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Show>
|
|
||||||
</span>
|
|
||||||
<span class="settings-v2-models-group-label">
|
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id={group.category}
|
id={group.category}
|
||||||
width={PROVIDER_ICON_SIZE}
|
width={PROVIDER_ICON_SIZE}
|
||||||
height={PROVIDER_ICON_SIZE}
|
height={PROVIDER_ICON_SIZE}
|
||||||
class="settings-v2-models-provider-icon shrink-0"
|
class="settings-v2-models-provider-icon shrink-0"
|
||||||
/>
|
/>
|
||||||
<span class="settings-v2-section-title">{group.items[0].provider.name}</span>
|
<h3 class="settings-v2-section-title">{group.items[0].provider.name}</h3>
|
||||||
</span>
|
</div>
|
||||||
</button>
|
|
||||||
</h3>
|
|
||||||
<Show when={expanded()}>
|
|
||||||
<SettingsListV2>
|
<SettingsListV2>
|
||||||
<For each={group.items}>
|
<For each={group.items}>
|
||||||
{(item) => {
|
{(item) => {
|
||||||
|
|
@ -174,10 +127,8 @@ export const SettingsModelsV2: Component = () => {
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
</SettingsListV2>
|
</SettingsListV2>
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
}}
|
|
||||||
</For>
|
</For>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -373,64 +373,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-models {
|
.settings-v2-models {
|
||||||
gap: 12px;
|
gap: 24px;
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models .settings-v2-section {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models .settings-v2-section[data-expanded] {
|
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-models-group-header {
|
.settings-v2-models-group-header {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models-group-trigger {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 28px;
|
|
||||||
padding: 4px 8px 4px 4px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--v2-text-text-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models-group-trigger:focus-visible {
|
|
||||||
outline: 2px solid var(--v2-border-border-focus);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
.settings-v2-models-group-trigger:not(:disabled):hover {
|
|
||||||
background-color: var(--v2-background-bg-layer-02);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models-group-trigger:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models-group-chevron {
|
|
||||||
display: flex;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--v2-icon-icon-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models-group-label {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-models .settings-v2-section-title {
|
.settings-v2-models .settings-v2-section-title {
|
||||||
|
|
@ -444,10 +394,6 @@
|
||||||
color: var(--v2-icon-icon-base);
|
color: var(--v2-icon-icon-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-models [data-component="settings-v2-list"] {
|
|
||||||
padding-inline: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-v2-models .settings-v2-section-title + [data-component="settings-v2-list"] {
|
.settings-v2-models .settings-v2-section-title + [data-component="settings-v2-list"] {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -382,11 +382,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
if (URL.canParse(text) && new URL(text).protocol === "file:" && platform.openLocalFile) {
|
platform.openLink(text)
|
||||||
platform.openLocalFile(text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
platform.openExternal(text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
||||||
|
|
@ -9,86 +9,53 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab] {
|
|
||||||
--tab-base: var(--v2-background-bg-deep);
|
|
||||||
--tab-overlay: transparent;
|
|
||||||
background: linear-gradient(var(--tab-overlay), var(--tab-overlay)), var(--tab-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab]:is(:hover, :has(> [data-slot="tab-link"]:focus-visible)):not([data-state="pressed"]):not(
|
|
||||||
[data-dragging="true"]
|
|
||||||
):not([data-editing="true"]) {
|
|
||||||
--tab-base: var(--v2-background-bg-layer-02);
|
|
||||||
--tab-overlay: var(--v2-overlay-simple-overlay-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab]:is([data-state="pressed"], [data-dragging="true"], [data-editing="true"]) {
|
|
||||||
--tab-base: var(--v2-background-bg-layer-02);
|
|
||||||
--tab-overlay: var(--v2-overlay-simple-overlay-pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab]:is(:hover, [data-state="pressed"]) [data-slot="tab-close"] {
|
|
||||||
background: linear-gradient(var(--tab-overlay), var(--tab-overlay)), var(--tab-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab][data-editing="true"] [data-slot="tab-close"] {
|
[data-titlebar-tab][data-editing="true"] [data-slot="tab-close"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-list] {
|
[data-titlebar-tab-list] {
|
||||||
gap: 6px;
|
gap: 13.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-slot] {
|
[data-titlebar-tab-slot] {
|
||||||
--tab-separator: var(--v2-background-bg-layer-03);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-color-scheme="dark"] [data-titlebar-tab-slot] {
|
|
||||||
--tab-separator: var(--v2-background-bg-layer-02);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-titlebar-tab-slot]:not(:first-child):not([data-active="true"])::before {
|
[data-titlebar-tab-slot]:not(:first-child):not([data-active="true"])::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
left: -3.75px;
|
left: -6.75px;
|
||||||
width: 1.5px;
|
width: 1.5px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
background: var(--tab-separator);
|
background: var(--v2-background-bg-layer-02);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-slot][data-active="true"] + [data-titlebar-tab-slot]::before {
|
[data-titlebar-tab-slot][data-active="true"] + [data-titlebar-tab-slot]::before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab-slot]:not([data-active="true"]):has([data-titlebar-tab]:hover)::before,
|
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
||||||
[data-titlebar-tab-slot]:not([data-active="true"]):has([data-titlebar-tab]:hover) + [data-titlebar-tab-slot]::before {
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 28px;
|
||||||
|
width: 16px;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(to right, transparent, var(--tab-bg));
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab][data-title-overflow="true"]:not([data-editing="true"]) [data-slot="tab-link"] {
|
[data-titlebar-tab][data-title-overflow="true"]:not([data-editing="true"]) [data-slot="tab-close"]::before {
|
||||||
--tab-title-fade-offset: 4px;
|
display: block;
|
||||||
-webkit-mask-image: linear-gradient(
|
right: 0;
|
||||||
to right,
|
|
||||||
black 0,
|
|
||||||
black calc(100% - var(--tab-title-fade-offset) - 16px),
|
|
||||||
transparent calc(100% - var(--tab-title-fade-offset)),
|
|
||||||
transparent 100%
|
|
||||||
);
|
|
||||||
mask-image: linear-gradient(
|
|
||||||
to right,
|
|
||||||
black 0,
|
|
||||||
black calc(100% - var(--tab-title-fade-offset) - 16px),
|
|
||||||
transparent calc(100% - var(--tab-title-fade-offset)),
|
|
||||||
transparent 100%
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab][data-title-overflow="true"]:is(:hover, [data-active="true"]):not([data-editing="true"])
|
[data-titlebar-tab][data-title-overflow="true"]:hover:not([data-editing="true"]) [data-slot="tab-close"]::before,
|
||||||
[data-slot="tab-link"] {
|
[data-titlebar-tab][data-title-overflow="true"][data-active="true"]:not([data-editing="true"])
|
||||||
--tab-title-fade-offset: 24px;
|
[data-slot="tab-close"]::before {
|
||||||
|
right: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab][data-title-overflow="true"]:not(:hover):not([data-active="true"]):not([data-editing="true"])
|
[data-titlebar-tab][data-title-overflow="true"]:not(:hover):not([data-active="true"]):not([data-editing="true"])
|
||||||
|
|
|
||||||
|
|
@ -180,11 +180,11 @@ export function TabNavItem(props: {
|
||||||
data-slot="titlebar-tab-item"
|
data-slot="titlebar-tab-item"
|
||||||
data-title-overflow={titleOverflowing()}
|
data-title-overflow={titleOverflowing()}
|
||||||
data-editing={editing()}
|
data-editing={editing()}
|
||||||
class="group relative flex h-7 w-full min-w-0 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] px-1.5 [container-type:inline-size]"
|
class="group relative flex h-7 w-full min-w-0 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||||
classList={{ invisible: props.hidden }}
|
classList={{ invisible: props.hidden }}
|
||||||
data-active={props.active}
|
data-active={props.active}
|
||||||
data-dragging={props.dragging}
|
data-dragging={props.dragging}
|
||||||
data-state={props.active || props.pressed ? "pressed" : undefined}
|
data-pressed={props.pressed}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
@ -224,7 +224,6 @@ export function TabNavItem(props: {
|
||||||
<span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
|
<span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
|
||||||
<Show
|
<Show
|
||||||
when={props.session()}
|
when={props.session()}
|
||||||
keyed
|
|
||||||
fallback={
|
fallback={
|
||||||
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
|
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
|
||||||
}
|
}
|
||||||
|
|
@ -232,8 +231,8 @@ export function TabNavItem(props: {
|
||||||
{(session) => (
|
{(session) => (
|
||||||
<SessionTabAvatar
|
<SessionTabAvatar
|
||||||
project={project()}
|
project={project()}
|
||||||
directory={session.directory}
|
directory={session().directory}
|
||||||
sessionId={session.id}
|
sessionId={session().id}
|
||||||
server={props.server}
|
server={props.server}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -277,7 +276,7 @@ export function TabNavItem(props: {
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div data-slot="tab-close">
|
<div data-slot="tab-close" class="group-hover:bg-[var(--tab-bg)] group-data-[active=true]:bg-[var(--tab-bg)]">
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
size="small"
|
size="small"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
|
|
@ -335,8 +334,8 @@ export function DraftTabItem(props: {
|
||||||
data-slot="titlebar-tab-item"
|
data-slot="titlebar-tab-item"
|
||||||
data-active={props.active}
|
data-active={props.active}
|
||||||
data-dragging={props.dragging}
|
data-dragging={props.dragging}
|
||||||
data-state={props.active || props.pressed ? "pressed" : undefined}
|
data-pressed={props.pressed}
|
||||||
class="group relative flex h-7 w-full min-w-0 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] px-1.5 [container-type:inline-size] whitespace-nowrap"
|
class="group relative flex h-7 w-full min-w-0 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||||
classList={{ invisible: props.hidden }}
|
classList={{ invisible: props.hidden }}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||||
|
|
@ -382,7 +381,7 @@ export function DraftTabItem(props: {
|
||||||
{props.title}
|
{props.title}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div data-slot="tab-close">
|
<div data-slot="tab-close" class="group-hover:bg-[var(--tab-bg)] group-data-[active=true]:bg-[var(--tab-bg)]">
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
size="small"
|
size="small"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
|
|
|
||||||
|
|
@ -395,6 +395,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
||||||
}}
|
}}
|
||||||
onReorder={(keys) => tabsStoreActions.reorder(keys)}
|
onReorder={(keys) => tabsStoreActions.reorder(keys)}
|
||||||
/>
|
/>
|
||||||
|
<Show when={!creating()}>
|
||||||
<TooltipV2
|
<TooltipV2
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
value={
|
value={
|
||||||
|
|
@ -414,6 +415,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
||||||
aria-label={language.t("command.session.new")}
|
aria-label={language.t("command.session.new")}
|
||||||
/>
|
/>
|
||||||
</TooltipV2>
|
</TooltipV2>
|
||||||
|
</Show>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<TitlebarV2Right state={v2RightState()} />
|
<TitlebarV2Right state={v2RightState()} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export function WindowsAppMenu(props: {
|
||||||
runAction(entry.action)
|
runAction(entry.action)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (entry.href) props.platform.openExternal(entry.href)
|
if (entry.href) props.platform.openLink(entry.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
bootstrapDirectory,
|
bootstrapDirectory,
|
||||||
loadAgentsQuery,
|
loadAgentsQuery,
|
||||||
loadCommands,
|
loadCommands,
|
||||||
loadGlobalConfigQuery,
|
|
||||||
loadPathQuery,
|
loadPathQuery,
|
||||||
loadProjectsQuery,
|
loadProjectsQuery,
|
||||||
loadProvidersQuery,
|
loadProvidersQuery,
|
||||||
|
|
@ -77,7 +76,6 @@ function directoryState() {
|
||||||
|
|
||||||
describe("bootstrapDirectory", () => {
|
describe("bootstrapDirectory", () => {
|
||||||
test("uses legacy MCP endpoints while refreshing a v1 directory", async () => {
|
test("uses legacy MCP endpoints while refreshing a v1 directory", async () => {
|
||||||
const legacyConfigReads: string[] = []
|
|
||||||
const mcpReads: string[] = []
|
const mcpReads: string[] = []
|
||||||
const [store, setStore] = directoryState()
|
const [store, setStore] = directoryState()
|
||||||
|
|
||||||
|
|
@ -93,12 +91,7 @@ describe("bootstrapDirectory", () => {
|
||||||
},
|
},
|
||||||
sdk: {
|
sdk: {
|
||||||
app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) },
|
app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) },
|
||||||
config: {
|
config: { get: async () => ({ data: {} }) },
|
||||||
get: async () => {
|
|
||||||
legacyConfigReads.push("directory")
|
|
||||||
return { data: {} }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
session: { status: async () => ({ data: {} }) },
|
session: { status: async () => ({ data: {} }) },
|
||||||
vcs: { get: async () => ({ data: undefined }) },
|
vcs: { get: async () => ({ data: undefined }) },
|
||||||
command: {
|
command: {
|
||||||
|
|
@ -141,88 +134,8 @@ describe("bootstrapDirectory", () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 80))
|
await new Promise((resolve) => setTimeout(resolve, 80))
|
||||||
|
|
||||||
expect(store.status).toBe("complete")
|
expect(store.status).toBe("complete")
|
||||||
expect(legacyConfigReads).toEqual(["directory"])
|
|
||||||
expect(mcpReads.sort()).toEqual(["command", "resource", "status"])
|
expect(mcpReads.sort()).toEqual(["command", "resource", "status"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("skips legacy config while refreshing a v2 directory", async () => {
|
|
||||||
const [store, setStore] = directoryState()
|
|
||||||
|
|
||||||
await bootstrapDirectory({
|
|
||||||
directory: "/project",
|
|
||||||
scope: ServerScope.local,
|
|
||||||
mcp: false,
|
|
||||||
global: {
|
|
||||||
config: {} satisfies Config,
|
|
||||||
path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" },
|
|
||||||
project: [{ id: "project", worktree: "/project" } as Project],
|
|
||||||
provider,
|
|
||||||
},
|
|
||||||
sdk: {
|
|
||||||
config: {
|
|
||||||
get: async () => {
|
|
||||||
throw new Error("legacy directory config should not be called")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown as OpencodeClient,
|
|
||||||
api,
|
|
||||||
store,
|
|
||||||
setStore,
|
|
||||||
vcsCache: { setStore() {} } as unknown as VcsCache,
|
|
||||||
loadSessions() {},
|
|
||||||
translate: (key) => key,
|
|
||||||
queryClient: new QueryClient(),
|
|
||||||
protocol: Promise.resolve("v2"),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(store.status).toBe("partial")
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 80))
|
|
||||||
|
|
||||||
expect(store.status).toBe("complete")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("config queries", () => {
|
|
||||||
test("skips legacy global config for v2 servers", async () => {
|
|
||||||
const sdk = {
|
|
||||||
global: {
|
|
||||||
config: {
|
|
||||||
get: async () => {
|
|
||||||
throw new Error("legacy global config should not be called")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown as OpencodeClient
|
|
||||||
|
|
||||||
const result = await new QueryClient().fetchQuery(
|
|
||||||
loadGlobalConfigQuery(ServerScope.local, sdk, Promise.resolve("v2")),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("loads legacy global config for v1 servers", async () => {
|
|
||||||
const calls: string[] = []
|
|
||||||
const config = { shell: "zsh" } satisfies Config
|
|
||||||
const sdk = {
|
|
||||||
global: {
|
|
||||||
config: {
|
|
||||||
get: async () => {
|
|
||||||
calls.push("global")
|
|
||||||
return { data: config }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown as OpencodeClient
|
|
||||||
|
|
||||||
const result = await new QueryClient().fetchQuery(
|
|
||||||
loadGlobalConfigQuery(ServerScope.local, sdk, Promise.resolve("v1")),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).toEqual(config)
|
|
||||||
expect(calls).toEqual(["global"])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("query keys", () => {
|
describe("query keys", () => {
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,10 @@ function showErrors(input: {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadGlobalConfigQuery = (scope: ServerScope, sdk: OpencodeClient, protocol?: Promise<ServerProtocol>) =>
|
export const loadGlobalConfigQuery = (scope: ServerScope, sdk: OpencodeClient) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [scope, "config"],
|
queryKey: [scope, "config"],
|
||||||
queryFn: async () => {
|
queryFn: () => retry(() => sdk.global.config.get().then((x) => x.data!)),
|
||||||
if ((await protocol) !== "v1") return {}
|
|
||||||
return retry(() => sdk.global.config.get().then((x) => x.data!))
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
type ProjectApi = {
|
type ProjectApi = {
|
||||||
|
|
@ -152,7 +149,7 @@ export async function bootstrapGlobal(input: {
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
}) {
|
}) {
|
||||||
const slow = [
|
const slow = [
|
||||||
() => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.scope, input.serverSDK, input.protocol)),
|
() => input.queryClient.fetchQuery(loadGlobalConfigQuery(input.scope, input.serverSDK)),
|
||||||
() =>
|
() =>
|
||||||
input.queryClient.fetchQuery(
|
input.queryClient.fetchQuery(
|
||||||
loadProvidersQuery(input.scope, null, input.serverAPI, input.serverSDK, input.protocol),
|
loadProvidersQuery(input.scope, null, input.serverAPI, input.serverSDK, input.protocol),
|
||||||
|
|
@ -379,10 +376,7 @@ export async function bootstrapDirectory(input: {
|
||||||
.ensureQueryData(loadAgentsQuery(input.scope, input.directory, input.api.agent, input.sdk, input.protocol))
|
.ensureQueryData(loadAgentsQuery(input.scope, input.directory, input.api.agent, input.sdk, input.protocol))
|
||||||
.then((data) => input.setStore("agent", data)),
|
.then((data) => input.setStore("agent", data)),
|
||||||
() =>
|
() =>
|
||||||
retry(async () => {
|
retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
|
||||||
if ((await input.protocol) !== "v1") return
|
|
||||||
return input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))
|
|
||||||
}),
|
|
||||||
() =>
|
() =>
|
||||||
retry(() =>
|
retry(() =>
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
import { type Accessor, batch, createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
|
import { type Accessor, batch, createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
|
||||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
import { useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import type { ServerSDK } from "./server-sdk"
|
import type { ServerSDK } from "./server-sdk"
|
||||||
import type { ServerSync } from "./server-sync"
|
import type { ServerSync } from "./server-sync"
|
||||||
|
|
@ -118,7 +118,6 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const navigate = useNavigate()
|
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
@ -154,7 +153,6 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||||
platform,
|
platform,
|
||||||
settings,
|
settings,
|
||||||
language,
|
language,
|
||||||
navigate,
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
owner ?? undefined,
|
owner ?? undefined,
|
||||||
|
|
@ -219,7 +217,6 @@ function createServerNotificationState(input: {
|
||||||
platform: ReturnType<typeof usePlatform>
|
platform: ReturnType<typeof usePlatform>
|
||||||
settings: ReturnType<typeof useSettings>
|
settings: ReturnType<typeof useSettings>
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
navigate: (href: string) => void
|
|
||||||
}) {
|
}) {
|
||||||
const serverSDK = () => input.sdk
|
const serverSDK = () => input.sdk
|
||||||
const serverSync = () => input.sync
|
const serverSync = () => input.sync
|
||||||
|
|
@ -356,9 +353,7 @@ function createServerNotificationState(input: {
|
||||||
|
|
||||||
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
||||||
if (settings.notifications.agent()) {
|
if (settings.notifications.agent()) {
|
||||||
void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, () =>
|
void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, href)
|
||||||
input.navigate(href),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +386,7 @@ function createServerNotificationState(input: {
|
||||||
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
||||||
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
||||||
if (settings.notifications.errors()) {
|
if (settings.notifications.errors()) {
|
||||||
void platform.notify(language.t("notification.session.error.title"), description, () => input.navigate(href))
|
void platform.notify(language.t("notification.session.error.title"), description, href)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import type { DesktopMenuAction } from "../desktop-menu"
|
||||||
import { ServerConnection } from "./server"
|
import { ServerConnection } from "./server"
|
||||||
import type { WslServersPlatform } from "../wsl/types"
|
import type { WslServersPlatform } from "../wsl/types"
|
||||||
import type { UpdaterPlatform } from "../updater"
|
import type { UpdaterPlatform } from "../updater"
|
||||||
import type { DraftStore } from "@/utils/draft-store"
|
|
||||||
|
|
||||||
type PickerPaths = string | string[] | null
|
type PickerPaths = string | string[] | null
|
||||||
type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean }
|
type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean }
|
||||||
|
|
@ -32,23 +31,26 @@ type PlatformBase = {
|
||||||
/** App version */
|
/** App version */
|
||||||
version?: string
|
version?: string
|
||||||
|
|
||||||
/** Open a web or mail URL in the default system application */
|
/** Open a URL in the default browser */
|
||||||
openExternal(url: string): void
|
openLink(url: string): void
|
||||||
|
|
||||||
/** Open a local path in a local app (desktop only) */
|
/** Open a local path in a local app (desktop only) */
|
||||||
openPath?(path: string, app?: string): Promise<void>
|
openPath?(path: string, app?: string): Promise<void>
|
||||||
|
|
||||||
/** Open a local file URL in its default app (desktop only) */
|
|
||||||
openLocalFile?(url: string): void
|
|
||||||
|
|
||||||
/** Reveal a local path in the system file manager; false when the path does not exist (desktop only) */
|
/** Reveal a local path in the system file manager; false when the path does not exist (desktop only) */
|
||||||
revealPath?(path: string): Promise<boolean>
|
revealPath?(path: string): Promise<boolean>
|
||||||
|
|
||||||
/** Restart the app */
|
/** Restart the app */
|
||||||
restart(): Promise<void>
|
restart(): Promise<void>
|
||||||
|
|
||||||
/** Send a system notification */
|
/** Navigate back in history */
|
||||||
notify(title: string, description?: string, onClick?: () => void): Promise<void>
|
back(): void
|
||||||
|
|
||||||
|
/** Navigate forward in history */
|
||||||
|
forward(): void
|
||||||
|
|
||||||
|
/** Send a system notification (optional deep link) */
|
||||||
|
notify(title: string, description?: string, href?: string): Promise<void>
|
||||||
|
|
||||||
/** Open a native attachment picker and read selected files sequentially (desktop only) */
|
/** Open a native attachment picker and read selected files sequentially (desktop only) */
|
||||||
openAttachmentPickerDialog?(
|
openAttachmentPickerDialog?(
|
||||||
|
|
@ -65,9 +67,6 @@ type PlatformBase = {
|
||||||
/** Storage mechanism, defaults to localStorage */
|
/** Storage mechanism, defaults to localStorage */
|
||||||
storage?: (name?: string) => SyncStorage | AsyncStorage
|
storage?: (name?: string) => SyncStorage | AsyncStorage
|
||||||
|
|
||||||
/** Prompt drafts, history, and their blobs. */
|
|
||||||
draftStore?: DraftStore
|
|
||||||
|
|
||||||
/** Stable platform window identity for window-scoped persistence */
|
/** Stable platform window identity for window-scoped persistence */
|
||||||
windowID?: string
|
windowID?: string
|
||||||
|
|
||||||
|
|
@ -92,6 +91,9 @@ type PlatformBase = {
|
||||||
/** Set the preferred display backend (desktop only) */
|
/** Set the preferred display backend (desktop only) */
|
||||||
setDisplayBackend?(backend: DisplayBackend): Promise<void>
|
setDisplayBackend?(backend: DisplayBackend): Promise<void>
|
||||||
|
|
||||||
|
/** Parse markdown to HTML using native parser (desktop only, returns unprocessed code blocks) */
|
||||||
|
parseMarkdown?(markdown: string): Promise<string>
|
||||||
|
|
||||||
/** Webview zoom level (desktop only) */
|
/** Webview zoom level (desktop only) */
|
||||||
webviewZoom?: Accessor<number>
|
webviewZoom?: Accessor<number>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { createStore, type SetStoreFunction } from "solid-js/store"
|
||||||
import type { FileSelection } from "@/context/file"
|
import type { FileSelection } from "@/context/file"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import type { ServerScope } from "@/utils/server-scope"
|
import type { ServerScope } from "@/utils/server-scope"
|
||||||
import type { BlobReference } from "@/utils/draft-store"
|
|
||||||
|
|
||||||
interface PartBase {
|
interface PartBase {
|
||||||
content: string
|
content: string
|
||||||
|
|
@ -38,7 +37,7 @@ export interface ImageAttachmentPart {
|
||||||
filename: string
|
filename: string
|
||||||
sourcePath?: string
|
sourcePath?: string
|
||||||
mime: string
|
mime: string
|
||||||
blob: BlobReference
|
dataUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContentPart = TextPart | FileAttachmentPart | AgentPart | ImageAttachmentPart
|
export type ContentPart = TextPart | FileAttachmentPart | AgentPart | ImageAttachmentPart
|
||||||
|
|
@ -169,9 +168,9 @@ function createPromptActions(setStore: SetStoreFunction<PromptStore>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function promptTarget(serverScope: ServerScope, scope: PromptScope) {
|
function promptTarget(serverScope: ServerScope, scope: PromptScope) {
|
||||||
if ("draftID" in scope) return Persist.prompt(Persist.draft(scope.draftID, "prompt"))
|
if ("draftID" in scope) return Persist.draft(scope.draftID, "prompt")
|
||||||
const legacy = `${scope.dir}/prompt${scope.id ? "/" + scope.id : ""}.v2`
|
const legacy = `${scope.dir}/prompt${scope.id ? "/" + scope.id : ""}.v2`
|
||||||
return Persist.prompt(Persist.serverScoped(serverScope, scope.dir, scope.id, "prompt", [legacy]))
|
return Persist.serverScoped(serverScope, scope.dir, scope.id, "prompt", [legacy])
|
||||||
}
|
}
|
||||||
|
|
||||||
function promptStore(initial?: InitialPrompt): PromptStore {
|
function promptStore(initial?: InitialPrompt): PromptStore {
|
||||||
|
|
@ -246,7 +245,7 @@ export function createPromptSession(serverScope: ServerScope, scope: PromptScope
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDraftPromptSession(draftID: string, initial?: InitialPrompt) {
|
export function createDraftPromptSession(draftID: string, initial?: InitialPrompt) {
|
||||||
return createPersistedPrompt(Persist.prompt(Persist.draft(draftID, "prompt")), initial)
|
return createPersistedPrompt(Persist.draft(draftID, "prompt"), initial)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptSession = ReturnType<typeof createPromptSession>
|
export type PromptSession = ReturnType<typeof createPromptSession>
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ function makeQueryOptionsApi(
|
||||||
protocol: Promise<"v1" | "v2">,
|
protocol: Promise<"v1" | "v2">,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
globalConfig: () => loadGlobalConfigQuery(scope, serverSDK(), protocol),
|
globalConfig: () => loadGlobalConfigQuery(scope, serverSDK()),
|
||||||
projects: () => loadProjectsQuery(scope, serverAPI.project),
|
projects: () => loadProjectsQuery(scope, serverAPI.project),
|
||||||
providers: (directory: PathKey | null) =>
|
providers: (directory: PathKey | null) =>
|
||||||
loadProvidersQuery(scope, directory, serverAPI, directory ? sdkFor(directory) : serverSDK(), protocol),
|
loadProvidersQuery(scope, directory, serverAPI, directory ? sdkFor(directory) : serverSDK(), protocol),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import {
|
import {
|
||||||
hasExistingWebState,
|
hasExistingWebState,
|
||||||
initialAgentVisibility,
|
|
||||||
isAppUpgrade,
|
isAppUpgrade,
|
||||||
layoutTransitionState,
|
layoutTransitionState,
|
||||||
maximumSunsetTimeout,
|
maximumSunsetTimeout,
|
||||||
|
|
@ -12,22 +11,6 @@ import {
|
||||||
shouldEnableNewLayout,
|
shouldEnableNewLayout,
|
||||||
} from "./settings"
|
} from "./settings"
|
||||||
|
|
||||||
describe("agent visibility", () => {
|
|
||||||
test("shows the picker for existing profiles and hides it for first-time installs", () => {
|
|
||||||
expect(initialAgentVisibility(undefined, true)).toBe(true)
|
|
||||||
expect(initialAgentVisibility(undefined, false)).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("shows the picker when updating from a recent release", () => {
|
|
||||||
expect(initialAgentVisibility(undefined, false, "1.18.8")).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("preserves the preference after initialization", () => {
|
|
||||||
expect(initialAgentVisibility(true, true, "1.18.8")).toBeUndefined()
|
|
||||||
expect(initialAgentVisibility(true, false)).toBeUndefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("layout transition", () => {
|
describe("layout transition", () => {
|
||||||
test("blank profiles default to the new layout", () => {
|
test("blank profiles default to the new layout", () => {
|
||||||
expect(newLayoutDesignsDefault).toBe(true)
|
expect(newLayoutDesignsDefault).toBe(true)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
import { batch, createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { persisted } from "@/utils/persist"
|
import { persisted } from "@/utils/persist"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
|
@ -36,7 +36,6 @@ export interface Settings {
|
||||||
mobileTitlebarPosition: "top" | "bottom"
|
mobileTitlebarPosition: "top" | "bottom"
|
||||||
newLayoutDesigns?: boolean
|
newLayoutDesigns?: boolean
|
||||||
layoutTransitionEligible?: boolean
|
layoutTransitionEligible?: boolean
|
||||||
agentVisibilityInitialized?: boolean
|
|
||||||
newInterfaceNoticeDismissed?: boolean
|
newInterfaceNoticeDismissed?: boolean
|
||||||
shouldDisplayTabsToast?: boolean
|
shouldDisplayTabsToast?: boolean
|
||||||
}
|
}
|
||||||
|
|
@ -94,11 +93,6 @@ export function hasExistingWebState(settings: Promise<string> | string | null, p
|
||||||
return settings !== null || previousVersion !== undefined
|
return settings !== null || previousVersion !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialAgentVisibility(initialized: boolean | undefined, existing: boolean, previousVersion?: string) {
|
|
||||||
if (initialized === true) return
|
|
||||||
return existing || previousVersion !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldEnableNewLayout(previous: string | undefined, current: string | undefined) {
|
export function shouldEnableNewLayout(previous: string | undefined, current: string | undefined) {
|
||||||
if (!current) return false
|
if (!current) return false
|
||||||
const currentComparison = compareVersions(current, newLayoutDesignsUpgradeCutoff)
|
const currentComparison = compareVersions(current, newLayoutDesignsUpgradeCutoff)
|
||||||
|
|
@ -277,14 +271,6 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference())
|
const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference())
|
||||||
const initializeAgentVisibility = (existing: boolean) => {
|
|
||||||
const initial = initialAgentVisibility(store.general?.agentVisibilityInitialized, existing, launchState.previous)
|
|
||||||
if (initial === undefined) return
|
|
||||||
batch(() => {
|
|
||||||
setStore("general", "showCustomAgents", initial)
|
|
||||||
setStore("general", "agentVisibilityInitialized", true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sunset && !oldInterfaceRetired()) {
|
if (sunset && !oldInterfaceRetired()) {
|
||||||
const timeout = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
const timeout = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||||
|
|
@ -313,9 +299,8 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!ready() || !launchState.classified || platform.platform !== "web") return
|
if (!ready() || !launchState.classified || platform.platform !== "web") return
|
||||||
const existing = hasExistingWebState(settingsInit, launchState.previous)
|
if (layoutTransitionClassified()) return
|
||||||
if (!layoutTransitionClassified()) setStore("general", "layoutTransitionEligible", existing)
|
setStore("general", "layoutTransitionEligible", hasExistingWebState(settingsInit, launchState.previous))
|
||||||
initializeAgentVisibility(existing)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|
@ -441,7 +426,6 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||||
if (typeof current === "boolean") return
|
if (typeof current === "boolean") return
|
||||||
setStore("general", "layoutTransitionEligible", eligible)
|
setStore("general", "layoutTransitionEligible", eligible)
|
||||||
},
|
},
|
||||||
initializeAgentVisibility,
|
|
||||||
layoutTransitionAvailable: createMemo(() => ready() && layoutTransition().available),
|
layoutTransitionAvailable: createMemo(() => ready() && layoutTransition().available),
|
||||||
newInterfaceNoticeVisible: createMemo(() => ready() && layoutTransition().notice),
|
newInterfaceNoticeVisible: createMemo(() => ready() && layoutTransition().notice),
|
||||||
dismissNewInterfaceNotice() {
|
dismissNewInterfaceNotice() {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { ServerConnection } from "./server"
|
|
||||||
import type { Tab } from "./tabs"
|
|
||||||
|
|
||||||
export function migrateTabs(value: unknown, fallback: ServerConnection.Key): Tab[] {
|
|
||||||
if (!Array.isArray(value)) return []
|
|
||||||
return value.flatMap<Tab>((tab) => {
|
|
||||||
if (!tab || typeof tab !== "object") return []
|
|
||||||
if ("server" in tab && typeof tab.server !== "string") return []
|
|
||||||
const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key
|
|
||||||
if (tab.type === "session" && typeof tab.sessionId === "string") {
|
|
||||||
return [{ type: tab.type, server, sessionId: tab.sessionId }]
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
tab.type === "draft" &&
|
|
||||||
typeof tab.draftID === "string" &&
|
|
||||||
typeof tab.directory === "string" &&
|
|
||||||
(tab.worktree === undefined || typeof tab.worktree === "string")
|
|
||||||
) {
|
|
||||||
return [{ type: tab.type, server, draftID: tab.draftID, directory: tab.directory, worktree: tab.worktree }]
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { createRoot, getOwner, onCleanup } from "solid-js"
|
||||||
import { createTabMemory } from "./tab-memory"
|
import { createTabMemory } from "./tab-memory"
|
||||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
||||||
import type { SessionTab, Tab } from "./tabs"
|
import type { SessionTab, Tab } from "./tabs"
|
||||||
import { migrateTabs } from "./tab-migration"
|
|
||||||
import type { ServerConnection } from "./server"
|
import type { ServerConnection } from "./server"
|
||||||
|
|
||||||
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
|
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
|
||||||
|
|
@ -12,23 +11,6 @@ function sessionTab(sessionId: string): SessionTab {
|
||||||
return { type: "session", server, sessionId }
|
return { type: "session", server, sessionId }
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("tab migration", () => {
|
|
||||||
test("drops null and malformed persisted tabs", () => {
|
|
||||||
expect(
|
|
||||||
migrateTabs([null, sessionTab("a"), { type: "session", server }, { type: "unknown", server }, "invalid"], server),
|
|
||||||
).toEqual([sessionTab("a")])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("adds the fallback server to valid legacy tabs", () => {
|
|
||||||
expect(migrateTabs([{ type: "session", sessionId: "a", dirBase64: "legacy" }], server)).toEqual([sessionTab("a")])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("replaces invalid top-level persisted data", () => {
|
|
||||||
expect(migrateTabs(null, server)).toEqual([])
|
|
||||||
expect(migrateTabs({}, server)).toEqual([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("tab memory", () => {
|
describe("tab memory", () => {
|
||||||
test("keeps state until its tab is removed", () => {
|
test("keeps state until its tab is removed", () => {
|
||||||
createRoot((dispose) => {
|
createRoot((dispose) => {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { sessionHref } from "@/utils/session-route"
|
||||||
import { createTabMemory } from "./tab-memory"
|
import { createTabMemory } from "./tab-memory"
|
||||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed-tabs"
|
||||||
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
|
import { createDraftPromptSession, type PromptModel } from "./prompt-state"
|
||||||
import { migrateTabs } from "./tab-migration"
|
|
||||||
|
|
||||||
export type SessionTab = {
|
export type SessionTab = {
|
||||||
type: "session"
|
type: "session"
|
||||||
|
|
@ -60,7 +59,13 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
{
|
{
|
||||||
...Persist.window("tabs"),
|
...Persist.window("tabs"),
|
||||||
migrate: (value: unknown) => migrateTabs(value, fallback),
|
migrate: (value: unknown) => {
|
||||||
|
if (!Array.isArray(value)) return value
|
||||||
|
return value.map((tab) => {
|
||||||
|
if (!tab || typeof tab !== "object" || "server" in tab) return tab
|
||||||
|
return { ...tab, server: fallback }
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
createStore<Tab[]>([]),
|
createStore<Tab[]>([]),
|
||||||
)
|
)
|
||||||
|
|
@ -101,10 +106,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDraftPersisted = (draftID: string) => {
|
const removeDraftPersisted = (draftID: string) => {
|
||||||
for (const key of draftPersistedKeys()) {
|
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
|
||||||
const target = Persist.draft(draftID, key)
|
|
||||||
removePersisted(key === "prompt" ? Persist.prompt(target) : target, platform)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeInfo = (key: string) => {
|
const removeInfo = (key: string) => {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import * as Sentry from "@sentry/solid"
|
||||||
import { render } from "solid-js/web"
|
import { render } from "solid-js/web"
|
||||||
import { AppBaseProviders, AppInterface } from "@/app"
|
import { AppBaseProviders, AppInterface } from "@/app"
|
||||||
import { type Platform, PlatformProvider } from "@/context/platform"
|
import { type Platform, PlatformProvider } from "@/context/platform"
|
||||||
import { createBrowserDraftStore } from "@/utils/draft-store"
|
|
||||||
import { dict as en } from "@/i18n/en"
|
import { dict as en } from "@/i18n/en"
|
||||||
import { dict as zh } from "@/i18n/zh"
|
import { dict as zh } from "@/i18n/zh"
|
||||||
|
import { handleNotificationClick } from "@/utils/notification-click"
|
||||||
import { authFromToken } from "@/utils/server"
|
import { authFromToken } from "@/utils/server"
|
||||||
import pkg from "../package.json"
|
import pkg from "../package.json"
|
||||||
import { ServerConnection } from "./context/server"
|
import { ServerConnection } from "./context/server"
|
||||||
|
|
@ -54,7 +54,7 @@ const setStorage = (key: string, value: string | null) => {
|
||||||
const readDefaultServerUrl = () => getStorage(DEFAULT_SERVER_URL_KEY)
|
const readDefaultServerUrl = () => getStorage(DEFAULT_SERVER_URL_KEY)
|
||||||
const writeDefaultServerUrl = (url: string | null) => setStorage(DEFAULT_SERVER_URL_KEY, url)
|
const writeDefaultServerUrl = (url: string | null) => setStorage(DEFAULT_SERVER_URL_KEY, url)
|
||||||
|
|
||||||
const notify: Platform["notify"] = async (title, description, onClick) => {
|
const notify: Platform["notify"] = async (title, description, href) => {
|
||||||
if (!("Notification" in window)) return
|
if (!("Notification" in window)) return
|
||||||
|
|
||||||
const permission =
|
const permission =
|
||||||
|
|
@ -73,17 +73,21 @@ const notify: Platform["notify"] = async (title, description, onClick) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
window.focus()
|
handleNotificationClick(href)
|
||||||
onClick?.()
|
|
||||||
notification.close()
|
notification.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openExternal: Platform["openExternal"] = (value) => {
|
const openLink: Platform["openLink"] = (url) => {
|
||||||
if (!URL.canParse(value)) return
|
window.open(url, "_blank")
|
||||||
const url = new URL(value)
|
}
|
||||||
if (url.protocol !== "http:" && url.protocol !== "https:" && url.protocol !== "mailto:") return
|
|
||||||
window.open(url.href, "_blank", "noopener,noreferrer")
|
const back: Platform["back"] = () => {
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
const forward: Platform["forward"] = () => {
|
||||||
|
window.history.forward()
|
||||||
}
|
}
|
||||||
|
|
||||||
const restart: Platform["restart"] = async () => {
|
const restart: Platform["restart"] = async () => {
|
||||||
|
|
@ -117,9 +121,10 @@ const clearAuthToken = () => {
|
||||||
|
|
||||||
const platform: Platform = {
|
const platform: Platform = {
|
||||||
platform: "web",
|
platform: "web",
|
||||||
draftStore: createBrowserDraftStore(),
|
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
openExternal,
|
openLink,
|
||||||
|
back,
|
||||||
|
forward,
|
||||||
restart,
|
restart,
|
||||||
notify,
|
notify,
|
||||||
getDefaultServer: async () => {
|
getDefaultServer: async () => {
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@ export const dict = {
|
||||||
"prompt.action.send": "إرسال",
|
"prompt.action.send": "إرسال",
|
||||||
"prompt.action.stop": "توقف",
|
"prompt.action.stop": "توقف",
|
||||||
"prompt.toast.pasteUnsupported.title": "مرفق غير مدعوم",
|
"prompt.toast.pasteUnsupported.title": "مرفق غير مدعوم",
|
||||||
"prompt.toast.attachmentDuplicate.title": "تم تحميل هذا الملف بالفعل",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "يمكن إرفاق الصور أو ملفات PDF أو الملفات النصية فقط هنا.",
|
"prompt.toast.pasteUnsupported.description": "يمكن إرفاق الصور أو ملفات PDF أو الملفات النصية فقط هنا.",
|
||||||
"prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً",
|
"prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً",
|
||||||
"prompt.toast.modelAgentRequired.description": "اختر وكيلاً ونموذجاً قبل إرسال الموجه.",
|
"prompt.toast.modelAgentRequired.description": "اختر وكيلاً ونموذجاً قبل إرسال الموجه.",
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@ export const dict = {
|
||||||
"prompt.action.send": "Enviar",
|
"prompt.action.send": "Enviar",
|
||||||
"prompt.action.stop": "Parar",
|
"prompt.action.stop": "Parar",
|
||||||
"prompt.toast.pasteUnsupported.title": "Anexo não suportado",
|
"prompt.toast.pasteUnsupported.title": "Anexo não suportado",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Este arquivo já foi enviado",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Apenas imagens, PDFs ou arquivos de texto podem ser anexados aqui.",
|
"prompt.toast.pasteUnsupported.description": "Apenas imagens, PDFs ou arquivos de texto podem ser anexados aqui.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
|
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
|
||||||
"prompt.toast.modelAgentRequired.description": "Escolha um agente e modelo antes de enviar um prompt.",
|
"prompt.toast.modelAgentRequired.description": "Escolha um agente e modelo antes de enviar um prompt.",
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Zaustavi",
|
"prompt.action.stop": "Zaustavi",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Nepodržan prilog",
|
"prompt.toast.pasteUnsupported.title": "Nepodržan prilog",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Ova datoteka je već učitana",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu priložiti samo slike, PDF-ovi ili tekstualne datoteke.",
|
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu priložiti samo slike, PDF-ovi ili tekstualne datoteke.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Odaberi agenta i model",
|
"prompt.toast.modelAgentRequired.title": "Odaberi agenta i model",
|
||||||
"prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.",
|
"prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.",
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Stop",
|
"prompt.action.stop": "Stop",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Ikke understøttet vedhæftning",
|
"prompt.toast.pasteUnsupported.title": "Ikke understøttet vedhæftning",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Denne fil er allerede uploadet",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Kun billeder, PDF'er eller tekstfiler kan vedhæftes her.",
|
"prompt.toast.pasteUnsupported.description": "Kun billeder, PDF'er eller tekstfiler kan vedhæftes her.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Vælg en agent og model",
|
"prompt.toast.modelAgentRequired.title": "Vælg en agent og model",
|
||||||
"prompt.toast.modelAgentRequired.description": "Vælg en agent og model før du sender en forespørgsel.",
|
"prompt.toast.modelAgentRequired.description": "Vælg en agent og model før du sender en forespørgsel.",
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,6 @@ export const dict = {
|
||||||
"prompt.action.send": "Senden",
|
"prompt.action.send": "Senden",
|
||||||
"prompt.action.stop": "Stopp",
|
"prompt.action.stop": "Stopp",
|
||||||
"prompt.toast.pasteUnsupported.title": "Nicht unterstützter Anhang",
|
"prompt.toast.pasteUnsupported.title": "Nicht unterstützter Anhang",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Diese Datei wurde bereits hochgeladen",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder, PDFs oder Textdateien angehängt werden.",
|
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder, PDFs oder Textdateien angehängt werden.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
|
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
|
||||||
"prompt.toast.modelAgentRequired.description":
|
"prompt.toast.modelAgentRequired.description":
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,6 @@ export const dict = {
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Unsupported attachment",
|
"prompt.toast.pasteUnsupported.title": "Unsupported attachment",
|
||||||
"prompt.toast.pasteUnsupported.description": "Only images, PDFs, or text files can be attached here.",
|
"prompt.toast.pasteUnsupported.description": "Only images, PDFs, or text files can be attached here.",
|
||||||
"prompt.toast.attachmentDuplicate.title": "This file has already been uploaded",
|
|
||||||
"prompt.toast.modelAgentRequired.title": "Select an agent and model",
|
"prompt.toast.modelAgentRequired.title": "Select an agent and model",
|
||||||
"prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.",
|
"prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.",
|
||||||
"prompt.toast.worktreeCreateFailed.title": "Failed to create worktree",
|
"prompt.toast.worktreeCreateFailed.title": "Failed to create worktree",
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Detener",
|
"prompt.action.stop": "Detener",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Adjunto no compatible",
|
"prompt.toast.pasteUnsupported.title": "Adjunto no compatible",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Este archivo ya se ha subido",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Solo se pueden adjuntar imágenes, PDFs o archivos de texto aquí.",
|
"prompt.toast.pasteUnsupported.description": "Solo se pueden adjuntar imágenes, PDFs o archivos de texto aquí.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo",
|
"prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo",
|
||||||
"prompt.toast.modelAgentRequired.description": "Elige un agente y modelo antes de enviar un prompt.",
|
"prompt.toast.modelAgentRequired.description": "Elige un agente y modelo antes de enviar un prompt.",
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@ export const dict = {
|
||||||
"prompt.action.send": "Envoyer",
|
"prompt.action.send": "Envoyer",
|
||||||
"prompt.action.stop": "Arrêter",
|
"prompt.action.stop": "Arrêter",
|
||||||
"prompt.toast.pasteUnsupported.title": "Pièce jointe non prise en charge",
|
"prompt.toast.pasteUnsupported.title": "Pièce jointe non prise en charge",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Ce fichier a déjà été téléversé",
|
|
||||||
"prompt.toast.pasteUnsupported.description":
|
"prompt.toast.pasteUnsupported.description":
|
||||||
"Seules les images, les PDF ou les fichiers texte peuvent être joints ici.",
|
"Seules les images, les PDF ou les fichiers texte peuvent être joints ici.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle",
|
"prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle",
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,6 @@ export const dict = {
|
||||||
"prompt.action.send": "送信",
|
"prompt.action.send": "送信",
|
||||||
"prompt.action.stop": "停止",
|
"prompt.action.stop": "停止",
|
||||||
"prompt.toast.pasteUnsupported.title": "サポートされていない添付ファイル",
|
"prompt.toast.pasteUnsupported.title": "サポートされていない添付ファイル",
|
||||||
"prompt.toast.attachmentDuplicate.title": "このファイルはすでにアップロードされています",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "画像、PDF、またはテキストファイルのみ添付できます。",
|
"prompt.toast.pasteUnsupported.description": "画像、PDF、またはテキストファイルのみ添付できます。",
|
||||||
"prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択",
|
"prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択",
|
||||||
"prompt.toast.modelAgentRequired.description": "プロンプトを送信する前にエージェントとモデルを選択してください。",
|
"prompt.toast.modelAgentRequired.description": "プロンプトを送信する前にエージェントとモデルを選択してください。",
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,6 @@ export const dict = {
|
||||||
"prompt.action.send": "전송",
|
"prompt.action.send": "전송",
|
||||||
"prompt.action.stop": "중지",
|
"prompt.action.stop": "중지",
|
||||||
"prompt.toast.pasteUnsupported.title": "지원되지 않는 첨부 파일",
|
"prompt.toast.pasteUnsupported.title": "지원되지 않는 첨부 파일",
|
||||||
"prompt.toast.attachmentDuplicate.title": "이 파일은 이미 업로드되었습니다",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "이미지, PDF 또는 텍스트 파일만 첨부할 수 있습니다.",
|
"prompt.toast.pasteUnsupported.description": "이미지, PDF 또는 텍스트 파일만 첨부할 수 있습니다.",
|
||||||
"prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택",
|
"prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택",
|
||||||
"prompt.toast.modelAgentRequired.description": "프롬프트를 보내기 전에 에이전트와 모델을 선택하세요.",
|
"prompt.toast.modelAgentRequired.description": "프롬프트를 보내기 전에 에이전트와 모델을 선택하세요.",
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Stopp",
|
"prompt.action.stop": "Stopp",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Ikke støttet vedlegg",
|
"prompt.toast.pasteUnsupported.title": "Ikke støttet vedlegg",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Denne filen er allerede lastet opp",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Kun bilder, PDF-er eller tekstfiler kan legges ved her.",
|
"prompt.toast.pasteUnsupported.description": "Kun bilder, PDF-er eller tekstfiler kan legges ved her.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Velg en agent og modell",
|
"prompt.toast.modelAgentRequired.title": "Velg en agent og modell",
|
||||||
"prompt.toast.modelAgentRequired.description": "Velg en agent og modell før du sender en forespørsel.",
|
"prompt.toast.modelAgentRequired.description": "Velg en agent og modell før du sender en forespørsel.",
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,6 @@ export const dict = {
|
||||||
"prompt.action.send": "Wyślij",
|
"prompt.action.send": "Wyślij",
|
||||||
"prompt.action.stop": "Zatrzymaj",
|
"prompt.action.stop": "Zatrzymaj",
|
||||||
"prompt.toast.pasteUnsupported.title": "Nieobsługiwany załącznik",
|
"prompt.toast.pasteUnsupported.title": "Nieobsługiwany załącznik",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Ten plik został już przesłany",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Można tutaj załączać tylko obrazy, pliki PDF lub pliki tekstowe.",
|
"prompt.toast.pasteUnsupported.description": "Można tutaj załączać tylko obrazy, pliki PDF lub pliki tekstowe.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
|
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
|
||||||
"prompt.toast.modelAgentRequired.description": "Wybierz agenta i model przed wysłaniem zapytania.",
|
"prompt.toast.modelAgentRequired.description": "Wybierz agenta i model przed wysłaniem zapytania.",
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Остановить",
|
"prompt.action.stop": "Остановить",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Неподдерживаемое вложение",
|
"prompt.toast.pasteUnsupported.title": "Неподдерживаемое вложение",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Этот файл уже загружен",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Здесь можно прикрепить только изображения, PDF или текстовые файлы.",
|
"prompt.toast.pasteUnsupported.description": "Здесь можно прикрепить только изображения, PDF или текстовые файлы.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Выберите агента и модель",
|
"prompt.toast.modelAgentRequired.title": "Выберите агента и модель",
|
||||||
"prompt.toast.modelAgentRequired.description": "Выберите агента и модель перед отправкой запроса.",
|
"prompt.toast.modelAgentRequired.description": "Выберите агента и модель перед отправкой запроса.",
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "หยุด",
|
"prompt.action.stop": "หยุด",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "ไฟล์แนบที่ไม่รองรับ",
|
"prompt.toast.pasteUnsupported.title": "ไฟล์แนบที่ไม่รองรับ",
|
||||||
"prompt.toast.attachmentDuplicate.title": "ไฟล์นี้ถูกอัปโหลดแล้ว",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "แนบได้เฉพาะรูปภาพ, PDF หรือไฟล์ข้อความเท่านั้น",
|
"prompt.toast.pasteUnsupported.description": "แนบได้เฉพาะรูปภาพ, PDF หรือไฟล์ข้อความเท่านั้น",
|
||||||
"prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล",
|
"prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล",
|
||||||
"prompt.toast.modelAgentRequired.description": "เลือกเอเจนต์และโมเดลก่อนส่งพร้อมท์",
|
"prompt.toast.modelAgentRequired.description": "เลือกเอเจนต์และโมเดลก่อนส่งพร้อมท์",
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Durdur",
|
"prompt.action.stop": "Durdur",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Desteklenmeyen ek",
|
"prompt.toast.pasteUnsupported.title": "Desteklenmeyen ek",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Bu dosya zaten yüklendi",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Buraya yalnızca resimler, PDF'ler veya metin dosyaları eklenebilir.",
|
"prompt.toast.pasteUnsupported.description": "Buraya yalnızca resimler, PDF'ler veya metin dosyaları eklenebilir.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin",
|
"prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin",
|
||||||
"prompt.toast.modelAgentRequired.description": "Komut göndermeden önce bir ajan ve model seçin.",
|
"prompt.toast.modelAgentRequired.description": "Komut göndermeden önce bir ajan ve model seçin.",
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "Зупинити",
|
"prompt.action.stop": "Зупинити",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "Непідтримуване вкладення",
|
"prompt.toast.pasteUnsupported.title": "Непідтримуване вкладення",
|
||||||
"prompt.toast.attachmentDuplicate.title": "Цей файл уже завантажено",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "Сюди можна прикріплювати лише зображення, PDF або текстові файли.",
|
"prompt.toast.pasteUnsupported.description": "Сюди можна прикріплювати лише зображення, PDF або текстові файли.",
|
||||||
"prompt.toast.modelAgentRequired.title": "Виберіть агента та модель",
|
"prompt.toast.modelAgentRequired.title": "Виберіть агента та модель",
|
||||||
"prompt.toast.modelAgentRequired.description": "Виберіть агента та модель перед надсиланням запиту.",
|
"prompt.toast.modelAgentRequired.description": "Виберіть агента та модель перед надсиланням запиту.",
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,6 @@ export const dict = {
|
||||||
"prompt.action.send": "发送",
|
"prompt.action.send": "发送",
|
||||||
"prompt.action.stop": "停止",
|
"prompt.action.stop": "停止",
|
||||||
"prompt.toast.pasteUnsupported.title": "不支持的附件",
|
"prompt.toast.pasteUnsupported.title": "不支持的附件",
|
||||||
"prompt.toast.attachmentDuplicate.title": "此文件已上传",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "此处仅能附加图片、PDF 或文本文件。",
|
"prompt.toast.pasteUnsupported.description": "此处仅能附加图片、PDF 或文本文件。",
|
||||||
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
|
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
|
||||||
"prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。",
|
"prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。",
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,6 @@ export const dict = {
|
||||||
"prompt.action.stop": "停止",
|
"prompt.action.stop": "停止",
|
||||||
|
|
||||||
"prompt.toast.pasteUnsupported.title": "不支援的附件",
|
"prompt.toast.pasteUnsupported.title": "不支援的附件",
|
||||||
"prompt.toast.attachmentDuplicate.title": "此檔案已上傳",
|
|
||||||
"prompt.toast.pasteUnsupported.description": "此處僅能附加圖片、PDF 或文字檔案。",
|
"prompt.toast.pasteUnsupported.description": "此處僅能附加圖片、PDF 或文字檔案。",
|
||||||
"prompt.toast.modelAgentRequired.title": "請選擇代理程式和模型",
|
"prompt.toast.modelAgentRequired.title": "請選擇代理程式和模型",
|
||||||
"prompt.toast.modelAgentRequired.description": "傳送提示前請先選擇代理程式和模型。",
|
"prompt.toast.modelAgentRequired.description": "傳送提示前請先選擇代理程式和模型。",
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,4 @@ export {
|
||||||
type WslServersState,
|
type WslServersState,
|
||||||
} from "./wsl/types"
|
} from "./wsl/types"
|
||||||
export { ServerConnection } from "./context/server"
|
export { ServerConnection } from "./context/server"
|
||||||
export { createDraftStore, type DraftStore } from "./utils/draft-store"
|
export { handleNotificationClick } from "./utils/notification-click"
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ export function DirectoryDataProvider(
|
||||||
<DataProvider
|
<DataProvider
|
||||||
data={sync().data}
|
data={sync().data}
|
||||||
directory={directory}
|
directory={directory}
|
||||||
sessionID={params.id}
|
|
||||||
onNavigateToSession={(sessionID: string) => navigate(href(sessionID))}
|
onNavigateToSession={(sessionID: string) => navigate(href(sessionID))}
|
||||||
onSessionHref={href}
|
onSessionHref={href}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center text-text-interactive-base gap-1"
|
class="flex items-center text-text-interactive-base gap-1"
|
||||||
onClick={() => platform.openExternal("https://opencode.ai/desktop-feedback")}
|
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
||||||
>
|
>
|
||||||
<div>{language.t("error.page.report.discord")}</div>
|
<div>{language.t("error.page.report.discord")}</div>
|
||||||
<Icon name="discord" class="text-text-interactive-base" />
|
<Icon name="discord" class="text-text-interactive-base" />
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ export function createHomeProjectsController(home: HomeController) {
|
||||||
},
|
},
|
||||||
utility: {
|
utility: {
|
||||||
settings: openSettings,
|
settings: openSettings,
|
||||||
help: () => platform.openExternal("https://opencode.ai/desktop-feedback"),
|
help: () => platform.openLink("https://opencode.ai/desktop-feedback"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,7 @@ function HomeProjectRow(
|
||||||
class="pr-16 disabled:opacity-60"
|
class="pr-16 disabled:opacity-60"
|
||||||
classList={{
|
classList={{
|
||||||
"bg-v2-background-bg-layer-01 text-v2-text-text-base": sortable.isDragSource(),
|
"bg-v2-background-bg-layer-01 text-v2-text-text-base": sortable.isDragSource(),
|
||||||
|
"[box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)]": sortable.isDragSource(),
|
||||||
}}
|
}}
|
||||||
data-selected={props.selected ? "" : undefined}
|
data-selected={props.selected ? "" : undefined}
|
||||||
aria-current={props.selected ? "page" : undefined}
|
aria-current={props.selected ? "page" : undefined}
|
||||||
|
|
@ -589,9 +590,9 @@ function HomeProjectNavButton(props: JSX.ButtonHTMLAttributes<HTMLButtonElement>
|
||||||
class={`
|
class={`
|
||||||
flex h-7 min-w-0 w-full shrink-0 cursor-default items-center gap-2 rounded-[6px] bg-transparent px-1.5 text-left
|
flex h-7 min-w-0 w-full shrink-0 cursor-default items-center gap-2 rounded-[6px] bg-transparent px-1.5 text-left
|
||||||
text-v2-text-text-muted [font-weight:440] transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out
|
text-v2-text-text-muted [font-weight:440] transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out
|
||||||
hover:bg-v2-background-bg-layer-01 hover:text-v2-text-text-base
|
hover:bg-v2-background-bg-layer-01 hover:text-v2-text-text-base hover:[box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)]
|
||||||
data-[selected]:bg-v2-background-bg-layer-03 data-[selected]:text-v2-text-text-base
|
data-[selected]:bg-v2-background-bg-layer-03 data-[selected]:text-v2-text-text-base
|
||||||
data-[selected]:hover:bg-v2-background-bg-layer-03
|
data-[selected]:[box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)] data-[selected]:hover:bg-v2-background-bg-layer-03
|
||||||
focus-visible:bg-v2-background-bg-layer-01 focus-visible:text-v2-text-text-base focus-visible:outline-none
|
focus-visible:bg-v2-background-bg-layer-01 focus-visible:text-v2-text-text-base focus-visible:outline-none
|
||||||
focus-visible:[box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)]
|
focus-visible:[box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)]
|
||||||
${local.class ?? ""}
|
${local.class ?? ""}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Session } from "@opencode-ai/sdk/v2/client"
|
import type { Session } from "@opencode-ai/sdk/v2/client"
|
||||||
import { type Accessor, createMemo, For, Show, Suspense } from "solid-js"
|
import { type Accessor, createMemo, For, Show } from "solid-js"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||||
|
|
@ -39,6 +39,7 @@ function isBackgroundOpen(event: MouseEvent) {
|
||||||
export type HomeSessionsViewProps = {
|
export type HomeSessionsViewProps = {
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
groups: Accessor<HomeSessionGroup[]>
|
groups: Accessor<HomeSessionGroup[]>
|
||||||
|
loading: Accessor<boolean>
|
||||||
showProjectName: Accessor<boolean>
|
showProjectName: Accessor<boolean>
|
||||||
server: Accessor<ServerConnection.Key>
|
server: Accessor<ServerConnection.Key>
|
||||||
canCreateSession: Accessor<boolean>
|
canCreateSession: Accessor<boolean>
|
||||||
|
|
@ -80,7 +81,6 @@ export function HomeSessionsView(props: HomeSessionsViewProps) {
|
||||||
>
|
>
|
||||||
<div class="sticky top-0 z-30 shrink-0 bg-v2-background-bg-base pb-3 pt-6 lg:pt-12" onWheel={props.onWheel}>
|
<div class="sticky top-0 z-30 shrink-0 bg-v2-background-bg-base pb-3 pt-6 lg:pt-12" onWheel={props.onWheel}>
|
||||||
<HomeSessionSearch {...props} />
|
<HomeSessionSearch {...props} />
|
||||||
<Suspense>
|
|
||||||
<Show when={props.groups().length > 0 && props.canCreateSession()}>
|
<Show when={props.groups().length > 0 && props.canCreateSession()}>
|
||||||
<div class="pointer-events-none absolute right-0 top-[84px] z-20 flex lg:top-[108px]">
|
<div class="pointer-events-none absolute right-0 top-[84px] z-20 flex lg:top-[108px]">
|
||||||
<ButtonV2
|
<ButtonV2
|
||||||
|
|
@ -95,7 +95,6 @@ export function HomeSessionsView(props: HomeSessionsViewProps) {
|
||||||
</ButtonV2>
|
</ButtonV2>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pointer-events-none sticky top-[84px] z-40 h-0 -mr-3 lg:top-[108px]">
|
<div class="pointer-events-none sticky top-[84px] z-40 h-0 -mr-3 lg:top-[108px]">
|
||||||
<div
|
<div
|
||||||
|
|
@ -105,7 +104,8 @@ export function HomeSessionsView(props: HomeSessionsViewProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="-mr-3 min-h-[calc(100cqh-72px)] lg:min-h-[calc(100cqh-96px)]">
|
<div class="-mr-3 min-h-[calc(100cqh-72px)] lg:min-h-[calc(100cqh-96px)]">
|
||||||
<Suspense
|
<Show
|
||||||
|
when={!props.loading()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
<HomeSessionSkeleton label={props.language.t("common.loading")} />
|
<HomeSessionSkeleton label={props.language.t("common.loading")} />
|
||||||
|
|
@ -141,7 +141,7 @@ export function HomeSessionsView(props: HomeSessionsViewProps) {
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Suspense>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export function HomeSessions(props: {
|
||||||
<HomeSessionsView
|
<HomeSessionsView
|
||||||
language={props.sessions.copy.language}
|
language={props.sessions.copy.language}
|
||||||
groups={props.sessions.data.groups}
|
groups={props.sessions.data.groups}
|
||||||
|
loading={props.sessions.data.loading}
|
||||||
showProjectName={props.sessions.session.showProjectName}
|
showProjectName={props.sessions.session.showProjectName}
|
||||||
server={props.sessions.session.server}
|
server={props.sessions.session.server}
|
||||||
canCreateSession={props.sessions.session.canCreate}
|
canCreateSession={props.sessions.session.canCreate}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { DebugBar } from "@/components/debug-bar"
|
import { DebugBar } from "@/components/debug-bar"
|
||||||
import { TabsInfoPopup } from "@/components/help-button"
|
import { TabsInfoPopup } from "@/components/help-button"
|
||||||
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
||||||
|
|
||||||
export default function NewLayout(props: ParentProps) {
|
export default function NewLayout(props: ParentProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
setNavigate(navigate)
|
||||||
const [state, setState] = createStore({ debugTools: true })
|
const [state, setState] = createStore({ debugTools: true })
|
||||||
|
|
||||||
createEffect(() => setV2Toast(true))
|
createEffect(() => setV2Toast(true))
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd"
|
||||||
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
import type { DragEvent } from "@thisbeyond/solid-dnd"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { dismissToast, setV2Toast, showToast, ToastRegion } from "@/utils/toast"
|
import { toaster } from "@opencode-ai/ui/toast"
|
||||||
|
import { setV2Toast, showToast, ToastRegion } from "@/utils/toast"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { normalizeProjectInfo } from "@/context/global-sync/utils"
|
import { normalizeProjectInfo } from "@/context/global-sync/utils"
|
||||||
import { clearWorkspaceTerminals } from "@/context/terminal"
|
import { clearWorkspaceTerminals } from "@/context/terminal"
|
||||||
|
|
@ -44,6 +45,7 @@ import { Binary } from "@opencode-ai/core/util/binary"
|
||||||
import { retry } from "@opencode-ai/core/util/retry"
|
import { retry } from "@opencode-ai/core/util/retry"
|
||||||
import { playSoundById } from "@/utils/sound"
|
import { playSoundById } from "@/utils/sound"
|
||||||
import { createAim } from "@/utils/aim"
|
import { createAim } from "@/utils/aim"
|
||||||
|
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 { SessionRouteKey, SessionStateKey } from "@/utils/server-scope"
|
import { SessionRouteKey, SessionStateKey } from "@/utils/server-scope"
|
||||||
|
|
@ -116,6 +118,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
setNavigate(navigate)
|
||||||
const providers = useProviders(() => undefined)
|
const providers = useProviders(() => undefined)
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
|
|
@ -381,7 +384,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
const dismissSessionAlert = (sessionKey: string) => {
|
const dismissSessionAlert = (sessionKey: string) => {
|
||||||
const toastId = toastBySession.get(sessionKey)
|
const toastId = toastBySession.get(sessionKey)
|
||||||
if (toastId === undefined) return
|
if (toastId === undefined) return
|
||||||
dismissToast(toastId)
|
toaster.dismiss(toastId)
|
||||||
toastBySession.delete(sessionKey)
|
toastBySession.delete(sessionKey)
|
||||||
alertedAtBySession.delete(sessionKey)
|
alertedAtBySession.delete(sessionKey)
|
||||||
}
|
}
|
||||||
|
|
@ -446,13 +449,13 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
void playSoundById(settings.sounds.permissions())
|
void playSoundById(settings.sounds.permissions())
|
||||||
}
|
}
|
||||||
if (settings.notifications.permissions()) {
|
if (settings.notifications.permissions()) {
|
||||||
void platform.notify(title, description, () => navigate(href))
|
void platform.notify(title, description, href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.details.type === "question.asked") {
|
if (e.details.type === "question.asked") {
|
||||||
if (settings.notifications.agent()) {
|
if (settings.notifications.agent()) {
|
||||||
void platform.notify(title, description, () => navigate(href))
|
void platform.notify(title, description, href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1456,7 +1459,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
title: language.t("workspace.resetting.title"),
|
title: language.t("workspace.resetting.title"),
|
||||||
description: language.t("workspace.resetting.description"),
|
description: language.t("workspace.resetting.description"),
|
||||||
})
|
})
|
||||||
const dismiss = () => dismissToast(progress)
|
const dismiss = () => toaster.dismiss(progress)
|
||||||
|
|
||||||
const sessions = await listAllSessions(serverSDK().api.session, { directory, order: "desc" }).catch(() => [])
|
const sessions = await listAllSessions(serverSDK().api.session, { directory, order: "desc" }).catch(() => [])
|
||||||
|
|
||||||
|
|
@ -2249,7 +2252,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
settingsKeybind={() => command.keybind("settings.open")}
|
settingsKeybind={() => command.keybind("settings.open")}
|
||||||
onOpenSettings={openSettings}
|
onOpenSettings={openSettings}
|
||||||
helpLabel={() => language.t("sidebar.help")}
|
helpLabel={() => language.t("sidebar.help")}
|
||||||
onOpenHelp={() => platform.openExternal("https://opencode.ai/desktop-feedback")}
|
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
||||||
renderPanel={() =>
|
renderPanel={() =>
|
||||||
mobile ? <SidebarPanel project={currentProject} mobile /> : <SidebarPanel project={currentProject} merged />
|
mobile ? <SidebarPanel project={currentProject} mobile /> : <SidebarPanel project={currentProject} merged />
|
||||||
}
|
}
|
||||||
|
|
@ -2447,7 +2450,7 @@ function UpdateAvailableToast(props: {
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (toastId === undefined) return
|
if (toastId === undefined) return
|
||||||
dismissToast(toastId)
|
toaster.dismiss(toastId)
|
||||||
})
|
})
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,9 @@ export function NewSessionStatus(props: { mount: Accessor<HTMLElement | null>; v
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={props.mount()} keyed>
|
<Show when={props.mount()}>
|
||||||
{(mount) => (
|
{(mount) => (
|
||||||
<Portal mount={mount}>
|
<Portal mount={mount()}>
|
||||||
<Show when={props.visible()}>
|
<Show when={props.visible()}>
|
||||||
<Tooltip placement="bottom" value={language.t("status.popover.trigger")}>
|
<Tooltip placement="bottom" value={language.t("status.popover.trigger")}>
|
||||||
<StatusPopoverV2 />
|
<StatusPopoverV2 />
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ import { SessionFileBrowserTab, type SessionFileBrowserState } from "@/pages/ses
|
||||||
|
|
||||||
type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
||||||
type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff
|
type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff
|
||||||
const FILE_TREE_WIDTH_MIN = 240
|
|
||||||
|
|
||||||
function renderDiff(value: ReviewDiff): value is RenderDiff {
|
function renderDiff(value: ReviewDiff): value is RenderDiff {
|
||||||
return typeof value.file === "string"
|
return typeof value.file === "string"
|
||||||
|
|
@ -105,14 +104,13 @@ export function SessionSidePanel(props: {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const open = createMemo(() => reviewOpen() || fileOpen())
|
const open = createMemo(() => reviewOpen() || fileOpen())
|
||||||
const fileTreeWidth = createMemo(() => Math.max(FILE_TREE_WIDTH_MIN, layout.fileTree.width()))
|
|
||||||
const reviewTab = createMemo(() => isDesktop())
|
const reviewTab = createMemo(() => isDesktop())
|
||||||
const panelWidth = createMemo(() => {
|
const panelWidth = createMemo(() => {
|
||||||
if (!open()) return "0px"
|
if (!open()) return "0px"
|
||||||
if (reviewOpen()) return "auto"
|
if (reviewOpen()) return "auto"
|
||||||
return `${fileTreeWidth()}px`
|
return `${layout.fileTree.width()}px`
|
||||||
})
|
})
|
||||||
const treeWidth = createMemo(() => (fileOpen() ? `${fileTreeWidth()}px` : "0px"))
|
const treeWidth = createMemo(() => (fileOpen() ? `${layout.fileTree.width()}px` : "0px"))
|
||||||
|
|
||||||
const diffs = createMemo(() => props.diffs().filter(renderDiff))
|
const diffs = createMemo(() => props.diffs().filter(renderDiff))
|
||||||
const diffFiles = createMemo(() => diffs().map((d) => d.file))
|
const diffFiles = createMemo(() => diffs().map((d) => d.file))
|
||||||
|
|
@ -847,8 +845,8 @@ export function SessionSidePanel(props: {
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
edge="start"
|
edge="start"
|
||||||
size={fileTreeWidth()}
|
size={layout.fileTree.width()}
|
||||||
min={FILE_TREE_WIDTH_MIN}
|
min={200}
|
||||||
max={480}
|
max={480}
|
||||||
onResize={(width) => {
|
onResize={(width) => {
|
||||||
props.size.touch()
|
props.size.touch()
|
||||||
|
|
|
||||||
|
|
@ -646,7 +646,7 @@ export function MessageTimeline(props: {
|
||||||
const viewShare = () => {
|
const viewShare = () => {
|
||||||
const url = shareUrl()
|
const url = shareUrl()
|
||||||
if (!url) return
|
if (!url) return
|
||||||
platform.openExternal(url)
|
platform.openLink(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = (err: unknown) => {
|
const errorMessage = (err: unknown) => {
|
||||||
|
|
|
||||||
|
|
@ -167,41 +167,4 @@ describe("current session timeline rows", () => {
|
||||||
"thinking:msg_2",
|
"thinking:msg_2",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("removes a failed assistant error when the turn continues streaming", () => {
|
|
||||||
const source = [
|
|
||||||
{ id: "msg_user", type: "user", text: "recover", time: { created: 1 } },
|
|
||||||
{
|
|
||||||
id: "msg_failed",
|
|
||||||
type: "assistant",
|
|
||||||
agent: "build",
|
|
||||||
model: { id: "model", providerID: "provider" },
|
|
||||||
content: [],
|
|
||||||
error: { type: "ProviderError", message: "temporary failure" },
|
|
||||||
time: { created: 2, completed: 3 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "msg_recovery",
|
|
||||||
type: "assistant",
|
|
||||||
agent: "build",
|
|
||||||
model: { id: "model", providerID: "provider" },
|
|
||||||
content: [{ type: "text", text: "streaming again" }],
|
|
||||||
time: { created: 4 },
|
|
||||||
},
|
|
||||||
] satisfies SessionMessageInfo[]
|
|
||||||
const normalized = normalizeSessionMessages("ses_1", source)
|
|
||||||
const messages = new Map(normalized.messages.map((message) => [message.id, message]))
|
|
||||||
|
|
||||||
const result = Timeline.constructSessionMessageRows(
|
|
||||||
source,
|
|
||||||
(messageID) => messages.get(messageID),
|
|
||||||
(messageID) => normalized.parts.get(messageID) ?? [],
|
|
||||||
true,
|
|
||||||
"busy",
|
|
||||||
true,
|
|
||||||
normalized.messages.filter((message) => message.role === "user"),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.rows.map((row) => row._tag)).toEqual(["UserMessage", "AssistantPart"])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,7 @@ export namespace Timeline {
|
||||||
const compaction = userParts.some((p) => p.type === "compaction")
|
const compaction = userParts.some((p) => p.type === "compaction")
|
||||||
const interruptedMessageIndex = assistantMessages.findIndex((m) => m.error?.name === "MessageAbortedError")
|
const interruptedMessageIndex = assistantMessages.findIndex((m) => m.error?.name === "MessageAbortedError")
|
||||||
const interrupted = interruptedMessageIndex !== -1
|
const interrupted = interruptedMessageIndex !== -1
|
||||||
const latestError = assistantMessages.at(-1)?.error
|
const error = assistantMessages.find((m) => m.error && m.error.name !== "MessageAbortedError")?.error
|
||||||
const error = latestError?.name === "MessageAbortedError" ? undefined : latestError
|
|
||||||
|
|
||||||
const assistantPartRefs = assistantMessages.flatMap((message, messageIndex) =>
|
const assistantPartRefs = assistantMessages.flatMap((message, messageIndex) =>
|
||||||
getMessageParts(message.id)
|
getMessageParts(message.id)
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
|
||||||
|
|
||||||
export type BlobReference = { id: string; url: string }
|
|
||||||
|
|
||||||
type Driver = {
|
|
||||||
get(key: string): Promise<string | null>
|
|
||||||
set(key: string, value: string): Promise<void>
|
|
||||||
remove(key: string): Promise<void>
|
|
||||||
putBlob(blob: Blob): Promise<string>
|
|
||||||
getBlob(id: string): Promise<Blob | null>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DraftStore = AsyncStorage & { putBlob(blob: Blob): Promise<BlobReference> }
|
|
||||||
const urls = new Map<string, string>()
|
|
||||||
|
|
||||||
function blobUrl(id: string, blob: Blob) {
|
|
||||||
const existing = urls.get(id)
|
|
||||||
if (existing) return existing
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
urls.set(id, url)
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
async function blobID(blob: Blob) {
|
|
||||||
const id = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", await blob.arrayBuffer())))
|
|
||||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
||||||
.join("")
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createBlobReference(blob: Blob): Promise<BlobReference> {
|
|
||||||
const id = await blobID(blob)
|
|
||||||
return { id, url: blobUrl(id, blob) }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDraftStore(driver: Driver): DraftStore {
|
|
||||||
const versions = new Map<string, number>()
|
|
||||||
const putBlob = async (blob: Blob) => {
|
|
||||||
const id = await driver.putBlob(blob)
|
|
||||||
return { id, url: blobUrl(id, blob) }
|
|
||||||
}
|
|
||||||
const encode = async (value: unknown): Promise<unknown> => {
|
|
||||||
if (Array.isArray(value)) return Promise.all(value.map(encode))
|
|
||||||
if (!value || typeof value !== "object") return value
|
|
||||||
const item = value as Record<string, unknown>
|
|
||||||
if (item.type === "image" && typeof item.dataUrl === "string") {
|
|
||||||
const blob = await fetch(item.dataUrl).then((response) => response.blob())
|
|
||||||
const { dataUrl: _, ...rest } = item
|
|
||||||
return { ...rest, blob: { id: await driver.putBlob(blob) } }
|
|
||||||
}
|
|
||||||
if ("blob" in item && item.blob && typeof item.blob === "object") {
|
|
||||||
const blob = item.blob as Record<string, unknown>
|
|
||||||
if (typeof blob.id === "string" && blob.id.startsWith("data:")) {
|
|
||||||
const data = await fetch(blob.id).then((response) => response.blob())
|
|
||||||
return { ...item, blob: { id: await driver.putBlob(data) } }
|
|
||||||
}
|
|
||||||
return { ...item, blob: { id: blob.id } }
|
|
||||||
}
|
|
||||||
return Object.fromEntries(
|
|
||||||
await Promise.all(Object.entries(item).map(async ([key, entry]) => [key, await encode(entry)])),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const decode = async (value: unknown): Promise<unknown> => {
|
|
||||||
if (Array.isArray(value)) return Promise.all(value.map(decode))
|
|
||||||
if (!value || typeof value !== "object") return value
|
|
||||||
const item = value as Record<string, unknown>
|
|
||||||
if (item.blob && typeof item.blob === "object") {
|
|
||||||
const ref = item.blob as Record<string, unknown>
|
|
||||||
if (typeof ref.id === "string") {
|
|
||||||
const blob = await driver.getBlob(ref.id)
|
|
||||||
if (blob) return { ...item, blob: { id: ref.id, url: blobUrl(ref.id, blob) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Object.fromEntries(
|
|
||||||
await Promise.all(Object.entries(item).map(async ([key, entry]) => [key, await decode(entry)])),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
getItem: async (key) => {
|
|
||||||
const value = await driver.get(key)
|
|
||||||
return value === null ? null : JSON.stringify(await decode(JSON.parse(value)))
|
|
||||||
},
|
|
||||||
setItem: async (key, value) => {
|
|
||||||
const version = (versions.get(key) ?? 0) + 1
|
|
||||||
versions.set(key, version)
|
|
||||||
const encoded = JSON.stringify(await encode(JSON.parse(value)))
|
|
||||||
if (versions.get(key) === version) await driver.set(key, encoded)
|
|
||||||
},
|
|
||||||
removeItem: async (key) => {
|
|
||||||
versions.set(key, (versions.get(key) ?? 0) + 1)
|
|
||||||
await driver.remove(key)
|
|
||||||
},
|
|
||||||
putBlob,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createBrowserDraftStore(): DraftStore {
|
|
||||||
const request = indexedDB.open("opencode-drafts", 1)
|
|
||||||
request.addEventListener("upgradeneeded", () => {
|
|
||||||
request.result.createObjectStore("documents")
|
|
||||||
request.result.createObjectStore("blobs")
|
|
||||||
})
|
|
||||||
const db = new Promise<IDBDatabase>((resolve, reject) => {
|
|
||||||
request.addEventListener("success", () => {
|
|
||||||
const database = request.result
|
|
||||||
const transaction = database.transaction(["documents", "blobs"], "readwrite")
|
|
||||||
const documents = transaction.objectStore("documents").getAll()
|
|
||||||
documents.addEventListener("success", () => {
|
|
||||||
const used = new Set<string>()
|
|
||||||
JSON.parse(`[${documents.result.join(",")}]`, (_key, item) => {
|
|
||||||
if (item?.blob && typeof item.blob.id === "string") used.add(item.blob.id)
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
const blobs = transaction.objectStore("blobs").openKeyCursor()
|
|
||||||
blobs.addEventListener("success", () => {
|
|
||||||
const cursor = blobs.result
|
|
||||||
if (!cursor) return
|
|
||||||
if (!used.has(String(cursor.key))) cursor.delete()
|
|
||||||
cursor.continue()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
transaction.addEventListener("complete", () => resolve(database))
|
|
||||||
transaction.addEventListener("abort", () => resolve(database))
|
|
||||||
})
|
|
||||||
request.addEventListener("error", () => reject(request.error))
|
|
||||||
})
|
|
||||||
const get = async (store: string, key: string) => {
|
|
||||||
const result = (await db).transaction(store).objectStore(store).get(key)
|
|
||||||
return new Promise<unknown>((resolve, reject) => {
|
|
||||||
result.addEventListener("success", () => resolve(result.result))
|
|
||||||
result.addEventListener("error", () => reject(result.error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const write = async (store: string, key: string, value?: unknown) => {
|
|
||||||
const transaction = (await db).transaction(store, "readwrite")
|
|
||||||
if (value === undefined) transaction.objectStore(store).delete(key)
|
|
||||||
else transaction.objectStore(store).put(value, key)
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
transaction.addEventListener("complete", () => resolve())
|
|
||||||
transaction.addEventListener("error", () => reject(transaction.error))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return createDraftStore({
|
|
||||||
get: async (key) => ((await get("documents", key)) as string | undefined) ?? null,
|
|
||||||
set: (key, value) => write("documents", key, value),
|
|
||||||
remove: (key) => write("documents", key),
|
|
||||||
putBlob: async (blob) => {
|
|
||||||
const id = await blobID(blob)
|
|
||||||
await write("blobs", id, blob)
|
|
||||||
return id
|
|
||||||
},
|
|
||||||
getBlob: async (id) => ((await get("blobs", id)) as Blob | undefined) ?? null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function blobDataUrl(blob: BlobReference, mime: string) {
|
|
||||||
const data = await fetch(blob.url).then((response) => response.blob())
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.addEventListener("error", () => reject(reader.error))
|
|
||||||
reader.addEventListener("load", () => {
|
|
||||||
const value = typeof reader.result === "string" ? reader.result : ""
|
|
||||||
resolve(`data:${mime};base64,${value.slice(value.indexOf(",") + 1)}`)
|
|
||||||
})
|
|
||||||
reader.readAsDataURL(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLegacyBlobReference(dataUrl: string): BlobReference {
|
|
||||||
return { id: dataUrl, url: dataUrl }
|
|
||||||
}
|
|
||||||
27
packages/app/src/utils/notification-click.test.ts
Normal file
27
packages/app/src/utils/notification-click.test.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
|
import { handleNotificationClick, setNavigate } from "./notification-click"
|
||||||
|
|
||||||
|
describe("notification click", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
setNavigate(undefined as any)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("navigates via registered navigate function", () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
setNavigate((href) => calls.push(href))
|
||||||
|
handleNotificationClick("/abc/session/123")
|
||||||
|
expect(calls).toEqual(["/abc/session/123"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not navigate when href is missing", () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
setNavigate((href) => calls.push(href))
|
||||||
|
handleNotificationClick(undefined)
|
||||||
|
expect(calls).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to location.assign without registered navigate", () => {
|
||||||
|
handleNotificationClick("/abc/session/123")
|
||||||
|
// falls back to window.location.assign — no error thrown
|
||||||
|
})
|
||||||
|
})
|
||||||
13
packages/app/src/utils/notification-click.ts
Normal file
13
packages/app/src/utils/notification-click.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
let nav: ((href: string) => void) | undefined
|
||||||
|
|
||||||
|
export const setNavigate = (fn: (href: string) => void) => {
|
||||||
|
nav = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleNotificationClick = (href?: string) => {
|
||||||
|
window.focus()
|
||||||
|
if (!href) return
|
||||||
|
if (nav) return nav(href)
|
||||||
|
console.warn("notification-click: navigate function not set, falling back to window.location.assign")
|
||||||
|
window.location.assign(href)
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,6 @@ type PersistedWithReady<T> = [
|
||||||
]
|
]
|
||||||
|
|
||||||
type PersistTarget = {
|
type PersistTarget = {
|
||||||
draft?: boolean
|
|
||||||
storage?: string
|
storage?: string
|
||||||
scope?: "window"
|
scope?: "window"
|
||||||
legacyStorageNames?: string[]
|
legacyStorageNames?: string[]
|
||||||
|
|
@ -296,14 +295,6 @@ async function removeAsync(storage: AsyncStorage, key: string) {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAsyncStorage(storage: SyncStorage | AsyncStorage): AsyncStorage {
|
|
||||||
return {
|
|
||||||
getItem: async (key) => storage.getItem(key),
|
|
||||||
setItem: async (key, value) => storage.setItem(key, value),
|
|
||||||
removeItem: async (key) => storage.removeItem(key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function migrateLegacyAsync(input: {
|
async function migrateLegacyAsync(input: {
|
||||||
current: AsyncStorage
|
current: AsyncStorage
|
||||||
legacyStore?: AsyncStorage
|
legacyStore?: AsyncStorage
|
||||||
|
|
@ -522,9 +513,6 @@ export const Persist = {
|
||||||
if (session) return Persist.serverSession(scope, dir, session, key, legacy)
|
if (session) return Persist.serverSession(scope, dir, session, key, legacy)
|
||||||
return Persist.serverWorkspace(scope, dir, key, legacy)
|
return Persist.serverWorkspace(scope, dir, key, legacy)
|
||||||
},
|
},
|
||||||
prompt(target: PersistTarget): PersistTarget {
|
|
||||||
return { ...target, draft: true }
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTarget(target: PersistTarget, platform: Platform): PersistTarget {
|
function resolveTarget(target: PersistTarget, platform: Platform): PersistTarget {
|
||||||
|
|
@ -538,12 +526,9 @@ function resolveTarget(target: PersistTarget, platform: Platform): PersistTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removePersisted(
|
export function removePersisted(
|
||||||
target: { draft?: boolean; storage?: string; legacyStorageNames?: string[]; key: string },
|
target: { storage?: string; legacyStorageNames?: string[]; key: string },
|
||||||
platform?: Platform,
|
platform?: Platform,
|
||||||
) {
|
) {
|
||||||
if (target.draft && platform?.draftStore) {
|
|
||||||
void platform.draftStore.removeItem(`${target.storage ?? "default"}:${target.key}`)
|
|
||||||
}
|
|
||||||
const isDesktop = platform?.platform === "desktop" && !!platform.storage
|
const isDesktop = platform?.platform === "desktop" && !!platform.storage
|
||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
|
|
@ -576,17 +561,8 @@ export function persisted<T>(
|
||||||
const legacy = config.legacy ?? []
|
const legacy = config.legacy ?? []
|
||||||
|
|
||||||
const isDesktop = platform.platform === "desktop" && !!platform.storage
|
const isDesktop = platform.platform === "desktop" && !!platform.storage
|
||||||
const draft = config.draft ? platform.draftStore : undefined
|
|
||||||
|
|
||||||
const currentStorage = (() => {
|
const currentStorage = (() => {
|
||||||
if (draft) {
|
|
||||||
const prefix = `${config.storage ?? "default"}:`
|
|
||||||
return {
|
|
||||||
getItem: (key: string) => draft.getItem(prefix + key),
|
|
||||||
setItem: (key: string, value: string) => draft.setItem(prefix + key, value),
|
|
||||||
removeItem: (key: string) => draft.removeItem(prefix + key),
|
|
||||||
} satisfies AsyncStorage
|
|
||||||
}
|
|
||||||
if (isDesktop) return platform.storage?.(config.storage)
|
if (isDesktop) return platform.storage?.(config.storage)
|
||||||
if (!config.storage) return localStorageDirect()
|
if (!config.storage) return localStorageDirect()
|
||||||
return localStorageWithPrefix(config.storage)
|
return localStorageWithPrefix(config.storage)
|
||||||
|
|
@ -601,7 +577,7 @@ export function persisted<T>(
|
||||||
const legacyStorageNames = config.legacyStorageNames ?? []
|
const legacyStorageNames = config.legacyStorageNames ?? []
|
||||||
|
|
||||||
const storage = (() => {
|
const storage = (() => {
|
||||||
if (!isDesktop && !draft) {
|
if (!isDesktop) {
|
||||||
const current = currentStorage as SyncStorage
|
const current = currentStorage as SyncStorage
|
||||||
const legacyStore = legacyStorage as SyncStorage
|
const legacyStore = legacyStorage as SyncStorage
|
||||||
const legacyStores = legacyStorageNames.map(localStorageWithPrefix)
|
const legacyStores = legacyStorageNames.map(localStorageWithPrefix)
|
||||||
|
|
@ -633,26 +609,15 @@ export function persisted<T>(
|
||||||
|
|
||||||
const current = currentStorage as AsyncStorage
|
const current = currentStorage as AsyncStorage
|
||||||
const legacyStore = legacyStorage as AsyncStorage | undefined
|
const legacyStore = legacyStorage as AsyncStorage | undefined
|
||||||
const oldCurrent = draft
|
const legacyStores = legacyStorageNames
|
||||||
? isDesktop
|
.map((name) => platform.storage?.(name) as AsyncStorage | undefined)
|
||||||
? platform.storage?.(config.storage)
|
|
||||||
: config.storage
|
|
||||||
? localStorageWithPrefix(config.storage)
|
|
||||||
: localStorageDirect()
|
|
||||||
: undefined
|
|
||||||
const legacyStores = [
|
|
||||||
oldCurrent,
|
|
||||||
...legacyStorageNames.map((name) => (isDesktop ? platform.storage?.(name) : localStorageWithPrefix(name))),
|
|
||||||
]
|
|
||||||
.filter((x) => !!x)
|
.filter((x) => !!x)
|
||||||
.map(toAsyncStorage)
|
|
||||||
let draftLatest: string | undefined
|
|
||||||
|
|
||||||
const api: AsyncStorage = {
|
const api: AsyncStorage = {
|
||||||
getItem: async (key) => {
|
getItem: async (key) => {
|
||||||
const value = await readCurrentAsync({ storage: current, key, defaults, migrate: config.migrate })
|
const value = await readCurrentAsync({ storage: current, key, defaults, migrate: config.migrate })
|
||||||
if (value !== undefined) return value
|
if (value !== undefined) return value
|
||||||
const migrated = await migrateLegacyAsync({
|
return migrateLegacyAsync({
|
||||||
current,
|
current,
|
||||||
legacyStore,
|
legacyStore,
|
||||||
stores: legacyStores,
|
stores: legacyStores,
|
||||||
|
|
@ -661,15 +626,8 @@ export function persisted<T>(
|
||||||
defaults,
|
defaults,
|
||||||
migrate: config.migrate,
|
migrate: config.migrate,
|
||||||
})
|
})
|
||||||
if (draftLatest === undefined) {
|
|
||||||
if (draft && migrated !== null) return (await current.getItem(key)) ?? migrated
|
|
||||||
return migrated
|
|
||||||
}
|
|
||||||
await current.setItem(key, draftLatest)
|
|
||||||
return draftLatest
|
|
||||||
},
|
},
|
||||||
setItem: async (key, value) => {
|
setItem: async (key, value) => {
|
||||||
if (draft) draftLatest = value
|
|
||||||
await current.setItem(key, value)
|
await current.setItem(key, value)
|
||||||
},
|
},
|
||||||
removeItem: async (key) => {
|
removeItem: async (key) => {
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,8 @@ describe("extractPromptFromParts", () => {
|
||||||
expect(result).toHaveLength(3)
|
expect(result).toHaveLength(3)
|
||||||
expect(result[0]).toMatchObject({ type: "text", content: "check these" })
|
expect(result[0]).toMatchObject({ type: "text", content: "check these" })
|
||||||
expect(result.slice(1)).toMatchObject([
|
expect(result.slice(1)).toMatchObject([
|
||||||
{
|
{ type: "image", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" },
|
||||||
type: "image",
|
{ type: "image", filename: "b.pdf", mime: "application/pdf", dataUrl: "data:application/pdf;base64,BBB" },
|
||||||
filename: "a.png",
|
|
||||||
mime: "image/png",
|
|
||||||
blob: expect.objectContaining({ id: expect.any(String) }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
filename: "b.pdf",
|
|
||||||
mime: "application/pdf",
|
|
||||||
blob: expect.objectContaining({ id: expect.any(String) }),
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { AgentPart as MessageAgentPart, FilePart, Part, TextPart } from "@opencode-ai/sdk/v2"
|
import type { AgentPart as MessageAgentPart, FilePart, Part, TextPart } from "@opencode-ai/sdk/v2"
|
||||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
||||||
import { createLegacyBlobReference } from "@/utils/draft-store"
|
|
||||||
|
|
||||||
type Inline =
|
type Inline =
|
||||||
| {
|
| {
|
||||||
|
|
@ -108,7 +107,7 @@ export function extractPromptFromParts(parts: Part[], opts?: { directory?: strin
|
||||||
id: filePart.id,
|
id: filePart.id,
|
||||||
filename: filePart.filename ?? attachmentName,
|
filename: filePart.filename ?? attachmentName,
|
||||||
mime: filePart.mime,
|
mime: filePart.mime,
|
||||||
blob: createLegacyBlobReference(filePart.url),
|
dataUrl: filePart.url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
import { Icon, type IconProps } from "@opencode-ai/ui/icon"
|
import { Icon, type IconProps } from "@opencode-ai/ui/icon"
|
||||||
import {
|
import { Toast, showToast as showLegacyToast, type ToastOptions, type ToastVariant } from "@opencode-ai/ui/toast"
|
||||||
Toast,
|
import { ToastV2, showToastV2 } from "@opencode-ai/ui/v2/toast-v2"
|
||||||
showToast as showLegacyToast,
|
|
||||||
toaster as legacyToaster,
|
|
||||||
type ToastOptions,
|
|
||||||
type ToastVariant,
|
|
||||||
} from "@opencode-ai/ui/toast"
|
|
||||||
import { ToastV2, showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
|
|
||||||
|
|
||||||
let v2 = false
|
let v2 = false
|
||||||
|
|
||||||
|
|
@ -33,13 +27,6 @@ export function showToast(options: ToastOptions | string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1 and v2 ids come from separate registries, so dismissal has to use the same
|
|
||||||
// implementation that issued the id.
|
|
||||||
export function dismissToast(toastId: number) {
|
|
||||||
if (!v2) return legacyToaster.dismiss(toastId)
|
|
||||||
return toasterV2.dismiss(toastId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveIcon(icon: IconProps["name"] | undefined, variant: ToastVariant | undefined) {
|
function resolveIcon(icon: IconProps["name"] | undefined, variant: ToastVariant | undefined) {
|
||||||
const name = icon ?? (variant === "success" ? "check" : undefined)
|
const name = icon ?? (variant === "success" ? "check" : undefined)
|
||||||
if (!name) return
|
if (!name) return
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { createRoot } from "solid-js"
|
import { createRoot } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { createPromptAttachmentsCore } from "@/components/prompt-input/attachments"
|
import { createPromptAttachmentsCore } from "@/components/prompt-input/attachments"
|
||||||
import { createPromptState } from "@/context/prompt"
|
import { createPromptState } from "@/context/prompt"
|
||||||
import { createPromptInputV2Attachments } from "../../session-ui/src/v2/components/prompt-input/attachments"
|
|
||||||
import type { PromptInputV2Prompt } from "../../session-ui/src/v2/components/prompt-input/types"
|
|
||||||
|
|
||||||
describe("prompt attachment session ownership", () => {
|
describe("prompt attachment session ownership", () => {
|
||||||
test("adds an asynchronously read image to the session where the read started", async () => {
|
test("adds an asynchronously read image to the session where the read started", async () => {
|
||||||
|
|
@ -88,88 +85,6 @@ describe("prompt attachment session ownership", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("rejects a duplicate native clipboard attachment in the V2 prompt store", async () => {
|
|
||||||
await createRoot(async (dispose) => {
|
|
||||||
const [state, setState] = createStore({ prompt: [] as PromptInputV2Prompt })
|
|
||||||
const duplicate = Promise.withResolvers<void>()
|
|
||||||
const files = [
|
|
||||||
new File(["hello"], "clipboard-1.txt", { type: "text/plain" }),
|
|
||||||
new File(["hello"], "clipboard-2.txt", { type: "text/plain" }),
|
|
||||||
]
|
|
||||||
const attachments = createPromptInputV2Attachments({
|
|
||||||
capture: () => ({
|
|
||||||
current: () => state.prompt,
|
|
||||||
cursor: () => 0,
|
|
||||||
set: (prompt) => setState("prompt", prompt),
|
|
||||||
}),
|
|
||||||
editor: () => document.createElement("div"),
|
|
||||||
focusEditor: () => undefined,
|
|
||||||
addPart: () => false,
|
|
||||||
setDraggingType: () => undefined,
|
|
||||||
directory: () => "/",
|
|
||||||
isDialogActive: () => false,
|
|
||||||
warn: () => undefined,
|
|
||||||
duplicate: duplicate.resolve,
|
|
||||||
onError: () => undefined,
|
|
||||||
readClipboardImage: async () => files.shift() ?? null,
|
|
||||||
})
|
|
||||||
const event = {
|
|
||||||
clipboardData: { items: [], getData: () => "" },
|
|
||||||
preventDefault: () => undefined,
|
|
||||||
stopPropagation: () => undefined,
|
|
||||||
} as unknown as ClipboardEvent
|
|
||||||
|
|
||||||
await attachments.handlePaste(event)
|
|
||||||
await attachments.handlePaste(event)
|
|
||||||
await duplicate.promise
|
|
||||||
|
|
||||||
expect(state.prompt).toHaveLength(1)
|
|
||||||
dispose()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("rejects desktop duplicates and keeps changed files in the V2 prompt store", async () => {
|
|
||||||
await createRoot(async (dispose) => {
|
|
||||||
const [state, setState] = createStore({ prompt: [] as PromptInputV2Prompt })
|
|
||||||
const duplicates: string[] = []
|
|
||||||
const attachments = createPromptInputV2Attachments({
|
|
||||||
capture: () => ({
|
|
||||||
current: () => state.prompt,
|
|
||||||
cursor: () => 0,
|
|
||||||
set: (prompt) => setState("prompt", prompt),
|
|
||||||
}),
|
|
||||||
editor: () => document.createElement("div"),
|
|
||||||
focusEditor: () => undefined,
|
|
||||||
addPart: () => false,
|
|
||||||
setDraggingType: () => undefined,
|
|
||||||
directory: () => "/",
|
|
||||||
isDialogActive: () => false,
|
|
||||||
warn: () => undefined,
|
|
||||||
duplicate: () => duplicates.push("duplicate"),
|
|
||||||
onError: () => undefined,
|
|
||||||
getPathForFile: (file) => (file.name === "browser.txt" ? "" : `/tmp/${file.name}`),
|
|
||||||
})
|
|
||||||
const first = new File(["first"], "a.txt", { type: "text/plain" })
|
|
||||||
const second = new File(["second"], "b.txt", { type: "text/plain" })
|
|
||||||
|
|
||||||
await attachments.addAttachments([first, second])
|
|
||||||
await attachments.addAttachments([first, second])
|
|
||||||
expect(state.prompt).toHaveLength(2)
|
|
||||||
expect(duplicates).toEqual(["duplicate", "duplicate"])
|
|
||||||
|
|
||||||
await attachments.addAttachments([new File(["edited"], "a.txt", { type: "text/plain" })])
|
|
||||||
expect(state.prompt).toHaveLength(3)
|
|
||||||
|
|
||||||
await attachments.addAttachments([
|
|
||||||
new File(["same"], "browser.txt", { type: "text/plain" }),
|
|
||||||
new File(["same"], "browser.txt", { type: "text/plain" }),
|
|
||||||
])
|
|
||||||
expect(state.prompt).toHaveLength(4)
|
|
||||||
expect(duplicates).toHaveLength(3)
|
|
||||||
dispose()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function images(prompt: ReturnType<typeof createPromptState>) {
|
function images(prompt: ReturnType<typeof createPromptState>) {
|
||||||
return prompt.current().filter((part) => part.type === "image")
|
return prompt.current().filter((part) => part.type === "image")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { beforeAll, describe, expect, mock, test } from "bun:test"
|
||||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
import type { AsyncStorage } from "@solid-primitives/storage"
|
||||||
import { createEffect, createRoot } from "solid-js"
|
import { createEffect, createRoot } from "solid-js"
|
||||||
import { ServerScope } from "@/utils/server-scope"
|
import { ServerScope } from "@/utils/server-scope"
|
||||||
import { createDraftStore } from "@/utils/draft-store"
|
|
||||||
|
|
||||||
let Prompt: typeof import("@/context/prompt")
|
let Prompt: typeof import("@/context/prompt")
|
||||||
let read: ((value: string | null) => void) | undefined
|
let read: ((value: string | null) => void) | undefined
|
||||||
|
|
@ -31,7 +30,7 @@ beforeAll(async () => {
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
mock.module("@/context/platform", () => ({
|
mock.module("@/context/platform", () => ({
|
||||||
usePlatform: () => ({ platform: "desktop", storage: () => storage, draftStore: storage }),
|
usePlatform: () => ({ platform: "desktop", storage: () => storage }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
Prompt = await import("@/context/prompt")
|
Prompt = await import("@/context/prompt")
|
||||||
|
|
@ -70,50 +69,3 @@ describe("prompt persistence", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("moves legacy image data URLs into blobs and hydrates object URLs", async () => {
|
|
||||||
const documents = new Map<string, string>()
|
|
||||||
const blobs = new Map<string, Blob>()
|
|
||||||
const store = createDraftStore({
|
|
||||||
get: async (key) => documents.get(key) ?? null,
|
|
||||||
set: async (key, value) => void documents.set(key, value),
|
|
||||||
remove: async (key) => void documents.delete(key),
|
|
||||||
putBlob: async (blob) => {
|
|
||||||
const id = String(blob.size)
|
|
||||||
blobs.set(id, blob)
|
|
||||||
return id
|
|
||||||
},
|
|
||||||
getBlob: async (id) => blobs.get(id) ?? null,
|
|
||||||
})
|
|
||||||
|
|
||||||
await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }))
|
|
||||||
expect(documents.get("prompt")).not.toContain("dataUrl")
|
|
||||||
const value = JSON.parse((await store.getItem("prompt"))!)
|
|
||||||
expect(value.prompt[0].blob.id).toBe("1")
|
|
||||||
expect(value.prompt[0].blob.url).toStartWith("blob:")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("does not let delayed blob migration overwrite a newer draft", async () => {
|
|
||||||
const documents = new Map<string, string>()
|
|
||||||
const migration = Promise.withResolvers<void>()
|
|
||||||
const store = createDraftStore({
|
|
||||||
get: async () => null,
|
|
||||||
set: async (key, value) => void documents.set(key, value),
|
|
||||||
remove: async () => undefined,
|
|
||||||
putBlob: async () => {
|
|
||||||
await migration.promise
|
|
||||||
return "blob"
|
|
||||||
},
|
|
||||||
getBlob: async () => null,
|
|
||||||
})
|
|
||||||
const older = store.setItem(
|
|
||||||
"prompt",
|
|
||||||
JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }),
|
|
||||||
)
|
|
||||||
await Bun.sleep(0)
|
|
||||||
await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "text", content: "latest" }] }))
|
|
||||||
migration.resolve()
|
|
||||||
await older
|
|
||||||
|
|
||||||
expect(documents.get("prompt")).toContain("latest")
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import { expect, test } from "bun:test"
|
|
||||||
import { MetaProvider, Title } from "@solidjs/meta"
|
|
||||||
import { MemoryRouter, Route, createMemoryHistory, useParams } from "@solidjs/router"
|
|
||||||
import { createMemo } from "solid-js"
|
|
||||||
import { createComponent, render } from "solid-js/web"
|
|
||||||
|
|
||||||
test("route cleanup cannot invalidate an owner list being disposed", async () => {
|
|
||||||
const host = document.createElement("div")
|
|
||||||
document.body.append(host)
|
|
||||||
const history = createMemoryHistory()
|
|
||||||
|
|
||||||
const RepoPage = () => {
|
|
||||||
const params = useParams<{ id?: string }>()
|
|
||||||
const title = createMemo(() => params.id ?? "")
|
|
||||||
const button = document.createElement("button")
|
|
||||||
button.textContent = "Back"
|
|
||||||
button.addEventListener("click", () => history.set({ value: "/", scroll: false, replace: false }))
|
|
||||||
return [
|
|
||||||
createComponent(Title, {
|
|
||||||
get children() {
|
|
||||||
return title()
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
button,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const HomePage = () => {
|
|
||||||
const button = document.createElement("button")
|
|
||||||
button.textContent = "Go"
|
|
||||||
button.addEventListener("click", () => history.set({ value: "/project", scroll: false, replace: false }))
|
|
||||||
return button
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = () =>
|
|
||||||
createComponent(MetaProvider, {
|
|
||||||
get children() {
|
|
||||||
return createComponent(MemoryRouter, {
|
|
||||||
history,
|
|
||||||
get children() {
|
|
||||||
return [
|
|
||||||
createComponent(Route, { path: "/", component: HomePage }),
|
|
||||||
createComponent(Route, { path: "/:id", component: RepoPage }),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const dispose = render(() => createComponent(App, {}), host)
|
|
||||||
const go = host.querySelector("button")
|
|
||||||
expect(go?.textContent).toBe("Go")
|
|
||||||
go?.click()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
||||||
|
|
||||||
const back = host.querySelector("button")
|
|
||||||
expect(back?.textContent).toBe("Back")
|
|
||||||
back?.click()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
||||||
|
|
||||||
expect(host.querySelector("button")?.textContent).toBe("Go")
|
|
||||||
dispose()
|
|
||||||
host.remove()
|
|
||||||
})
|
|
||||||
|
|
@ -1,46 +1,8 @@
|
||||||
import { beforeEach, describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { createSignal, type JSX } from "solid-js"
|
import { createSignal, type JSX } from "solid-js"
|
||||||
import { showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
|
import { showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
|
||||||
|
|
||||||
describe("showToastV2", () => {
|
describe("showToastV2", () => {
|
||||||
// The toast registry is module state, so each test starts from an empty stack.
|
|
||||||
beforeEach(() => {
|
|
||||||
toasterV2.dismiss()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("coalesces exact active content", () => {
|
|
||||||
const first = showToastV2({ title: "Repeated error", description: "Try again" })
|
|
||||||
const second = showToastV2({ title: "Repeated error", description: "Try again" })
|
|
||||||
const different = showToastV2({ title: "Repeated error", description: "A different error" })
|
|
||||||
|
|
||||||
expect(second).toBe(first)
|
|
||||||
expect(different).not.toBe(first)
|
|
||||||
|
|
||||||
toasterV2.dismiss(first)
|
|
||||||
toasterV2.dismiss(different)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("allows dismissed content to appear again", () => {
|
|
||||||
const first = showToastV2("Dismiss and retry")
|
|
||||||
toasterV2.dismiss(first)
|
|
||||||
|
|
||||||
const second = showToastV2("Dismiss and retry")
|
|
||||||
expect(second).not.toBe(first)
|
|
||||||
|
|
||||||
toasterV2.dismiss(second)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("recreates matching content when it is not the topmost toast", () => {
|
|
||||||
const first = showToastV2("First toast")
|
|
||||||
const topmost = showToastV2("Topmost toast")
|
|
||||||
const repeated = showToastV2("First toast")
|
|
||||||
|
|
||||||
expect(repeated).not.toBe(first)
|
|
||||||
|
|
||||||
toasterV2.dismiss(topmost)
|
|
||||||
toasterV2.dismiss(repeated)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("creates no reactive computations at call time", () => {
|
test("creates no reactive computations at call time", () => {
|
||||||
const [tick, setTick] = createSignal(0)
|
const [tick, setTick] = createSignal(0)
|
||||||
let reads = 0
|
let reads = 0
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.opencode.ai"
|
const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.dev"
|
||||||
|
|
||||||
export const modelsData = process.env.MODELS_DEV_API_JSON
|
export const modelsData = process.env.MODELS_DEV_API_JSON
|
||||||
? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
|
? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/codemode",
|
"name": "@opencode-ai/codemode",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"description": "Effect-native confined code execution over schema-described tools",
|
"description": "Effect-native confined code execution over schema-described tools",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.18.11",
|
"version": "1.18.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -252,9 +252,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
|
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
"go.banner.text": "يحصل GPT 5.6 Luna على حدود استخدام مضاعفة لفترة محدودة",
|
"go.banner.text": "يحصل Kimi K3 على حدود استخدام مضاعفة لفترة محدودة",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"يبدأ Go بسعر $5 للشهر الأول، ثم $10/شهر، مع حدود استخدام سخية ووصول موثوق إلى نماذج البرمجة الرائدة.",
|
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ Grok 4.5 وGLM-5.2 وGLM-5.1 وKimi K3 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash وHy3.",
|
||||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
||||||
|
|
@ -301,7 +301,8 @@ export const dict = {
|
||||||
"go.problem.item1": "أسعار اشتراك منخفضة التكلفة",
|
"go.problem.item1": "أسعار اشتراك منخفضة التكلفة",
|
||||||
"go.problem.item2": "حدود سخية ووصول موثوق",
|
"go.problem.item2": "حدود سخية ووصول موثوق",
|
||||||
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
||||||
"go.problem.item4": "مجموعة منسقة من النماذج المختبرة للبرمجة الوكيلة",
|
"go.problem.item4":
|
||||||
|
"يتضمن Grok 4.5 وGLM-5.2 وGLM-5.1 وKimi K3 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash وHy3",
|
||||||
"go.how.title": "كيف يعمل Go",
|
"go.how.title": "كيف يعمل Go",
|
||||||
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
||||||
"go.how.step1.title": "أنشئ حسابًا",
|
"go.how.step1.title": "أنشئ حسابًا",
|
||||||
|
|
@ -325,7 +326,7 @@ export const dict = {
|
||||||
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
||||||
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"لا. يعتمد Zen على الدفع حسب الاستخدام، بينما يبدأ Go بسعر $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى مجموعة منسقة من النماذج.",
|
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح Grok 4.5 وGLM-5.2 وGLM-5.1 وKimi K3 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash وHy3.",
|
||||||
"go.faq.q4": "كم تكلفة Go؟",
|
"go.faq.q4": "كم تكلفة Go؟",
|
||||||
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
||||||
|
|
@ -334,20 +335,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "حسابك",
|
"go.faq.a4.p2.accountLink": "حسابك",
|
||||||
"go.faq.a4.p3": "ألغِ في أي وقت.",
|
"go.faq.a4.p3": "ألغِ في أي وقت.",
|
||||||
"go.faq.q5": "ماذا عن البيانات والخصوصية؟",
|
"go.faq.q5": "ماذا عن البيانات والخصوصية؟",
|
||||||
"go.faq.a5.model": "النموذج",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "تدريب النموذج",
|
"تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة للحصول على وصول عالمي مستقر. يتبع مزودونا سياسة عدم الاحتفاظ بالبيانات ولا يستخدمون بياناتك لتدريب النماذج.",
|
||||||
"go.faq.a5.retention": "الاحتفاظ بالبيانات",
|
|
||||||
"go.faq.a5.retention30": "30 يومًا",
|
|
||||||
"go.faq.a5.retention0": "0 أيام",
|
|
||||||
"go.faq.a5.used": "مستخدَمة",
|
|
||||||
"go.faq.a5.notUsed": "غير مستخدَمة",
|
|
||||||
"go.faq.a5.noAgreement": "لا توجد اتفاقية",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"تعطّل ZDR ميزات API مهمة تعتمد على البيانات المخزنة، بما في ذلك Responses API ذات الحالة، وFiles and Collections، وBatch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"تُنشأ سجلات مراقبة إساءة الاستخدام لكل استخدام لميزات API، ويُحتفظ بها لمدة تصل إلى 30 يومًا.",
|
|
||||||
"go.faq.a5.learnMore": "اعرف المزيد",
|
|
||||||
"go.faq.a5.deepseekRetention": "تُجدَّد اتفاقية ZDR شهريًا. الاتفاقية الحالية سارية حتى 31 أغسطس 2026.",
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"تتم استضافة نماذج Go في الولايات المتحدة. يتبع المزودون سياسة عدم الاحتفاظ بالبيانات ولا يستخدمون بياناتك لتدريب النماذج، مع",
|
"تتم استضافة نماذج Go في الولايات المتحدة. يتبع المزودون سياسة عدم الاحتفاظ بالبيانات ولا يستخدمون بياناتك لتدريب النماذج، مع",
|
||||||
"go.faq.a5.exceptionsLink": "الاستثناءات التالية",
|
"go.faq.a5.exceptionsLink": "الاستثناءات التالية",
|
||||||
|
|
@ -360,7 +349,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة قدرها 200 طلب/يوم. يقدّم Go مجموعة منسقة من النماذج مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، وأسبوعية، وشهرية)، تعادل تقريبًا $12 لكل 5 ساعات، و$30 في الأسبوع، و$60 في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج Grok 4.5 وGLM-5.2 وGLM-5.1 وKimi K3 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash وHy3 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
|
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||||
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
|
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
|
||||||
|
|
@ -385,7 +374,7 @@ export const dict = {
|
||||||
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
|
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "النموذج معطل",
|
"zen.api.error.modelDisabled": "النموذج معطل",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"لا يتوفر أحدث إصدار من هذا النموذج إلا مستضافًا في الصين، ويتطلب تفعيلًا صريحًا: {{consoleGoUrl}}",
|
"هذا النموذج مستضاف في الصين. إذا كنت ترغب في استخدام هذا النموذج، فعّله في إعداداتك: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"انتهى العرض المجاني لـ {{model}}. يمكنك مواصلة استخدام النموذج بالاشتراك في OpenCode Go - {{link}}",
|
"انتهى العرض المجاني لـ {{model}}. يمكنك مواصلة استخدام النموذج بالاشتراك في OpenCode Go - {{link}}",
|
||||||
|
|
||||||
|
|
@ -675,7 +664,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 للشهر الأول",
|
"workspace.lite.promo.price": "$5 للشهر الأول",
|
||||||
"workspace.lite.promo.modelsTitle": "ما يتضمنه",
|
"workspace.lite.promo.modelsTitle": "ما يتضمنه",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"صُممت الخطة بشكل أساسي للمستخدمين الدوليين، وتوفر وصولًا عالميًا مستقرًا. قد تتغير الأسعار وحدود الاستخدام بينما نتعلم من الاستخدام المبكر والملاحظات.",
|
"تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة للحصول على وصول عالمي مستقر. قد تتغير الأسعار وحدود الاستخدام بناءً على تعلمنا من الاستخدام المبكر والملاحظات.",
|
||||||
"workspace.lite.promo.subscribe": "الاشتراك في Go",
|
"workspace.lite.promo.subscribe": "الاشتراك في Go",
|
||||||
"workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
|
"workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
|
||||||
"workspace.lite.promo.otherMethods": "طرق دفع أخرى",
|
"workspace.lite.promo.otherMethods": "طرق دفع أخرى",
|
||||||
|
|
|
||||||
|
|
@ -256,9 +256,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "seguintes exceções",
|
"zen.privacy.exceptionsLink": "seguintes exceções",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||||
"go.banner.text": "GPT 5.6 Luna tem limites de uso 2x maiores por tempo limitado",
|
"go.banner.text": "Kimi K3 tem limites de uso 2x maiores por tempo limitado",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de uso e acesso confiável aos principais modelos de codificação.",
|
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash e Hy3.",
|
||||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
||||||
|
|
@ -306,7 +306,8 @@ export const dict = {
|
||||||
"go.problem.item1": "Preço de assinatura de baixo custo",
|
"go.problem.item1": "Preço de assinatura de baixo custo",
|
||||||
"go.problem.item2": "Limites generosos e acesso confiável",
|
"go.problem.item2": "Limites generosos e acesso confiável",
|
||||||
"go.problem.item3": "Feito para o maior número possível de programadores",
|
"go.problem.item3": "Feito para o maior número possível de programadores",
|
||||||
"go.problem.item4": "Uma seleção de modelos testados para codificação com agentes",
|
"go.problem.item4":
|
||||||
|
"Inclui Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash e Hy3",
|
||||||
"go.how.title": "Como o Go funciona",
|
"go.how.title": "Como o Go funciona",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
||||||
|
|
@ -332,7 +333,7 @@ export const dict = {
|
||||||
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
||||||
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável a uma seleção de modelos.",
|
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash e Hy3.",
|
||||||
"go.faq.q4": "Quanto custa o Go?",
|
"go.faq.q4": "Quanto custa o Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "O Go custa",
|
"go.faq.a4.p1.beforePricing": "O Go custa",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
||||||
|
|
@ -341,21 +342,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "conta",
|
"go.faq.a4.p2.accountLink": "conta",
|
||||||
"go.faq.a4.p3": "Cancele a qualquer momento.",
|
"go.faq.a4.p3": "Cancele a qualquer momento.",
|
||||||
"go.faq.q5": "E sobre dados e privacidade?",
|
"go.faq.q5": "E sobre dados e privacidade?",
|
||||||
"go.faq.a5.model": "Modelo",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "Treinamento de modelos",
|
"O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. Nossos provedores seguem uma política de retenção zero e não usam seus dados para treinamento de modelos.",
|
||||||
"go.faq.a5.retention": "Retenção de dados",
|
|
||||||
"go.faq.a5.retention30": "30 dias",
|
|
||||||
"go.faq.a5.retention0": "0 dias",
|
|
||||||
"go.faq.a5.used": "Usado",
|
|
||||||
"go.faq.a5.notUsed": "Não usado",
|
|
||||||
"go.faq.a5.noAgreement": "Sem acordo",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"O ZDR desativa recursos importantes da API que dependem de dados armazenados, incluindo a Responses API com estado, Files and Collections e a Batch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"Logs de monitoramento de abuso são gerados para todo uso de recursos da API e retidos por até 30 dias.",
|
|
||||||
"go.faq.a5.learnMore": "Saiba mais",
|
|
||||||
"go.faq.a5.deepseekRetention":
|
|
||||||
"O acordo de ZDR é renovado mensalmente. O acordo atual é válido até 31 de agosto de 2026.",
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"Os modelos Go são hospedados nos EUA. Os provedores seguem uma política de retenção zero e não usam seus dados para treinamento de modelos, com as",
|
"Os modelos Go são hospedados nos EUA. Os provedores seguem uma política de retenção zero e não usam seus dados para treinamento de modelos, com as",
|
||||||
"go.faq.a5.exceptionsLink": "seguintes exceções",
|
"go.faq.a5.exceptionsLink": "seguintes exceções",
|
||||||
|
|
@ -369,7 +357,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go oferece uma seleção de modelos com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash e Hy3 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
|
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
|
||||||
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
|
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
|
||||||
|
|
@ -394,7 +382,7 @@ export const dict = {
|
||||||
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
|
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "O modelo está desabilitado",
|
"zen.api.error.modelDisabled": "O modelo está desabilitado",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"A versão mais recente deste modelo está disponível apenas com hospedagem na China e requer adesão explícita: {{consoleGoUrl}}",
|
"Este modelo está hospedado na China. Se você quiser usar este modelo, ative-o nas suas configurações: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"A promoção gratuita do {{model}} terminou. Você pode continuar usando o modelo assinando o OpenCode Go - {{link}}",
|
"A promoção gratuita do {{model}} terminou. Você pode continuar usando o modelo assinando o OpenCode Go - {{link}}",
|
||||||
|
|
||||||
|
|
@ -686,7 +674,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 no primeiro mês",
|
"workspace.lite.promo.price": "$5 no primeiro mês",
|
||||||
"workspace.lite.promo.modelsTitle": "O que está incluído",
|
"workspace.lite.promo.modelsTitle": "O que está incluído",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"O plano foi desenvolvido principalmente para usuários internacionais e oferece acesso global estável. Os preços e limites de uso podem mudar à medida que aprendemos com o uso inicial e o feedback recebido.",
|
"O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. Preços e limites de uso podem mudar conforme aprendemos com o uso inicial e feedback.",
|
||||||
"workspace.lite.promo.subscribe": "Assinar Go",
|
"workspace.lite.promo.subscribe": "Assinar Go",
|
||||||
"workspace.lite.promo.subscribing": "Redirecionando...",
|
"workspace.lite.promo.subscribing": "Redirecionando...",
|
||||||
"workspace.lite.promo.otherMethods": "Outros métodos de pagamento",
|
"workspace.lite.promo.otherMethods": "Outros métodos de pagamento",
|
||||||
|
|
|
||||||
|
|
@ -254,9 +254,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "følgende undtagelser",
|
"zen.privacy.exceptionsLink": "følgende undtagelser",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||||
"go.banner.text": "GPT 5.6 Luna får fordoblet brugsgrænse i en begrænset periode",
|
"go.banner.text": "Kimi K3 får fordoblet brugsgrænse i en begrænset periode",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse brugsgrænser og pålidelig adgang til førende kodningsmodeller.",
|
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash og Hy3.",
|
||||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
||||||
|
|
@ -303,7 +303,8 @@ export const dict = {
|
||||||
"go.problem.item1": "Lavpris abonnementspriser",
|
"go.problem.item1": "Lavpris abonnementspriser",
|
||||||
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
||||||
"go.problem.item3": "Bygget til så mange programmører som muligt",
|
"go.problem.item3": "Bygget til så mange programmører som muligt",
|
||||||
"go.problem.item4": "Et kurateret modeludvalg testet til agentisk kodning",
|
"go.problem.item4":
|
||||||
|
"Inkluderer Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash og Hy3",
|
||||||
"go.how.title": "Hvordan Go virker",
|
"go.how.title": "Hvordan Go virker",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
||||||
|
|
@ -329,7 +330,7 @@ export const dict = {
|
||||||
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
||||||
"go.faq.q3": "Er Go det samme som Zen?",
|
"go.faq.q3": "Er Go det samme som Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til et kurateret modeludvalg.",
|
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash og Hy3.",
|
||||||
"go.faq.q4": "Hvad koster Go?",
|
"go.faq.q4": "Hvad koster Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||||
|
|
@ -338,22 +339,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "konto",
|
"go.faq.a4.p2.accountLink": "konto",
|
||||||
"go.faq.a4.p3": "Annuller til enhver tid.",
|
"go.faq.a4.p3": "Annuller til enhver tid.",
|
||||||
"go.faq.q5": "Hvad med data og privatliv?",
|
"go.faq.q5": "Hvad med data og privatliv?",
|
||||||
"go.faq.a5.model": "Model",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "Modeltræning",
|
"Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. Vores udbydere følger en nulopbevaringspolitik og bruger ikke dine data til modeltræning.",
|
||||||
"go.faq.a5.retention": "Dataopbevaring",
|
|
||||||
"go.faq.a5.retention30": "30 dage",
|
|
||||||
"go.faq.a5.retention0": "0 dage",
|
|
||||||
"go.faq.a5.used": "Brugt",
|
|
||||||
"go.faq.a5.notUsed": "Ikke brugt",
|
|
||||||
"go.faq.a5.noAgreement": "Ingen aftale",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"ZDR deaktiverer vigtige API-funktioner, der afhænger af lagrede data, herunder den tilstandsbevarende Responses API, Files and Collections og Batch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"Logfiler til overvågning af misbrug genereres ved al brug af API-funktioner og opbevares i op til 30 dage.",
|
|
||||||
"go.faq.a5.learnMore": "Læs mere",
|
|
||||||
"go.faq.a5.deepseekRetention":
|
|
||||||
"ZDR-aftalen fornyes månedligt. Den nuværende aftale er gyldig til og med 31. august 2026.",
|
|
||||||
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"Go-modeller hostes i USA. Udbydere følger en nulopbevaringspolitik og bruger ikke dine data til modeltræning, med de",
|
"Go-modeller hostes i USA. Udbydere følger en nulopbevaringspolitik og bruger ikke dine data til modeltræning, med de",
|
||||||
"go.faq.a5.exceptionsLink": "følgende undtagelser",
|
"go.faq.a5.exceptionsLink": "følgende undtagelser",
|
||||||
|
|
@ -366,7 +353,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Gratis modeller inkluderer Big Pickle plus kampagnemodeller, der er tilgængelige på det pågældende tidspunkt, med en kvote på 200 forespørgsler/dag. Go tilbyder et kurateret modeludvalg med højere forespørgselskvoter håndhævet over rullende perioder (5 timer, ugentligt og månedligt), omtrent svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (det faktiske antal forespørgsler varierer efter model og brug).",
|
"Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash og Hy3 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
|
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
|
||||||
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
|
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
|
||||||
|
|
@ -391,7 +378,7 @@ export const dict = {
|
||||||
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
|
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
|
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"Den nyeste version af denne model er kun tilgængelig som hostet i Kina og kræver, at du aktivt tilvælger den: {{consoleGoUrl}}",
|
"Denne model hostes i Kina. Hvis du vil bruge denne model, skal du aktivere den i dine indstillinger: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Den gratis kampagne for {{model}} er afsluttet. Du kan fortsætte med at bruge modellen ved at abonnere på OpenCode Go - {{link}}",
|
"Den gratis kampagne for {{model}} er afsluttet. Du kan fortsætte med at bruge modellen ved at abonnere på OpenCode Go - {{link}}",
|
||||||
|
|
||||||
|
|
@ -683,7 +670,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 for den første måned",
|
"workspace.lite.promo.price": "$5 for den første måned",
|
||||||
"workspace.lite.promo.modelsTitle": "Hvad er inkluderet",
|
"workspace.lite.promo.modelsTitle": "Hvad er inkluderet",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"Planen er primært udviklet til internationale brugere og giver stabil adgang i hele verden. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af de første brugserfaringer og tilbagemeldinger.",
|
"Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af tidlig brug og feedback.",
|
||||||
"workspace.lite.promo.subscribe": "Abonner på Go",
|
"workspace.lite.promo.subscribe": "Abonner på Go",
|
||||||
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
||||||
"workspace.lite.promo.otherMethods": "Andre betalingsmetoder",
|
"workspace.lite.promo.otherMethods": "Andre betalingsmetoder",
|
||||||
|
|
|
||||||
|
|
@ -256,9 +256,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
|
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
||||||
"go.banner.text": "GPT 5.6 Luna erhält für begrenzte Zeit 2x Nutzungslimits",
|
"go.banner.text": "Kimi K3 erhält für begrenzte Zeit 2x Nutzungslimits",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go beginnt bei $5 für deinen ersten Monat, danach $10/Monat, mit großzügigen Nutzungslimits und zuverlässigem Zugang zu führenden Coding-Modellen.",
|
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash und Hy3.",
|
||||||
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
||||||
|
|
@ -305,7 +305,8 @@ export const dict = {
|
||||||
"go.problem.item1": "Kostengünstiges Abonnement",
|
"go.problem.item1": "Kostengünstiges Abonnement",
|
||||||
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
||||||
"go.problem.item3": "Für so viele Programmierer wie möglich gebaut",
|
"go.problem.item3": "Für so viele Programmierer wie möglich gebaut",
|
||||||
"go.problem.item4": "Eine kuratierte, für Agentic Coding getestete Modellauswahl",
|
"go.problem.item4":
|
||||||
|
"Beinhaltet Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash und Hy3",
|
||||||
"go.how.title": "Wie Go funktioniert",
|
"go.how.title": "Wie Go funktioniert",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
||||||
|
|
@ -331,7 +332,7 @@ export const dict = {
|
||||||
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
||||||
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für deinen ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu einer kuratierten Modellauswahl.",
|
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash und Hy3.",
|
||||||
"go.faq.q4": "Wie viel kostet Go?",
|
"go.faq.q4": "Wie viel kostet Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go kostet",
|
"go.faq.a4.p1.beforePricing": "Go kostet",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
||||||
|
|
@ -340,21 +341,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "Konto verwalten",
|
"go.faq.a4.p2.accountLink": "Konto verwalten",
|
||||||
"go.faq.a4.p3": "Jederzeit kündbar.",
|
"go.faq.a4.p3": "Jederzeit kündbar.",
|
||||||
"go.faq.q5": "Was ist mit Daten und Privatsphäre?",
|
"go.faq.q5": "Was ist mit Daten und Privatsphäre?",
|
||||||
"go.faq.a5.model": "Modell",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "Modelltraining",
|
"Der Plan ist primär für internationale Nutzer konzipiert, mit Modellen gehostet in den USA, der EU und Singapur für stabilen globalen Zugang. Unsere Anbieter verfolgen eine Zero-Retention-Politik und nutzen deine Daten nicht für das Training von Modellen.",
|
||||||
"go.faq.a5.retention": "Datenaufbewahrung",
|
|
||||||
"go.faq.a5.retention30": "30 Tage",
|
|
||||||
"go.faq.a5.retention0": "0 Tage",
|
|
||||||
"go.faq.a5.used": "Verwendet",
|
|
||||||
"go.faq.a5.notUsed": "Nicht verwendet",
|
|
||||||
"go.faq.a5.noAgreement": "Keine Vereinbarung",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"ZDR deaktiviert wichtige API-Funktionen, die von gespeicherten Daten abhängen, einschließlich der zustandsbehafteten Responses API, Files and Collections und der Batch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"Für die Nutzung aller API-Funktionen werden Protokolle zur Missbrauchsüberwachung erstellt und bis zu 30 Tage lang aufbewahrt.",
|
|
||||||
"go.faq.a5.learnMore": "Mehr erfahren",
|
|
||||||
"go.faq.a5.deepseekRetention":
|
|
||||||
"Die ZDR-Vereinbarung wird monatlich erneuert. Die aktuelle Vereinbarung gilt bis einschließlich 31. August 2026.",
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"Go-Modelle werden in den USA gehostet. Anbieter verfolgen eine Zero-Retention-Politik und nutzen deine Daten nicht für das Training von Modellen, mit den",
|
"Go-Modelle werden in den USA gehostet. Anbieter verfolgen eine Zero-Retention-Politik und nutzen deine Daten nicht für das Training von Modellen, mit den",
|
||||||
"go.faq.a5.exceptionsLink": "folgenden Ausnahmen",
|
"go.faq.a5.exceptionsLink": "folgenden Ausnahmen",
|
||||||
|
|
@ -368,7 +356,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go bietet eine kuratierte Modellauswahl mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash und Hy3 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
|
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
|
||||||
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
|
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
|
||||||
|
|
@ -393,7 +381,7 @@ export const dict = {
|
||||||
"Du hast dein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{membersUrl}}",
|
"Du hast dein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Modell ist deaktiviert",
|
"zen.api.error.modelDisabled": "Modell ist deaktiviert",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"Die neueste Version dieses Modells ist nur verfügbar, wenn sie in China gehostet wird, und muss ausdrücklich aktiviert werden: {{consoleGoUrl}}",
|
"Dieses Modell wird in China gehostet. Wenn du dieses Modell verwenden möchtest, aktiviere es in deinen Einstellungen: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Die kostenlose Aktion für {{model}} ist beendet. Du kannst das Modell weiterhin nutzen, indem du OpenCode Go abonnierst - {{link}}",
|
"Die kostenlose Aktion für {{model}} ist beendet. Du kannst das Modell weiterhin nutzen, indem du OpenCode Go abonnierst - {{link}}",
|
||||||
|
|
||||||
|
|
@ -685,7 +673,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 im ersten Monat",
|
"workspace.lite.promo.price": "$5 im ersten Monat",
|
||||||
"workspace.lite.promo.modelsTitle": "Was enthalten ist",
|
"workspace.lite.promo.modelsTitle": "Was enthalten ist",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"Der Plan richtet sich in erster Linie an internationale Nutzer und bietet stabilen weltweiten Zugriff. Preise und Nutzungslimits können sich ändern, wenn wir Erkenntnisse aus der ersten Nutzung und dem Feedback gewinnen.",
|
"Der Plan wurde hauptsächlich für internationale Nutzer entwickelt, wobei die Modelle in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. Preise und Nutzungslimits können sich ändern, während wir aus der frühen Nutzung und dem Feedback lernen.",
|
||||||
"workspace.lite.promo.subscribe": "Go abonnieren",
|
"workspace.lite.promo.subscribe": "Go abonnieren",
|
||||||
"workspace.lite.promo.subscribing": "Leite weiter...",
|
"workspace.lite.promo.subscribing": "Leite weiter...",
|
||||||
"workspace.lite.promo.otherMethods": "Andere Zahlungsmethoden",
|
"workspace.lite.promo.otherMethods": "Andere Zahlungsmethoden",
|
||||||
|
|
|
||||||
|
|
@ -253,9 +253,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "following exceptions",
|
"zen.privacy.exceptionsLink": "following exceptions",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||||
"go.banner.text": "GPT 5.6 Luna gets 2× usage limits for a limited time",
|
"go.banner.text": "Kimi K3 gets 2× usage limits for a limited time",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starts at $5 for your first month, then $10/month, with generous usage limits and reliable access to leading coding models.",
|
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash, and Hy3.",
|
||||||
"go.hero.title": "Low cost coding models for everyone",
|
"go.hero.title": "Low cost coding models for everyone",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
||||||
|
|
@ -270,7 +270,6 @@ export const dict = {
|
||||||
"go.graph.freePill": "Big Pickle and free models",
|
"go.graph.freePill": "Big Pickle and free models",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
"go.graph.label": "Requests per 5 hour",
|
"go.graph.label": "Requests per 5 hour",
|
||||||
"go.graph.tick": "{{n}}x",
|
|
||||||
"go.graph.usageLimits": "Usage limits",
|
"go.graph.usageLimits": "Usage limits",
|
||||||
"go.graph.aria": "Requests per 5h: {{free}} vs {{go}}",
|
"go.graph.aria": "Requests per 5h: {{free}} vs {{go}}",
|
||||||
|
|
||||||
|
|
@ -302,7 +301,8 @@ export const dict = {
|
||||||
"go.problem.item1": "Low cost subscription pricing",
|
"go.problem.item1": "Low cost subscription pricing",
|
||||||
"go.problem.item2": "Generous limits and reliable access",
|
"go.problem.item2": "Generous limits and reliable access",
|
||||||
"go.problem.item3": "Built for as many programmers as possible",
|
"go.problem.item3": "Built for as many programmers as possible",
|
||||||
"go.problem.item4": "A curated model lineup tested for agentic coding",
|
"go.problem.item4":
|
||||||
|
"Includes Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash, and Hy3",
|
||||||
"go.how.title": "How Go works",
|
"go.how.title": "How Go works",
|
||||||
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
||||||
"go.how.step1.title": "Create an account",
|
"go.how.step1.title": "Create an account",
|
||||||
|
|
@ -327,7 +327,7 @@ export const dict = {
|
||||||
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
||||||
"go.faq.q3": "Is Go the same as Zen?",
|
"go.faq.q3": "Is Go the same as Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to a curated model lineup.",
|
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash, and Hy3.",
|
||||||
"go.faq.q4": "How much does Go cost?",
|
"go.faq.q4": "How much does Go cost?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go costs",
|
"go.faq.a4.p1.beforePricing": "Go costs",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 first month",
|
"go.faq.a4.p1.pricingLink": "$5 first month",
|
||||||
|
|
@ -336,21 +336,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "account",
|
"go.faq.a4.p2.accountLink": "account",
|
||||||
"go.faq.a4.p3": "Cancel any time.",
|
"go.faq.a4.p3": "Cancel any time.",
|
||||||
"go.faq.q5": "What about data and privacy?",
|
"go.faq.q5": "What about data and privacy?",
|
||||||
"go.faq.a5.model": "Model",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "Model training",
|
"The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Our providers follow a zero-retention policy and do not use your data for model training.",
|
||||||
"go.faq.a5.retention": "Data retention",
|
|
||||||
"go.faq.a5.retention30": "30 days",
|
|
||||||
"go.faq.a5.retention0": "0 days",
|
|
||||||
"go.faq.a5.used": "Used",
|
|
||||||
"go.faq.a5.notUsed": "Not used",
|
|
||||||
"go.faq.a5.noAgreement": "No agreement",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"ZDR disables important API features that depend on stored data, including the stateful Responses API, Files and Collections, and the Batch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"Abuse monitoring logs are generated for all API feature usage and retained for up to 30 days.",
|
|
||||||
"go.faq.a5.deepseekRetention":
|
|
||||||
"ZDR agreement is renewed monthly. The current agreement is valid through August 31, 2026.",
|
|
||||||
"go.faq.a5.learnMore": "Learn more",
|
|
||||||
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"Go models are hosted in the US. Providers follow a zero-retention policy and do not use your data for model training, with the",
|
"Go models are hosted in the US. Providers follow a zero-retention policy and do not use your data for model training, with the",
|
||||||
|
|
@ -364,7 +351,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "What is the difference between free models and Go?",
|
"go.faq.q9": "What is the difference between free models and Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go offers a curated model lineup with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash, and Hy3 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
||||||
"zen.api.error.modelNotSupported": "Model {{model}} is not supported",
|
"zen.api.error.modelNotSupported": "Model {{model}} is not supported",
|
||||||
|
|
@ -389,7 +376,7 @@ export const dict = {
|
||||||
"You have reached your monthly spending limit of ${{amount}}. Manage your limits here: {{membersUrl}}",
|
"You have reached your monthly spending limit of ${{amount}}. Manage your limits here: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "Model is disabled",
|
"zen.api.error.modelDisabled": "Model is disabled",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"The latest version of this model is only available hosted in China and requires explicit opt in: {{consoleGoUrl}}",
|
"This model is hosted in China. If you would like to use this model, enable it in your settings: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"Free promotion has ended for {{model}}. You can continue using the model by subscribing to OpenCode Go - {{link}}",
|
"Free promotion has ended for {{model}}. You can continue using the model by subscribing to OpenCode Go - {{link}}",
|
||||||
|
|
||||||
|
|
@ -681,7 +668,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 for your first month",
|
"workspace.lite.promo.price": "$5 for your first month",
|
||||||
"workspace.lite.promo.modelsTitle": "What's Included",
|
"workspace.lite.promo.modelsTitle": "What's Included",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"The plan is designed primarily for international users and provides stable global access. Pricing and usage limits may change as we learn from early usage and feedback.",
|
"The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Pricing and usage limits may change as we learn from early usage and feedback.",
|
||||||
"workspace.lite.promo.subscribe": "Subscribe to Go",
|
"workspace.lite.promo.subscribe": "Subscribe to Go",
|
||||||
"workspace.lite.promo.subscribing": "Redirecting...",
|
"workspace.lite.promo.subscribing": "Redirecting...",
|
||||||
"workspace.lite.promo.otherMethods": "Other payment methods",
|
"workspace.lite.promo.otherMethods": "Other payment methods",
|
||||||
|
|
|
||||||
|
|
@ -257,9 +257,9 @@ export const dict = {
|
||||||
"zen.privacy.exceptionsLink": "siguientes excepciones",
|
"zen.privacy.exceptionsLink": "siguientes excepciones",
|
||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
||||||
"go.banner.text": "GPT 5.6 Luna tiene límites de uso 2x mayores por tiempo limitado",
|
"go.banner.text": "Kimi K3 tiene límites de uso 2x mayores por tiempo limitado",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go comienza en $5 el primer mes, luego 10 $/mes, con límites de uso generosos y acceso fiable a modelos de programación líderes.",
|
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash y Hy3.",
|
||||||
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
||||||
|
|
@ -307,7 +307,8 @@ export const dict = {
|
||||||
"go.problem.item1": "Precios de suscripción de bajo coste",
|
"go.problem.item1": "Precios de suscripción de bajo coste",
|
||||||
"go.problem.item2": "Límites generosos y acceso fiable",
|
"go.problem.item2": "Límites generosos y acceso fiable",
|
||||||
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
||||||
"go.problem.item4": "Una selección de modelos probados para programación agéntica",
|
"go.problem.item4":
|
||||||
|
"Incluye Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash y Hy3",
|
||||||
"go.how.title": "Cómo funciona Go",
|
"go.how.title": "Cómo funciona Go",
|
||||||
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
||||||
"go.how.step1.title": "Crear una cuenta",
|
"go.how.step1.title": "Crear una cuenta",
|
||||||
|
|
@ -332,7 +333,7 @@ export const dict = {
|
||||||
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
||||||
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"No. Zen es de pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a una selección de modelos.",
|
"No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash y Hy3.",
|
||||||
"go.faq.q4": "¿Cuánto cuesta Go?",
|
"go.faq.q4": "¿Cuánto cuesta Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
||||||
|
|
@ -341,21 +342,8 @@ export const dict = {
|
||||||
"go.faq.a4.p2.accountLink": "cuenta",
|
"go.faq.a4.p2.accountLink": "cuenta",
|
||||||
"go.faq.a4.p3": "Cancela en cualquier momento.",
|
"go.faq.a4.p3": "Cancela en cualquier momento.",
|
||||||
"go.faq.q5": "¿Qué pasa con los datos y la privacidad?",
|
"go.faq.q5": "¿Qué pasa con los datos y la privacidad?",
|
||||||
"go.faq.a5.model": "Modelo",
|
"go.faq.a5.body":
|
||||||
"go.faq.a5.training": "Entrenamiento del modelo",
|
"El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., UE y Singapur para un acceso global estable. Nuestros proveedores siguen una política de retención cero y no utilizan tus datos para el entrenamiento de modelos.",
|
||||||
"go.faq.a5.retention": "Retención de datos",
|
|
||||||
"go.faq.a5.retention30": "30 días",
|
|
||||||
"go.faq.a5.retention0": "0 días",
|
|
||||||
"go.faq.a5.used": "Utilizado",
|
|
||||||
"go.faq.a5.notUsed": "No utilizado",
|
|
||||||
"go.faq.a5.noAgreement": "Sin acuerdo",
|
|
||||||
"go.faq.a5.grokRetention":
|
|
||||||
"ZDR deshabilita funciones importantes de la API que dependen de datos almacenados, incluidas la Responses API con estado, Files and Collections y la Batch API.",
|
|
||||||
"go.faq.a5.gptRetention":
|
|
||||||
"Se generan registros de supervisión de abusos para todo el uso de funciones de la API y se conservan durante un máximo de 30 días.",
|
|
||||||
"go.faq.a5.learnMore": "Más información",
|
|
||||||
"go.faq.a5.deepseekRetention":
|
|
||||||
"El acuerdo de ZDR se renueva mensualmente. El acuerdo actual es válido hasta el 31 de agosto de 2026.",
|
|
||||||
"go.faq.a5.beforeExceptions":
|
"go.faq.a5.beforeExceptions":
|
||||||
"Los modelos de Go están alojados en EE. UU. Los proveedores siguen una política de retención cero y no utilizan tus datos para el entrenamiento de modelos, con las",
|
"Los modelos de Go están alojados en EE. UU. Los proveedores siguen una política de retención cero y no utilizan tus datos para el entrenamiento de modelos, con las",
|
||||||
"go.faq.a5.exceptionsLink": "siguientes excepciones",
|
"go.faq.a5.exceptionsLink": "siguientes excepciones",
|
||||||
|
|
@ -369,7 +357,7 @@ export const dict = {
|
||||||
|
|
||||||
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Los modelos gratuitos incluyen Big Pickle y los modelos promocionales disponibles en ese momento, con una cuota de 200 solicitudes/día. Go ofrece una selección de modelos con cuotas de solicitudes más altas aplicadas en ventanas móviles (de 5 horas, semanales y mensuales), aproximadamente equivalentes a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (la cantidad real de solicitudes varía según el modelo y el uso).",
|
"Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash y Hy3 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
|
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
|
||||||
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
|
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
|
||||||
|
|
@ -394,7 +382,7 @@ export const dict = {
|
||||||
"Has alcanzado tu límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{membersUrl}}",
|
"Has alcanzado tu límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{membersUrl}}",
|
||||||
"zen.api.error.modelDisabled": "El modelo está deshabilitado",
|
"zen.api.error.modelDisabled": "El modelo está deshabilitado",
|
||||||
"zen.api.error.regionNotAllowed":
|
"zen.api.error.regionNotAllowed":
|
||||||
"La versión más reciente de este modelo solo está disponible alojada en China y requiere una aceptación explícita: {{consoleGoUrl}}",
|
"Este modelo está alojado en China. Si quieres usar este modelo, actívalo en tu configuración: {{consoleGoUrl}}",
|
||||||
"zen.api.error.trialEnded":
|
"zen.api.error.trialEnded":
|
||||||
"La promoción gratuita de {{model}} ha finalizado. Puedes seguir usando el modelo suscribiéndote a OpenCode Go - {{link}}",
|
"La promoción gratuita de {{model}} ha finalizado. Puedes seguir usando el modelo suscribiéndote a OpenCode Go - {{link}}",
|
||||||
|
|
||||||
|
|
@ -686,7 +674,7 @@ export const dict = {
|
||||||
"workspace.lite.promo.price": "$5 el primer mes",
|
"workspace.lite.promo.price": "$5 el primer mes",
|
||||||
"workspace.lite.promo.modelsTitle": "Qué incluye",
|
"workspace.lite.promo.modelsTitle": "Qué incluye",
|
||||||
"workspace.lite.promo.footer":
|
"workspace.lite.promo.footer":
|
||||||
"El plan está diseñado principalmente para usuarios internacionales y ofrece un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y de los comentarios recibidos.",
|
"El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y los comentarios.",
|
||||||
"workspace.lite.promo.subscribe": "Suscribirse a Go",
|
"workspace.lite.promo.subscribe": "Suscribirse a Go",
|
||||||
"workspace.lite.promo.subscribing": "Redirigiendo...",
|
"workspace.lite.promo.subscribing": "Redirigiendo...",
|
||||||
"workspace.lite.promo.otherMethods": "Otros métodos de pago",
|
"workspace.lite.promo.otherMethods": "Otros métodos de pago",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue