mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-12 11:54:29 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa97475ee8 | ||
|
|
0eaeb4588e | ||
|
|
1413d77b1f | ||
|
|
624dd94b5d | ||
|
|
d86f24b6b3 | ||
|
|
03de51bd3c | ||
|
|
8f9742d988 | ||
|
|
f6e7aefa72 | ||
|
|
e269788a8f | ||
|
|
66780195dc | ||
|
|
ad2087094d | ||
|
|
5bdf1c4b96 | ||
|
|
135f8ffb2a | ||
|
|
bf5a01edd9 | ||
|
|
81ca2df6ad | ||
|
|
aea68c386a | ||
|
|
8eea53a41e | ||
|
|
3befd0c6c5 | ||
|
|
8577eb8ec9 | ||
|
|
c856f875a1 | ||
|
|
264dd213f9 | ||
|
|
125727d09c | ||
|
|
8c7b35ad05 | ||
|
|
e2a33f75e1 | ||
|
|
006d673ed2 | ||
|
|
6b4d617df0 | ||
|
|
e3471526f4 | ||
|
|
6b30e0b752 | ||
|
|
fbabce1125 | ||
|
|
8f56ed5b85 | ||
|
|
81b5a6a08b | ||
|
|
94cb6390aa | ||
|
|
42bea5d297 | ||
|
|
f252e3234c | ||
|
|
eef3ae3e1f | ||
|
|
fc88dde63f | ||
|
|
4619e9d183 |
2
.github/VOUCHED.td
vendored
2
.github/VOUCHED.td
vendored
@@ -8,6 +8,8 @@
|
||||
# - Denounce with minus prefix: -username or -platform:username.
|
||||
# - Optional details after a space following the handle.
|
||||
adamdotdevin
|
||||
ariane-emory
|
||||
-florianleibert
|
||||
fwang
|
||||
iamdavidhill
|
||||
jayair
|
||||
|
||||
11
.github/workflows/docs-locale-sync.yml
vendored
11
.github/workflows/docs-locale-sync.yml
vendored
@@ -64,10 +64,13 @@ jobs:
|
||||
|
||||
Requirements:
|
||||
1. Update all relevant locale docs under packages/web/src/content/docs/<locale>/ so they reflect these English page changes.
|
||||
2. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
||||
3. Keep locale docs structure aligned with their corresponding English pages.
|
||||
4. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||
5. If no locale updates are needed, make no changes.
|
||||
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
|
||||
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
|
||||
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
|
||||
5. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
||||
6. Keep locale docs structure aligned with their corresponding English pages.
|
||||
7. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||
8. If no locale updates are needed, make no changes.
|
||||
|
||||
- name: Commit and push locale docs updates
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
|
||||
54
.github/workflows/sign-cli.yml
vendored
Normal file
54
.github/workflows/sign-cli.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: sign-cli
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- brendan/desktop-signpath
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
sign-cli:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
./packages/opencode/script/build.ts
|
||||
|
||||
- name: Upload unsigned Windows CLI
|
||||
id: upload_unsigned_windows_cli
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unsigned-opencode-windows-cli
|
||||
path: packages/opencode/dist/opencode-windows-x64/bin/opencode.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Submit SignPath signing request
|
||||
id: submit_signpath_signing_request
|
||||
uses: signpath/github-action-submit-signing-request@v1
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_KEY }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
|
||||
artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
||||
github-artifact-id: ${{ steps.upload_unsigned_windows_cli.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: signed-opencode-cli
|
||||
|
||||
- name: Upload signed Windows CLI
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-opencode-windows-cli
|
||||
path: signed-opencode-cli/*.exe
|
||||
if-no-files-found: error
|
||||
@@ -32,6 +32,9 @@ description: Use this when you are working on file operations like reading, writ
|
||||
- Decode tool stderr with `Bun.readableStreamToText`.
|
||||
- For large writes, use `Bun.write(Bun.file(path), text)`.
|
||||
|
||||
NOTE: Bun.file(...).exists() will return `false` if the value is a directory.
|
||||
Use Filesystem.exists(...) instead if path can be file or directory
|
||||
|
||||
## Quick checklist
|
||||
|
||||
- Use Bun APIs first.
|
||||
|
||||
7
.signpath/policies/opencode/test-signing.yml
Normal file
7
.signpath/policies/opencode/test-signing.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
github-policies:
|
||||
runners:
|
||||
allowed_groups:
|
||||
- "blacksmith runners 01kbd5v56sg8tz7rea39b7ygpt"
|
||||
build:
|
||||
disallow_reruns: false
|
||||
branch_rulesets:
|
||||
56
bun.lock
56
bun.lock
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -107,7 +107,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -134,7 +134,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -158,7 +158,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -182,7 +182,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -215,7 +215,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -244,7 +244,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -260,7 +260,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -301,8 +301,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||
"@opentui/core": "0.1.77",
|
||||
"@opentui/solid": "0.1.77",
|
||||
"@opentui/core": "0.1.79",
|
||||
"@opentui/solid": "0.1.79",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -366,7 +366,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -386,7 +386,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -397,7 +397,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -410,7 +410,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -452,7 +452,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -463,7 +463,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -522,7 +522,7 @@
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/bun": "1.3.8",
|
||||
"@types/bun": "1.3.9",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "22.13.9",
|
||||
"@types/semver": "7.7.1",
|
||||
@@ -1279,21 +1279,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.77", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.77", "@opentui/core-darwin-x64": "0.1.77", "@opentui/core-linux-arm64": "0.1.77", "@opentui/core-linux-x64": "0.1.77", "@opentui/core-win32-arm64": "0.1.77", "@opentui/core-win32-x64": "0.1.77", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-lE3kabm6jdqK3AuBq+O0zZrXdxt6ulmibTc57sf+AsPny6cmwYHnWI4wD6hcreFiYoQVNVvdiJchVgPtowMlEg=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.79", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.79", "@opentui/core-darwin-x64": "0.1.79", "@opentui/core-linux-arm64": "0.1.79", "@opentui/core-linux-x64": "0.1.79", "@opentui/core-win32-arm64": "0.1.79", "@opentui/core-win32-x64": "0.1.79", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-job/t09w8A/aHb/WuaVbimu5fIffyN+PCuVO5cYhXEg/NkOkC/WdFi80B8bwncR/DBPyLAh6oJ3EG86grOVo5g=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.77", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SNqmygCMEsPCW7xWjzCZ5caBf36xaprwVdAnFijGDOuIzLA4iaDa6um8cj3TJh7awenN3NTRsuRc7OuH42UH+g=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.79", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kgsGniV+DM5G1P3GideyJhvfnthNKcVCAm2mPTIr9InQ3L0gS/Feh7zgwOS/jxDvdlQbOWGKMk2Z3JApeC1MLw=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.77", "", { "os": "darwin", "cpu": "x64" }, "sha512-/8fsa03swEHTQt/9NrGm98kemlU+VuTURI/OFZiH53vPDRrOYIYoa4Jyga/H7ZMcG+iFhkq97zIe+0Kw95LGmA=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.79", "", { "os": "darwin", "cpu": "x64" }, "sha512-OpyAmFqAAKQ2CeFmf/oLWcNksmP6Ryx/3R5dbKXThOudMCeQvfvInJTRbc2jTn9VFpf+Qj4BgHkJg1h90tf/EA=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.77", "", { "os": "linux", "cpu": "arm64" }, "sha512-QfUXZJPc69OvqoMu+AlLgjqXrwu4IeqcBuUWYMuH8nPTeLsVUc3CBbXdV2lv9UDxWzxzrxdS4ALPaxvmEv9lsQ=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.79", "", { "os": "linux", "cpu": "arm64" }, "sha512-DCa5YaknS4bWhFt8TMEGH+qmTinyzuY8hoZbO4crtWXAxofPP7Pas76Cwxlvis/PyLffA+pPxAl1l5sUZpsvqw=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.77", "", { "os": "linux", "cpu": "x64" }, "sha512-Kmfx0yUKnPj67AoXYIgL7qQo0QVsUG5Iw8aRtv6XFzXqa5SzBPhaKkKZ9yHPjOmTalZquUs+9zcCRNKpYYuL7A=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.79", "", { "os": "linux", "cpu": "x64" }, "sha512-V6xjvFfHh3NGvsuuDae1KHPRZXHMEE8XL0A/GM6v4I4OCC23kDmkK60Vn6OptQwAzwwbz0X0IX+Ut/GQU9qGgA=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.77", "", { "os": "win32", "cpu": "arm64" }, "sha512-HGTscPXc7gdd23Nh1DbzUNjog1I+5IZp95XPtLftGTpjrWs60VcetXcyJqK2rQcXNxewJK5yDyaa5QyMjfEhCQ=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.79", "", { "os": "win32", "cpu": "arm64" }, "sha512-sPRKnVzOdT5szI59tte7pxwwkYA+07EQN+6miFAvkFuiLmRUngONUD8HVjL7nCnxcPFqxaU4Rvl1y40ST86g8g=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.77", "", { "os": "win32", "cpu": "x64" }, "sha512-c7GijsbvVgnlzd2murIbwuwrGbcv76KdUw6WlVv7a0vex50z6xJCpv1keGzpe0QfxrZ/6fFEFX7JnwGLno0wjA=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.79", "", { "os": "win32", "cpu": "x64" }, "sha512-vmQcFTvKf9fqajnDtgU6/uAsiTGwx8//khqHVBmiTEXUsiT792Ki9l8sgNughbuldqG5iZOiF6IaAWU1H67UpA=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.77", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.77", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-JY+hUbXVV+XCk6bC8dvcwawWCEmC3Gid6GDs23AJWBgHZ3TU2kRKrgwTdltm45DOq2cZXrYCt690/yE8bP+Gxg=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.79", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.79", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-c5+0jexKxb8GwRDDkQ/U6isZZqClAzHccXmYiLYmSnqdoQQp2lIGHLartL+K8lfIQrsKClzP2ZHumN6nexRfRg=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -1853,7 +1853,7 @@
|
||||
|
||||
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
@@ -2181,7 +2181,7 @@
|
||||
|
||||
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||
|
||||
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"keep": {
|
||||
"days": true,
|
||||
"amount": 14
|
||||
},
|
||||
"auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
|
||||
"files": [
|
||||
{
|
||||
"date": 1759827172859,
|
||||
"name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
|
||||
"hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
|
||||
}
|
||||
],
|
||||
"hashType": "sha256"
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:52.879"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:52.880"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:56.191"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:56.192"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:59.267"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:52:59.268"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:20.276"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:20.277"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:30.838"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:30.839"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:42.452"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:42.452"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:46.499"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:53:46.500"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:02.295"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:02.295"}
|
||||
{"arguments":{"url":"https://google.com"},"level":"debug","message":"Tool call received","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:37.150","tool":"puppeteer_navigate"}
|
||||
{"0":"n","1":"p","2":"x","level":"info","message":"Launching browser with config:","service":"mcp-puppeteer","timestamp":"2025-10-07 04:54:37.150"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 04:55:08.488"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 04:55:08.489"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:11.815"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:11.816"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:21.934"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:21.935"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:32.544"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:32.544"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:41.154"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:41.155"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:55.426"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:23:55.427"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:15.715"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:15.716"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:25.063"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:25.064"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:48.567"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:24:48.568"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 05:25:08.937"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 05:25:08.938"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:37.120"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:37.121"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:52.490"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:38:52.491"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:39:25.524"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:39:25.525"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:40:57.126"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:40:57.127"}
|
||||
{"level":"info","message":"Starting MCP server","service":"mcp-puppeteer","timestamp":"2025-10-07 22:42:24.175"}
|
||||
{"level":"info","message":"MCP server started successfully","service":"mcp-puppeteer","timestamp":"2025-10-07 22:42:24.176"}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-cvRBvHRuunNjF07c4GVHl5rRgoTn1qfI/HdJWtOV63M=",
|
||||
"aarch64-linux": "sha256-DJUI4pMZ7wQTnyOiuDHALmZz7FZtrTbzRzCuNOShmWE=",
|
||||
"aarch64-darwin": "sha256-JnkqDwuC7lNsjafV+jOGfvs8K1xC8rk5CTOW+spjiCA=",
|
||||
"x86_64-darwin": "sha256-GBeTqq2vDn/mXplYNglrAT2xajjFVzB4ATHnMS0j7z4="
|
||||
"x86_64-linux": "sha256-pp2gb4nxiIT3VltB6Xli2wZPH32JfnMsI+BbihyU1+E=",
|
||||
"aarch64-linux": "sha256-hJwxhBICZz/pbIxQsF/sIpZTlFIgLpcAyF44O8wxMdU=",
|
||||
"aarch64-darwin": "sha256-DPONXP52XOg/ApdSnLp32a+K5XCOnDGhbTUto2Rme0g=",
|
||||
"x86_64-darwin": "sha256-KX1h5LRJSgthpbOPqWlbM/sPf8cvQrdRJvxtrz/FzBQ="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "AI-powered development tool",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.8",
|
||||
"packageManager": "bun@1.3.9",
|
||||
"scripts": {
|
||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||
"dev:desktop": "bun --cwd packages/desktop tauri dev",
|
||||
@@ -23,7 +23,7 @@
|
||||
"packages/slack"
|
||||
],
|
||||
"catalog": {
|
||||
"@types/bun": "1.3.8",
|
||||
"@types/bun": "1.3.9",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"ulid": "3.0.1",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, withSession } from "../actions"
|
||||
import { defocus, openSidebar, withSession } from "../actions"
|
||||
import { promptSelector } from "../selectors"
|
||||
import { modKey } from "../utils"
|
||||
|
||||
test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
@@ -40,3 +41,84 @@ test("titlebar back/forward navigates between sessions", async ({ page, slug, sd
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("titlebar forward is cleared after branching history from sidebar", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const stamp = Date.now()
|
||||
|
||||
await withSession(sdk, `e2e titlebar history a ${stamp}`, async (a) => {
|
||||
await withSession(sdk, `e2e titlebar history b ${stamp}`, async (b) => {
|
||||
await withSession(sdk, `e2e titlebar history c ${stamp}`, async (c) => {
|
||||
await gotoSession(a.id)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const second = page.locator(`[data-session-id="${b.id}"] a`).first()
|
||||
await expect(second).toBeVisible()
|
||||
await second.scrollIntoViewIfNeeded()
|
||||
await second.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${b.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
const back = page.getByRole("button", { name: "Back" })
|
||||
const forward = page.getByRole("button", { name: "Forward" })
|
||||
|
||||
await expect(back).toBeVisible()
|
||||
await expect(back).toBeEnabled()
|
||||
await back.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${a.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const third = page.locator(`[data-session-id="${c.id}"] a`).first()
|
||||
await expect(third).toBeVisible()
|
||||
await third.scrollIntoViewIfNeeded()
|
||||
await third.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${c.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await expect(forward).toBeVisible()
|
||||
await expect(forward).toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("keyboard shortcuts navigate titlebar history", async ({ page, slug, sdk, gotoSession }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const stamp = Date.now()
|
||||
|
||||
await withSession(sdk, `e2e titlebar shortcuts 1 ${stamp}`, async (one) => {
|
||||
await withSession(sdk, `e2e titlebar shortcuts 2 ${stamp}`, async (two) => {
|
||||
await gotoSession(one.id)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
|
||||
await expect(link).toBeVisible()
|
||||
await link.scrollIntoViewIfNeeded()
|
||||
await link.click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await defocus(page)
|
||||
await page.keyboard.press(`${modKey}+[`)
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${one.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
|
||||
await defocus(page)
|
||||
await page.keyboard.press(`${modKey}+]`)
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,37 +1,49 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
|
||||
test.skip("file tree can expand folders and open a file", async ({ page, gotoSession }) => {
|
||||
test("file tree can expand folders and open a file", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const toggle = page.getByRole("button", { name: "Toggle file tree" })
|
||||
const treeTabs = page.locator('[data-component="tabs"][data-variant="pill"][data-scope="filetree"]')
|
||||
const panel = page.locator("#file-tree-panel")
|
||||
const treeTabs = panel.locator('[data-component="tabs"][data-variant="pill"][data-scope="filetree"]')
|
||||
|
||||
await expect(toggle).toBeVisible()
|
||||
if ((await toggle.getAttribute("aria-expanded")) !== "true") await toggle.click()
|
||||
await expect(toggle).toHaveAttribute("aria-expanded", "true")
|
||||
await expect(panel).toBeVisible()
|
||||
await expect(treeTabs).toBeVisible()
|
||||
|
||||
await treeTabs.locator('[data-slot="tabs-trigger"]').nth(1).click()
|
||||
const allTab = treeTabs.getByRole("tab", { name: /^all files$/i })
|
||||
await expect(allTab).toBeVisible()
|
||||
await allTab.click()
|
||||
await expect(allTab).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
const node = (name: string) => treeTabs.getByRole("button", { name, exact: true })
|
||||
const tree = treeTabs.locator('[data-slot="tabs-content"]:not([hidden])')
|
||||
await expect(tree).toBeVisible()
|
||||
|
||||
await expect(node("packages")).toBeVisible()
|
||||
await node("packages").click()
|
||||
const expand = async (name: string) => {
|
||||
const folder = tree.getByRole("button", { name, exact: true }).first()
|
||||
await expect(folder).toBeVisible()
|
||||
await expect(folder).toHaveAttribute("aria-expanded", /true|false/)
|
||||
if ((await folder.getAttribute("aria-expanded")) === "false") await folder.click()
|
||||
await expect(folder).toHaveAttribute("aria-expanded", "true")
|
||||
}
|
||||
|
||||
await expect(node("app")).toBeVisible()
|
||||
await node("app").click()
|
||||
await expand("packages")
|
||||
await expand("app")
|
||||
await expand("src")
|
||||
await expand("components")
|
||||
|
||||
await expect(node("src")).toBeVisible()
|
||||
await node("src").click()
|
||||
|
||||
await expect(node("components")).toBeVisible()
|
||||
await node("components").click()
|
||||
|
||||
await expect(node("file-tree.tsx")).toBeVisible()
|
||||
await node("file-tree.tsx").click()
|
||||
const file = tree.getByRole("button", { name: "file-tree.tsx", exact: true }).first()
|
||||
await expect(file).toBeVisible()
|
||||
await file.click()
|
||||
|
||||
const tab = page.getByRole("tab", { name: "file-tree.tsx" })
|
||||
await expect(tab).toBeVisible()
|
||||
await tab.click()
|
||||
await expect(tab).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
const code = page.locator('[data-component="code"]').first()
|
||||
await expect(code.getByText("export default function FileTree")).toBeVisible()
|
||||
await expect(code).toBeVisible()
|
||||
await expect(code).toContainText("export default function FileTree")
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem } from "../actions"
|
||||
import { projectCloseHoverSelector, projectCloseMenuSelector, projectSwitchSelector } from "../selectors"
|
||||
import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem, openProjectMenu } from "../actions"
|
||||
import { projectCloseHoverSelector, projectSwitchSelector } from "../selectors"
|
||||
import { dirSlug } from "../utils"
|
||||
|
||||
test("can close a project via hover card close button", async ({ page, withProject }) => {
|
||||
@@ -31,16 +31,15 @@ test("can close a project via hover card close button", async ({ page, withProje
|
||||
}
|
||||
})
|
||||
|
||||
test("can close a project via project header more options menu", async ({ page, withProject }) => {
|
||||
test("closing active project navigates to another open project", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const other = await createTestProject()
|
||||
const otherName = other.split("/").pop() ?? other
|
||||
const otherSlug = dirSlug(other)
|
||||
|
||||
try {
|
||||
await withProject(
|
||||
async () => {
|
||||
async ({ slug }) => {
|
||||
await openSidebar(page)
|
||||
|
||||
const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
|
||||
@@ -49,21 +48,20 @@ test("can close a project via project header more options menu", async ({ page,
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
|
||||
|
||||
const header = page
|
||||
.locator(".group\\/project")
|
||||
.filter({ has: page.locator(`[data-action="project-menu"][data-project="${otherSlug}"]`) })
|
||||
.first()
|
||||
await expect(header).toContainText(otherName)
|
||||
|
||||
const trigger = header.locator(`[data-action="project-menu"][data-project="${otherSlug}"]`).first()
|
||||
await expect(trigger).toHaveCount(1)
|
||||
await trigger.focus()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
const menu = page.locator('[data-component="dropdown-menu-content"]').first()
|
||||
await expect(menu).toBeVisible({ timeout: 10_000 })
|
||||
const menu = await openProjectMenu(page, otherSlug)
|
||||
|
||||
await clickMenuItem(menu, /^Close$/i, { force: true })
|
||||
|
||||
await expect
|
||||
.poll(() => {
|
||||
const pathname = new URL(page.url()).pathname
|
||||
if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
|
||||
if (pathname === "/") return "home"
|
||||
return ""
|
||||
})
|
||||
.toMatch(/^(project|home)$/)
|
||||
|
||||
await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`))
|
||||
await expect(otherButton).toHaveCount(0)
|
||||
},
|
||||
{ extra: [other] },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import type { Page } from "@playwright/test"
|
||||
|
||||
@@ -10,11 +11,18 @@ import {
|
||||
cleanupTestProject,
|
||||
clickMenuItem,
|
||||
confirmDialog,
|
||||
openProjectMenu,
|
||||
openSidebar,
|
||||
openWorkspaceMenu,
|
||||
setWorkspacesEnabled,
|
||||
} from "../actions"
|
||||
import { inlineInputSelector, workspaceItemSelector } from "../selectors"
|
||||
import {
|
||||
inlineInputSelector,
|
||||
projectSwitchSelector,
|
||||
projectWorkspacesToggleSelector,
|
||||
workspaceItemSelector,
|
||||
} from "../selectors"
|
||||
import { dirSlug } from "../utils"
|
||||
|
||||
function slugFromUrl(url: string) {
|
||||
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
|
||||
@@ -126,6 +134,40 @@ test("can create a workspace", async ({ page, withProject }) => {
|
||||
})
|
||||
})
|
||||
|
||||
test("non-git projects keep workspace mode disabled", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
const nonGit = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-project-nongit-"))
|
||||
const nonGitSlug = dirSlug(nonGit)
|
||||
|
||||
await fs.writeFile(path.join(nonGit, "README.md"), "# e2e nongit\n")
|
||||
|
||||
try {
|
||||
await withProject(
|
||||
async () => {
|
||||
await openSidebar(page)
|
||||
|
||||
const nonGitButton = page.locator(projectSwitchSelector(nonGitSlug)).first()
|
||||
await expect(nonGitButton).toBeVisible()
|
||||
await nonGitButton.click()
|
||||
await expect(page).toHaveURL(new RegExp(`/${nonGitSlug}/session`))
|
||||
|
||||
const menu = await openProjectMenu(page, nonGitSlug)
|
||||
const toggle = menu.locator(projectWorkspacesToggleSelector(nonGitSlug)).first()
|
||||
|
||||
await expect(toggle).toBeVisible()
|
||||
await expect(toggle).toBeDisabled()
|
||||
|
||||
await expect(menu.getByRole("menuitem", { name: "New workspace" })).toHaveCount(0)
|
||||
await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
|
||||
},
|
||||
{ extra: [nonGit] },
|
||||
)
|
||||
} finally {
|
||||
await cleanupTestProject(nonGit)
|
||||
}
|
||||
})
|
||||
|
||||
test("can rename a workspace", async ({ page, withProject }) => {
|
||||
await page.setViewportSize({ width: 1400, height: 800 })
|
||||
|
||||
|
||||
@@ -23,10 +23,15 @@ async function seedConversation(input: {
|
||||
const messages = await input.sdk.session
|
||||
.messages({ sessionID: input.sessionID, limit: 50 })
|
||||
.then((r) => r.data ?? [])
|
||||
const users = messages.filter((m) => m.info.role === "user")
|
||||
const users = messages.filter(
|
||||
(m) =>
|
||||
m.info.role === "user" &&
|
||||
m.parts.filter((p) => p.type === "text").some((p) => p.text.includes(input.token)),
|
||||
)
|
||||
if (users.length === 0) return false
|
||||
|
||||
const user = users.reduce((acc, item) => (item.info.id > acc.info.id ? item : acc))
|
||||
const user = users[users.length - 1]
|
||||
if (!user) return false
|
||||
userMessageID = user.info.id
|
||||
|
||||
const assistantText = messages
|
||||
@@ -124,3 +129,107 @@ test("slash redo clears revert and restores latest state", async ({ page, withPr
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("slash undo/redo traverses multi-step revert stack", async ({ page, withProject }) => {
|
||||
test.setTimeout(120_000)
|
||||
|
||||
const firstToken = `undo_redo_first_${Date.now()}`
|
||||
const secondToken = `undo_redo_second_${Date.now()}`
|
||||
|
||||
await withProject(async (project) => {
|
||||
const sdk = createSdk(project.directory)
|
||||
|
||||
await withSession(sdk, `e2e undo redo stack ${Date.now()}`, async (session) => {
|
||||
await project.gotoSession(session.id)
|
||||
|
||||
const first = await seedConversation({
|
||||
page,
|
||||
sdk,
|
||||
sessionID: session.id,
|
||||
token: firstToken,
|
||||
})
|
||||
const second = await seedConversation({
|
||||
page,
|
||||
sdk,
|
||||
sessionID: session.id,
|
||||
token: secondToken,
|
||||
})
|
||||
|
||||
expect(first.userMessageID).not.toBe(second.userMessageID)
|
||||
|
||||
const firstMessage = page.locator(`[data-message-id="${first.userMessageID}"]`)
|
||||
const secondMessage = page.locator(`[data-message-id="${second.userMessageID}"]`)
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage.first()).toBeVisible()
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/undo")
|
||||
|
||||
const undo = page.locator('[data-slash-id="session.undo"]').first()
|
||||
await expect(undo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(second.userMessageID)
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/undo")
|
||||
await expect(undo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(first.userMessageID)
|
||||
|
||||
await expect(firstMessage).toHaveCount(0)
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/redo")
|
||||
|
||||
const redo = page.locator('[data-slash-id="session.redo"]').first()
|
||||
await expect(redo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBe(second.userMessageID)
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage).toHaveCount(0)
|
||||
|
||||
await second.prompt.click()
|
||||
await page.keyboard.press(`${modKey}+A`)
|
||||
await page.keyboard.press("Backspace")
|
||||
await page.keyboard.type("/redo")
|
||||
await expect(redo).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
|
||||
await expect
|
||||
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
||||
timeout: 30_000,
|
||||
})
|
||||
.toBeUndefined()
|
||||
|
||||
await expect(firstMessage.first()).toBeVisible()
|
||||
await expect(secondMessage.first()).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle"))
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle")).first()
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
const initialKeybind = await keybindButton.textContent()
|
||||
@@ -51,6 +51,40 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
|
||||
expect(finalClosed).toBe(initiallyClosed)
|
||||
})
|
||||
|
||||
test("sidebar toggle keybind guards against shortcut conflicts", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("sidebar.toggle"))
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
const initialKeybind = await keybindButton.textContent()
|
||||
expect(initialKeybind).toContain("B")
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toHaveText(/press/i)
|
||||
|
||||
await page.keyboard.press(`${modKey}+Shift+KeyP`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const toast = page.locator('[data-component="toast"]').last()
|
||||
await expect(toast).toBeVisible()
|
||||
await expect(toast).toContainText(/already/i)
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toContainText("B")
|
||||
|
||||
const stored = await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("settings.v3")
|
||||
return raw ? JSON.parse(raw) : null
|
||||
})
|
||||
expect(stored?.keybinds?.["sidebar.toggle"]).toBeUndefined()
|
||||
|
||||
await closeDialog(page, dialog)
|
||||
})
|
||||
|
||||
test("resetting all keybinds to defaults works", async ({ page, gotoSession }) => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ keybinds: { "sidebar.toggle": "mod+shift+x" } }))
|
||||
@@ -277,6 +311,44 @@ test("changing terminal toggle keybind works", async ({ page, gotoSession }) =>
|
||||
await expect(terminal).not.toBeVisible()
|
||||
})
|
||||
|
||||
test("terminal toggle keybind persists after reload", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
await dialog.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
|
||||
const keybindButton = dialog.locator(keybindButtonSelector("terminal.toggle"))
|
||||
await expect(keybindButton).toBeVisible()
|
||||
|
||||
await keybindButton.click()
|
||||
await expect(keybindButton).toHaveText(/press/i)
|
||||
|
||||
await page.keyboard.press(`${modKey}+Shift+KeyY`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(keybindButton).toContainText("Y")
|
||||
await closeDialog(page, dialog)
|
||||
|
||||
await page.reload()
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("settings.v3")
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw)
|
||||
return parsed?.keybinds?.["terminal.toggle"]
|
||||
})
|
||||
})
|
||||
.toBe("mod+shift+y")
|
||||
|
||||
const reloaded = await openSettings(page)
|
||||
await reloaded.getByRole("tab", { name: "Shortcuts" }).click()
|
||||
const reloadedKeybind = reloaded.locator(keybindButtonSelector("terminal.toggle")).first()
|
||||
await expect(reloadedKeybind).toContainText("Y")
|
||||
await closeDialog(page, reloaded)
|
||||
})
|
||||
|
||||
test("changing command palette keybind works", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
settingsNotificationsPermissionsSelector,
|
||||
settingsReleaseNotesSelector,
|
||||
settingsSoundsAgentSelector,
|
||||
settingsSoundsErrorsSelector,
|
||||
settingsSoundsPermissionsSelector,
|
||||
settingsThemeSelector,
|
||||
settingsUpdatesStartupSelector,
|
||||
} from "../selectors"
|
||||
@@ -139,6 +141,105 @@ test("changing font persists in localStorage and updates CSS variable", async ({
|
||||
expect(newFontFamily).not.toBe(initialFontFamily)
|
||||
})
|
||||
|
||||
test("color scheme and font rehydrate after reload", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
|
||||
const colorSchemeSelect = dialog.locator(settingsColorSchemeSelector)
|
||||
await expect(colorSchemeSelect).toBeVisible()
|
||||
await colorSchemeSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
|
||||
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
||||
|
||||
const fontSelect = dialog.locator(settingsFontSelector)
|
||||
await expect(fontSelect).toBeVisible()
|
||||
|
||||
const initialFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
|
||||
const initialSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const currentFont =
|
||||
(await fontSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await fontSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
|
||||
const fontItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await fontItems.count()).toBeGreaterThan(1)
|
||||
|
||||
if (currentFont) {
|
||||
await fontItems.filter({ hasNotText: currentFont }).first().click()
|
||||
}
|
||||
if (!currentFont) {
|
||||
await fontItems.nth(1).click()
|
||||
}
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
appearance: {
|
||||
font: expect.any(String),
|
||||
},
|
||||
})
|
||||
|
||||
const updatedSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const updatedFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
expect(updatedFontFamily).not.toBe(initialFontFamily)
|
||||
expect(updatedSettings?.appearance?.font).not.toBe(initialSettings?.appearance?.font)
|
||||
|
||||
await closeDialog(page, dialog)
|
||||
await page.reload()
|
||||
|
||||
await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
appearance: {
|
||||
font: updatedSettings?.appearance?.font,
|
||||
},
|
||||
})
|
||||
|
||||
const rehydratedSettings = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
})
|
||||
.not.toBe(initialFontFamily)
|
||||
|
||||
const rehydratedFontFamily = await page.evaluate(() => {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim()
|
||||
})
|
||||
expect(rehydratedFontFamily).not.toBe(initialFontFamily)
|
||||
expect(rehydratedSettings?.appearance?.font).toBe(updatedSettings?.appearance?.font)
|
||||
})
|
||||
|
||||
test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
@@ -234,6 +335,67 @@ test("changing sound agent selection persists in localStorage", async ({ page, g
|
||||
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
|
||||
})
|
||||
|
||||
test("changing permissions and errors sounds updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
const permissionsSelect = dialog.locator(settingsSoundsPermissionsSelector)
|
||||
const errorsSelect = dialog.locator(settingsSoundsErrorsSelector)
|
||||
await expect(permissionsSelect).toBeVisible()
|
||||
await expect(errorsSelect).toBeVisible()
|
||||
|
||||
const initial = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
const permissionsCurrent =
|
||||
(await permissionsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await permissionsSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
const permissionItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await permissionItems.count()).toBeGreaterThan(1)
|
||||
if (permissionsCurrent) {
|
||||
await permissionItems.filter({ hasNotText: permissionsCurrent }).first().click()
|
||||
}
|
||||
if (!permissionsCurrent) {
|
||||
await permissionItems.nth(1).click()
|
||||
}
|
||||
|
||||
const errorsCurrent =
|
||||
(await errorsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
|
||||
await errorsSelect.locator('[data-slot="select-select-trigger"]').click()
|
||||
const errorItems = page.locator('[data-slot="select-select-item"]')
|
||||
expect(await errorItems.count()).toBeGreaterThan(1)
|
||||
if (errorsCurrent) {
|
||||
await errorItems.filter({ hasNotText: errorsCurrent }).first().click()
|
||||
}
|
||||
if (!errorsCurrent) {
|
||||
await errorItems.nth(1).click()
|
||||
}
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
})
|
||||
.toMatchObject({
|
||||
sounds: {
|
||||
permissions: expect.any(String),
|
||||
errors: expect.any(String),
|
||||
},
|
||||
})
|
||||
|
||||
const stored = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
return raw ? JSON.parse(raw) : null
|
||||
}, settingsKey)
|
||||
|
||||
expect(stored?.sounds?.permissions).not.toBe(initial?.sounds?.permissions)
|
||||
expect(stored?.sounds?.errors).not.toBe(initial?.sounds?.errors)
|
||||
})
|
||||
|
||||
test("toggling updates startup switch updates localStorage", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, toggleSidebar } from "../actions"
|
||||
import { openSidebar, toggleSidebar, withSession } from "../actions"
|
||||
|
||||
test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
@@ -12,3 +12,26 @@ test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => {
|
||||
await toggleSidebar(page)
|
||||
await expect(page.locator("main")).not.toHaveClass(/xl:border-l/)
|
||||
})
|
||||
|
||||
test("sidebar collapsed state persists across navigation and reload", async ({ page, sdk, gotoSession }) => {
|
||||
await withSession(sdk, "sidebar persist session 1", async (session1) => {
|
||||
await withSession(sdk, "sidebar persist session 2", async (session2) => {
|
||||
await gotoSession(session1.id)
|
||||
|
||||
await openSidebar(page)
|
||||
await toggleSidebar(page)
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
await gotoSession(session2.id)
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
await page.reload()
|
||||
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
|
||||
|
||||
const opened = await page.evaluate(
|
||||
() => JSON.parse(localStorage.getItem("opencode.global.dat:layout") ?? "{}").sidebar?.opened,
|
||||
)
|
||||
await expect(opened).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -43,7 +43,7 @@ function UiI18nBridge(props: ParentProps) {
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[] }
|
||||
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[]; wsl?: boolean }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { onCleanup, onMount } from "solid-js"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { usePrompt, type ContentPart, type ImageAttachmentPart } from "@/context/prompt"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { uuid } from "@/utils/uuid"
|
||||
import { getCursorPosition } from "./editor-dom"
|
||||
|
||||
export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
|
||||
@@ -31,7 +32,7 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
||||
const dataUrl = reader.result as string
|
||||
const attachment: ImageAttachmentPart = {
|
||||
type: "image",
|
||||
id: crypto.randomUUID?.() ?? Math.random().toString(16).slice(2),
|
||||
id: uuid(),
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
dataUrl,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
||||
import { createEffect, createMemo, onCleanup, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Portal } from "solid-js/web"
|
||||
import { useParams } from "@solidjs/router"
|
||||
@@ -18,7 +18,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { AppIcon } from "@opencode-ai/ui/app-icon"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||
import { Popover } from "@opencode-ai/ui/popover"
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
@@ -168,7 +167,6 @@ export function SessionHeader() {
|
||||
|
||||
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
|
||||
const [menu, setMenu] = createStore({ open: false })
|
||||
const [openRequest, setOpenRequest] = createStore({ app: undefined as OpenApp | undefined, version: 0 })
|
||||
|
||||
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
|
||||
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
|
||||
@@ -181,32 +179,20 @@ export function SessionHeader() {
|
||||
setPrefs("app", options()[0]?.id ?? "finder")
|
||||
})
|
||||
|
||||
const [openTask] = createResource(
|
||||
() => openRequest.app && openRequest.version,
|
||||
async () => {
|
||||
const app = openRequest.app
|
||||
const directory = projectDirectory()
|
||||
if (!app || !directory || !canOpen()) return
|
||||
|
||||
const item = options().find((o) => o.id === app)
|
||||
const openWith = item && "openWith" in item ? item.openWith : undefined
|
||||
await platform.openPath?.(directory, openWith)
|
||||
},
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
const err = openTask.error
|
||||
if (!err) return
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
|
||||
const openDir = (app: OpenApp) => {
|
||||
if (openTask.loading) return
|
||||
setOpenRequest({ app, version: openRequest.version + 1 })
|
||||
const directory = projectDirectory()
|
||||
if (!directory) return
|
||||
if (!canOpen()) return
|
||||
|
||||
const item = options().find((o) => o.id === app)
|
||||
const openWith = item && "openWith" in item ? item.openWith : undefined
|
||||
Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => {
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const copyPath = () => {
|
||||
@@ -362,18 +348,12 @@ export function SessionHeader() {
|
||||
<div class="flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden">
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none disabled:!cursor-default"
|
||||
classList={{
|
||||
"bg-surface-raised-base-active": openTask.loading,
|
||||
}}
|
||||
class="rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none"
|
||||
onClick={() => openDir(current().id)}
|
||||
disabled={openTask.loading}
|
||||
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
|
||||
>
|
||||
<div class="flex size-5 shrink-0 items-center justify-center">
|
||||
<Show when={openTask.loading} fallback={<AppIcon id={current().icon} class="size-4" />}>
|
||||
<Spinner class="size-3.5 text-icon-base" />
|
||||
</Show>
|
||||
<AppIcon id={current().icon} class="size-4" />
|
||||
</div>
|
||||
<span class="text-12-regular text-text-strong">Open</span>
|
||||
</Button>
|
||||
@@ -388,11 +368,7 @@ export function SessionHeader() {
|
||||
as={IconButton}
|
||||
icon="chevron-down"
|
||||
variant="ghost"
|
||||
disabled={openTask.loading}
|
||||
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
|
||||
classList={{
|
||||
"bg-surface-raised-base-active": openTask.loading,
|
||||
}}
|
||||
class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active"
|
||||
aria-label={language.t("session.header.open.menu")}
|
||||
/>
|
||||
<DropdownMenu.Portal>
|
||||
@@ -409,7 +385,6 @@ export function SessionHeader() {
|
||||
{options().map((o) => (
|
||||
<DropdownMenu.RadioItem
|
||||
value={o.id}
|
||||
disabled={openTask.loading}
|
||||
onSelect={() => {
|
||||
setMenu("open", false)
|
||||
openDir(o.id)
|
||||
|
||||
@@ -367,6 +367,34 @@ export const SettingsGeneral: Component = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled}>
|
||||
{(_) => {
|
||||
const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
|
||||
const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest)
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.desktop.section.wsl")}</h3>
|
||||
|
||||
<div class="bg-surface-raised-base px-4 rounded-lg">
|
||||
<SettingsRow
|
||||
title={language.t("settings.desktop.wsl.title")}
|
||||
description={language.t("settings.desktop.wsl.description")}
|
||||
>
|
||||
<div data-action="settings-wsl">
|
||||
<Switch
|
||||
checked={enabled() ?? false}
|
||||
disabled={enabledResource.state === "pending"}
|
||||
onChange={(checked) => platform.setWslEnabled?.(checked)?.finally(() => actions.refetch())}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
|
||||
{/* Updates Section */}
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.updates")}</h3>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { createScopedCache } from "@/utils/scoped-cache"
|
||||
import { uuid } from "@/utils/uuid"
|
||||
import type { SelectedLineRange } from "@/context/file"
|
||||
|
||||
export type LineComment = {
|
||||
@@ -53,7 +54,7 @@ function createCommentSessionState(store: Store<CommentStore>, setStore: SetStor
|
||||
|
||||
const add = (input: Omit<LineComment, "id" | "time">) => {
|
||||
const next: LineComment = {
|
||||
id: crypto.randomUUID?.() ?? Math.random().toString(16).slice(2),
|
||||
id: uuid(),
|
||||
time: Date.now(),
|
||||
...input,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSync } from "./global-sync"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
import { useServer } from "./server"
|
||||
import { usePlatform } from "./platform"
|
||||
import { Project } from "@opencode-ai/sdk/v2"
|
||||
import { Persist, persisted, removePersisted } from "@/utils/persist"
|
||||
import { same } from "@/utils/same"
|
||||
@@ -90,6 +91,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
const globalSdk = useGlobalSDK()
|
||||
const globalSync = useGlobalSync()
|
||||
const server = useServer()
|
||||
const platform = usePlatform()
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
@@ -200,10 +202,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
|
||||
for (const entry of SESSION_STATE_KEYS) {
|
||||
const target = session ? Persist.session(dir, session, entry.key) : Persist.workspace(dir, entry.key)
|
||||
void removePersisted(target)
|
||||
void removePersisted(target, platform)
|
||||
|
||||
const legacyKey = `${dir}/${entry.legacy}${session ? "/" + session : ""}.${entry.version}`
|
||||
void removePersisted({ key: legacyKey })
|
||||
void removePersisted({ key: legacyKey }, platform)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ export type Platform = {
|
||||
/** Set the default server URL to use on app startup (platform-specific) */
|
||||
setDefaultServerUrl?(url: string | null): Promise<void> | void
|
||||
|
||||
/** Get the configured WSL integration (desktop only) */
|
||||
getWslEnabled?(): Promise<boolean>
|
||||
|
||||
/** Set the configured WSL integration (desktop only) */
|
||||
setWslEnabled?(config: boolean): Promise<void> | void
|
||||
|
||||
/** Get the preferred display backend (desktop only) */
|
||||
getDisplayBackend?(): Promise<DisplayBackend | null> | DisplayBackend | null
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { useSDK } from "./sdk"
|
||||
import type { Platform } from "./platform"
|
||||
import { Persist, persisted, removePersisted } from "@/utils/persist"
|
||||
|
||||
export type LocalPTY = {
|
||||
@@ -37,14 +38,14 @@ type TerminalCacheEntry = {
|
||||
|
||||
const caches = new Set<Map<string, TerminalCacheEntry>>()
|
||||
|
||||
export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[]) {
|
||||
export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], platform?: Platform) {
|
||||
const key = getWorkspaceTerminalCacheKey(dir)
|
||||
for (const cache of caches) {
|
||||
const entry = cache.get(key)
|
||||
entry?.value.clear()
|
||||
}
|
||||
|
||||
removePersisted(Persist.workspace(dir, "terminal"))
|
||||
removePersisted(Persist.workspace(dir, "terminal"), platform)
|
||||
|
||||
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
|
||||
for (const id of sessionIDs ?? []) {
|
||||
@@ -53,7 +54,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[]) {
|
||||
}
|
||||
}
|
||||
for (const key of legacy) {
|
||||
removePersisted({ key })
|
||||
removePersisted({ key }, platform)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,9 @@ export const dict = {
|
||||
"command.category.permissions": "أذونات",
|
||||
"command.category.workspace": "مساحة عمل",
|
||||
"command.category.settings": "إعدادات",
|
||||
|
||||
"theme.scheme.system": "نظام",
|
||||
"theme.scheme.light": "فاتح",
|
||||
"theme.scheme.dark": "داكن",
|
||||
|
||||
"command.sidebar.toggle": "تبديل الشريط الجانبي",
|
||||
"command.project.open": "فتح مشروع",
|
||||
"command.provider.connect": "اتصال بموفر",
|
||||
@@ -31,17 +29,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "الجلسة غير المقروءة السابقة",
|
||||
"command.session.next.unseen": "الجلسة غير المقروءة التالية",
|
||||
"command.session.archive": "أرشفة الجلسة",
|
||||
|
||||
"command.palette": "لوحة الأوامر",
|
||||
|
||||
"command.theme.cycle": "تغيير السمة",
|
||||
"command.theme.set": "استخدام السمة: {{theme}}",
|
||||
"command.theme.scheme.cycle": "تغيير مخطط الألوان",
|
||||
"command.theme.scheme.set": "استخدام مخطط الألوان: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "تغيير اللغة",
|
||||
"command.language.set": "استخدام اللغة: {{language}}",
|
||||
|
||||
"command.session.new": "جلسة جديدة",
|
||||
"command.file.open": "فتح ملف",
|
||||
"command.tab.close": "إغلاق علامة التبويب",
|
||||
@@ -72,6 +66,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا",
|
||||
"command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا",
|
||||
"command.workspace.toggle": "تبديل مساحات العمل",
|
||||
"command.workspace.toggle.description": "تمكين أو تعطيل مساحات العمل المتعددة في الشريط الجانبي",
|
||||
"command.session.undo": "تراجع",
|
||||
"command.session.undo.description": "تراجع عن الرسالة الأخيرة",
|
||||
"command.session.redo": "إعادة",
|
||||
@@ -84,32 +79,30 @@ export const dict = {
|
||||
"command.session.share.description": "مشاركة هذه الجلسة ونسخ الرابط إلى الحافظة",
|
||||
"command.session.unshare": "إلغاء مشاركة الجلسة",
|
||||
"command.session.unshare.description": "إيقاف مشاركة هذه الجلسة",
|
||||
|
||||
"palette.search.placeholder": "البحث في الملفات والأوامر والجلسات",
|
||||
"palette.empty": "لا توجد نتائج",
|
||||
"palette.group.commands": "الأوامر",
|
||||
"palette.group.files": "الملفات",
|
||||
|
||||
"dialog.provider.search.placeholder": "البحث عن موفرين",
|
||||
"dialog.provider.empty": "لم يتم العثور على موفرين",
|
||||
"dialog.provider.group.popular": "شائع",
|
||||
"dialog.provider.group.other": "آخر",
|
||||
"dialog.provider.tag.recommended": "موصى به",
|
||||
"dialog.provider.opencode.note": "نماذج مختارة تتضمن Claude و GPT و Gemini والمزيد",
|
||||
"dialog.provider.anthropic.note": "اتصل باستخدام Claude Pro/Max أو مفتاح API",
|
||||
"dialog.provider.openai.note": "اتصل باستخدام ChatGPT Pro/Plus أو مفتاح API",
|
||||
"dialog.provider.copilot.note": "اتصل باستخدام Copilot أو مفتاح API",
|
||||
|
||||
"dialog.provider.openai.note": "اتصل باستخدام ChatGPT Pro/Plus أو مفتاح API",
|
||||
"dialog.provider.google.note": "نماذج Gemini لاستجابات سريعة ومنظمة",
|
||||
"dialog.provider.openrouter.note": "الوصول إلى جميع النماذج المدعومة من موفر واحد",
|
||||
"dialog.provider.vercel.note": "وصول موحد إلى نماذج الذكاء الاصطناعي مع توجيه ذكي",
|
||||
"dialog.model.select.title": "تحديد نموذج",
|
||||
"dialog.model.search.placeholder": "البحث عن نماذج",
|
||||
"dialog.model.empty": "لا توجد نتائج للنماذج",
|
||||
"dialog.model.manage": "إدارة النماذج",
|
||||
"dialog.model.manage.description": "تخصيص النماذج التي تظهر في محدد النماذج.",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من OpenCode",
|
||||
"dialog.model.unpaid.addMore.title": "إضافة المزيد من النماذج من موفرين مشهورين",
|
||||
|
||||
"dialog.provider.viewAll": "عرض المزيد من الموفرين",
|
||||
|
||||
"provider.connect.title": "اتصال {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "تسجيل الدخول باستخدام Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "حدد طريقة تسجيل الدخول لـ {{provider}}.",
|
||||
@@ -144,7 +137,42 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "رمز التأكيد",
|
||||
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
|
||||
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
|
||||
|
||||
"provider.custom.title": "موفر مخصص",
|
||||
"provider.custom.description.prefix": "تكوين موفر متوافق مع OpenAI. راجع ",
|
||||
"provider.custom.description.link": "وثائق تكوين الموفر",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "معرف الموفر",
|
||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "أحرف صغيرة، أرقام، شرطات، أو شرطات سفلية",
|
||||
"provider.custom.field.name.label": "اسم العرض",
|
||||
"provider.custom.field.name.placeholder": "موفر الذكاء الاصطناعي الخاص بي",
|
||||
"provider.custom.field.baseURL.label": "عنوان URL الأساسي",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "مفتاح API",
|
||||
"provider.custom.field.apiKey.placeholder": "مفتاح API",
|
||||
"provider.custom.field.apiKey.description": "اختياري. اتركه فارغًا إذا كنت تدير المصادقة عبر الترويسات.",
|
||||
"provider.custom.models.label": "النماذج",
|
||||
"provider.custom.models.id.label": "المعرف",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "الاسم",
|
||||
"provider.custom.models.name.placeholder": "اسم العرض",
|
||||
"provider.custom.models.remove": "إزالة النموذج",
|
||||
"provider.custom.models.add": "إضافة نموذج",
|
||||
"provider.custom.headers.label": "الترويسات (اختياري)",
|
||||
"provider.custom.headers.key.label": "ترويسة",
|
||||
"provider.custom.headers.key.placeholder": "Header-Name",
|
||||
"provider.custom.headers.value.label": "القيمة",
|
||||
"provider.custom.headers.value.placeholder": "قيمة",
|
||||
"provider.custom.headers.remove": "إزالة الترويسة",
|
||||
"provider.custom.headers.add": "إضافة ترويسة",
|
||||
"provider.custom.error.providerID.required": "معرف الموفر مطلوب",
|
||||
"provider.custom.error.providerID.format": "استخدم أحرفًا صغيرة، أرقامًا، شرطات، أو شرطات سفلية",
|
||||
"provider.custom.error.providerID.exists": "معرف الموفر هذا موجود بالفعل",
|
||||
"provider.custom.error.name.required": "اسم العرض مطلوب",
|
||||
"provider.custom.error.baseURL.required": "عنوان URL الأساسي مطلوب",
|
||||
"provider.custom.error.baseURL.format": "يجب أن يبدأ بـ http:// أو https://",
|
||||
"provider.custom.error.required": "مطلوب",
|
||||
"provider.custom.error.duplicate": "مكرر",
|
||||
"provider.disconnect.toast.disconnected.title": "تم فصل {{provider}}",
|
||||
"provider.disconnect.toast.disconnected.description": "لم تعد نماذج {{provider}} متاحة.",
|
||||
"model.tag.free": "مجاني",
|
||||
@@ -163,9 +191,9 @@ export const dict = {
|
||||
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
|
||||
"model.tooltip.reasoning.none": "بدون استنتاج",
|
||||
"model.tooltip.context": "حد السياق {{limit}}",
|
||||
|
||||
"common.search.placeholder": "بحث",
|
||||
"common.goBack": "رجوع",
|
||||
"common.goForward": "انتقل للأمام",
|
||||
"common.loading": "جارٍ التحميل",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "إلغاء",
|
||||
@@ -176,14 +204,12 @@ export const dict = {
|
||||
"common.saving": "جارٍ الحفظ...",
|
||||
"common.default": "افتراضي",
|
||||
"common.attachment": "مرفق",
|
||||
|
||||
"prompt.placeholder.shell": "أدخل أمر shell...",
|
||||
"prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "لخّص التعليقات…",
|
||||
"prompt.placeholder.summarizeComment": "لخّص التعليق…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell.exit": "esc للخروج",
|
||||
|
||||
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
|
||||
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
|
||||
"prompt.example.3": "إصلاح الاختبارات المعطلة",
|
||||
@@ -209,7 +235,6 @@ export const dict = {
|
||||
"prompt.example.23": "إضافة ترقيم الصفحات إلى هذه القائمة",
|
||||
"prompt.example.24": "إنشاء أمر CLI لـ...",
|
||||
"prompt.example.25": "كيف تعمل متغيرات البيئة هنا؟",
|
||||
|
||||
"prompt.popover.emptyResults": "لا توجد نتائج مطابقة",
|
||||
"prompt.popover.emptyCommands": "لا توجد أوامر مطابقة",
|
||||
"prompt.dropzone.label": "أفلت الصور أو ملفات PDF هنا",
|
||||
@@ -225,7 +250,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "إزالة المرفق",
|
||||
"prompt.action.send": "إرسال",
|
||||
"prompt.action.stop": "توقف",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "لصق غير مدعوم",
|
||||
"prompt.toast.pasteUnsupported.description": "يمكن لصق الصور أو ملفات PDF فقط هنا.",
|
||||
"prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً",
|
||||
@@ -236,24 +260,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "فشل إرسال الأمر",
|
||||
"prompt.toast.promptSendFailed.title": "فشل إرسال الموجه",
|
||||
"prompt.toast.promptSendFailed.description": "تعذر استرداد الجلسة",
|
||||
|
||||
"dialog.mcp.title": "MCPs",
|
||||
"dialog.mcp.description": "{{enabled}} من {{total}} مفعل",
|
||||
"dialog.mcp.empty": "لم يتم تكوين MCPs",
|
||||
|
||||
"dialog.lsp.empty": "تم الكشف تلقائيًا عن LSPs من أنواع الملفات",
|
||||
"dialog.plugins.empty": "الإضافات المكونة في opencode.json",
|
||||
|
||||
"mcp.status.connected": "متصل",
|
||||
"mcp.status.failed": "فشل",
|
||||
"mcp.status.needs_auth": "يحتاج إلى مصادقة",
|
||||
"mcp.status.disabled": "معطل",
|
||||
|
||||
"dialog.fork.empty": "لا توجد رسائل للتفرع منها",
|
||||
|
||||
"dialog.directory.search.placeholder": "البحث في المجلدات",
|
||||
"dialog.directory.empty": "لم يتم العثور على مجلدات",
|
||||
|
||||
"dialog.server.title": "الخوادم",
|
||||
"dialog.server.description": "تبديل خادم OpenCode الذي يتصل به هذا التطبيق.",
|
||||
"dialog.server.search.placeholder": "البحث في الخوادم",
|
||||
@@ -271,14 +289,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "تعيين الخادم الحالي كافتراضي",
|
||||
"dialog.server.default.clear": "مسح",
|
||||
"dialog.server.action.remove": "إزالة الخادم",
|
||||
|
||||
"dialog.server.menu.edit": "تعديل",
|
||||
"dialog.server.menu.default": "تعيين كافتراضي",
|
||||
"dialog.server.menu.defaultRemove": "إزالة الافتراضي",
|
||||
"dialog.server.menu.delete": "حذف",
|
||||
"dialog.server.current": "الخادم الحالي",
|
||||
"dialog.server.status.default": "افتراضي",
|
||||
|
||||
"dialog.project.edit.title": "تحرير المشروع",
|
||||
"dialog.project.edit.name": "الاسم",
|
||||
"dialog.project.edit.icon": "أيقونة",
|
||||
@@ -287,7 +303,6 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "موصى به: 128x128px",
|
||||
"dialog.project.edit.color": "لون",
|
||||
"dialog.project.edit.color.select": "اختر لون {{color}}",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "سكريبت بدء تشغيل مساحة العمل",
|
||||
"dialog.project.edit.worktree.startup.description": "يتم تشغيله بعد إنشاء مساحة عمل جديدة (شجرة عمل).",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "مثال: bun install",
|
||||
@@ -298,10 +313,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "المساعد",
|
||||
"context.breakdown.tool": "استدعاءات الأداة",
|
||||
"context.breakdown.other": "أخرى",
|
||||
|
||||
"context.systemPrompt.title": "موجه النظام",
|
||||
"context.rawMessages.title": "الرسائل الخام",
|
||||
|
||||
"context.stats.session": "جلسة",
|
||||
"context.stats.messages": "رسائل",
|
||||
"context.stats.provider": "موفر",
|
||||
@@ -318,34 +331,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "التكلفة الإجمالية",
|
||||
"context.stats.sessionCreated": "تم إنشاء الجلسة",
|
||||
"context.stats.lastActivity": "آخر نشاط",
|
||||
|
||||
"context.usage.tokens": "رموز",
|
||||
"context.usage.usage": "استخدام",
|
||||
"context.usage.cost": "تكلفة",
|
||||
"context.usage.clickToView": "انقر لعرض السياق",
|
||||
"context.usage.view": "عرض استخدام السياق",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "لغة",
|
||||
"toast.language.description": "تم التبديل إلى {{language}}",
|
||||
|
||||
"toast.theme.title": "تم تبديل السمة",
|
||||
"toast.scheme.title": "مخطط الألوان",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "قبول التعديلات تلقائيًا",
|
||||
"toast.permissions.autoaccept.on.description": "سيتم الموافقة تلقائيًا على أذونات التحرير والكتابة",
|
||||
"toast.permissions.autoaccept.off.title": "توقف قبول التعديلات تلقائيًا",
|
||||
"toast.permissions.autoaccept.off.description": "ستتطلب أذونات التحرير والكتابة موافقة",
|
||||
|
||||
"toast.workspace.enabled.title": "تم تمكين مساحات العمل",
|
||||
"toast.workspace.enabled.description": "الآن يتم عرض عدة worktrees في الشريط الجانبي",
|
||||
"toast.workspace.disabled.title": "تم تعطيل مساحات العمل",
|
||||
"toast.workspace.disabled.description": "يتم عرض worktree الرئيسي فقط في الشريط الجانبي",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "قبول التعديلات تلقائيًا",
|
||||
"toast.permissions.autoaccept.on.description": "سيتم الموافقة تلقائيًا على أذونات التحرير والكتابة",
|
||||
"toast.permissions.autoaccept.off.title": "توقف قبول التعديلات تلقائيًا",
|
||||
"toast.permissions.autoaccept.off.description": "ستتطلب أذونات التحرير والكتابة موافقة",
|
||||
"toast.model.none.title": "لم يتم تحديد نموذج",
|
||||
"toast.model.none.description": "قم بتوصيل موفر لتلخيص هذه الجلسة",
|
||||
|
||||
"toast.file.loadFailed.title": "فشل تحميل الملف",
|
||||
|
||||
"toast.file.listFailed.title": "فشل سرد الملفات",
|
||||
"toast.context.noLineSelection.title": "لا يوجد تحديد للأسطر",
|
||||
"toast.context.noLineSelection.description": "حدد نطاق أسطر في تبويب ملف أولاً.",
|
||||
@@ -354,19 +375,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "تم نسخ عنوان URL للمشاركة إلى الحافظة!",
|
||||
"toast.session.share.failed.title": "فشل مشاركة الجلسة",
|
||||
"toast.session.share.failed.description": "حدث خطأ أثناء مشاركة الجلسة",
|
||||
|
||||
"toast.session.unshare.success.title": "تم إلغاء مشاركة الجلسة",
|
||||
"toast.session.unshare.success.description": "تم إلغاء مشاركة الجلسة بنجاح!",
|
||||
"toast.session.unshare.failed.title": "فشل إلغاء مشاركة الجلسة",
|
||||
"toast.session.unshare.failed.description": "حدث خطأ أثناء إلغاء مشاركة الجلسة",
|
||||
|
||||
"toast.session.listFailed.title": "فشل تحميل الجلسات لـ {{project}}",
|
||||
|
||||
"toast.update.title": "تحديث متاح",
|
||||
"toast.update.description": "نسخة جديدة من OpenCode ({{version}}) متاحة الآن للتثبيت.",
|
||||
"toast.update.action.installRestart": "تثبيت وإعادة تشغيل",
|
||||
"toast.update.action.notYet": "ليس الآن",
|
||||
|
||||
"error.page.title": "حدث خطأ ما",
|
||||
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
||||
"error.page.details.label": "تفاصيل الخطأ",
|
||||
@@ -377,12 +394,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "يرجى الإبلاغ عن هذا الخطأ لفريق OpenCode",
|
||||
"error.page.report.discord": "على Discord",
|
||||
"error.page.version": "الإصدار: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"لم يتم العثور على العنصر الجذري. هل نسيت إضافته إلى index.html؟ أو ربما تمت كتابة سمة id بشكل خاطئ؟",
|
||||
|
||||
"error.globalSync.connectFailed": "تعذر الاتصال بالخادم. هل هناك خادم يعمل في `{{url}}`؟",
|
||||
|
||||
"directory.error.invalidUrl": "دليل غير صالح في عنوان URL.",
|
||||
"error.chain.unknown": "خطأ غير معروف",
|
||||
"error.chain.causedBy": "بسبب:",
|
||||
"error.chain.apiError": "خطأ API",
|
||||
@@ -402,21 +417,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "فشل تحليل frontmatter في {{path}}:\n{{message}}",
|
||||
"error.chain.configInvalid": "ملف التكوين في {{path}} غير صالح",
|
||||
"error.chain.configInvalidWithMessage": "ملف التكوين في {{path}} غير صالح: {{message}}",
|
||||
|
||||
"notification.permission.title": "مطلوب إذن",
|
||||
"notification.permission.description": "{{sessionTitle}} في {{projectName}} يحتاج إلى إذن",
|
||||
"notification.question.title": "سؤال",
|
||||
"notification.question.description": "{{sessionTitle}} في {{projectName}} لديه سؤال",
|
||||
"notification.action.goToSession": "انتقل إلى الجلسة",
|
||||
|
||||
"notification.session.responseReady.title": "الاستجابة جاهزة",
|
||||
"notification.session.error.title": "خطأ في الجلسة",
|
||||
"notification.session.error.fallbackDescription": "حدث خطأ",
|
||||
|
||||
"home.recentProjects": "المشاريع الحديثة",
|
||||
"home.empty.title": "لا توجد مشاريع حديثة",
|
||||
"home.empty.description": "ابدأ بفتح مشروع محلي",
|
||||
|
||||
"session.tab.session": "جلسة",
|
||||
"session.tab.review": "مراجعة",
|
||||
"session.tab.context": "سياق",
|
||||
@@ -435,17 +446,18 @@ export const dict = {
|
||||
"session.messages.loadEarlier": "تحميل الرسائل السابقة",
|
||||
"session.messages.loading": "جارٍ تحميل الرسائل...",
|
||||
"session.messages.jumpToLatest": "الانتقال إلى الأحدث",
|
||||
|
||||
"session.context.addToContext": "إضافة {{selection}} إلى السياق",
|
||||
|
||||
"session.new.worktree.main": "الفرع الرئيسي",
|
||||
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
||||
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
||||
"session.new.lastModified": "آخر تعديل",
|
||||
|
||||
"session.header.search.placeholder": "بحث {{project}}",
|
||||
"session.header.searchFiles": "بحث عن الملفات",
|
||||
|
||||
"session.header.openIn": "فتح في",
|
||||
"session.header.open.action": "فتح {{app}}",
|
||||
"session.header.open.ariaLabel": "فتح في {{app}}",
|
||||
"session.header.open.menu": "خيارات الفتح",
|
||||
"session.header.open.copyPath": "نسخ المسار",
|
||||
"status.popover.trigger": "الحالة",
|
||||
"status.popover.ariaLabel": "إعدادات الخوادم",
|
||||
"status.popover.tab.servers": "الخوادم",
|
||||
@@ -453,7 +465,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "الإضافات",
|
||||
"status.popover.action.manageServers": "إدارة الخوادم",
|
||||
|
||||
"session.share.popover.title": "نشر على الويب",
|
||||
"session.share.popover.description.shared": "هذه الجلسة عامة على الويب. يمكن لأي شخص لديه الرابط الوصول إليها.",
|
||||
"session.share.popover.description.unshared": "شارك الجلسة علنًا على الويب. ستكون متاحة لأي شخص لديه الرابط.",
|
||||
@@ -465,10 +476,8 @@ export const dict = {
|
||||
"session.share.action.view": "عرض",
|
||||
"session.share.copy.copied": "تم النسخ",
|
||||
"session.share.copy.copyLink": "نسخ الرابط",
|
||||
|
||||
"lsp.tooltip.none": "لا توجد خوادم LSP",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "جارٍ تحميل الموجه...",
|
||||
"terminal.loading": "جارٍ تحميل المحطة الطرفية...",
|
||||
"terminal.title": "محطة طرفية",
|
||||
@@ -476,7 +485,6 @@ export const dict = {
|
||||
"terminal.close": "إغلاق المحطة الطرفية",
|
||||
"terminal.connectionLost.title": "فقد الاتصال",
|
||||
"terminal.connectionLost.description": "انقطع اتصال المحطة الطرفية. يمكن أن يحدث هذا عند إعادة تشغيل الخادم.",
|
||||
|
||||
"common.closeTab": "إغلاق علامة التبويب",
|
||||
"common.dismiss": "رفض",
|
||||
"common.requestFailed": "فشل الطلب",
|
||||
@@ -490,7 +498,6 @@ export const dict = {
|
||||
"common.edit": "تحرير",
|
||||
"common.loadMore": "تحميل المزيد",
|
||||
"common.key.esc": "ESC",
|
||||
|
||||
"sidebar.menu.toggle": "تبديل القائمة",
|
||||
"sidebar.nav.projectsAndSessions": "المشاريع والجلسات",
|
||||
"sidebar.settings": "الإعدادات",
|
||||
@@ -502,18 +509,19 @@ export const dict = {
|
||||
"sidebar.gettingStarted.line2": "قم بتوصيل أي موفر لاستخدام النماذج، بما في ذلك Claude و GPT و Gemini وما إلى ذلك.",
|
||||
"sidebar.project.recentSessions": "الجلسات الحديثة",
|
||||
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "سطح المكتب",
|
||||
"settings.section.server": "الخادم",
|
||||
"settings.tab.general": "عام",
|
||||
"settings.tab.shortcuts": "اختصارات",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "تكامل WSL",
|
||||
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
|
||||
"settings.general.section.appearance": "المظهر",
|
||||
"settings.general.section.notifications": "إشعارات النظام",
|
||||
"settings.general.section.updates": "التحديثات",
|
||||
"settings.general.section.sounds": "المؤثرات الصوتية",
|
||||
|
||||
"settings.general.section.display": "شاشة العرض",
|
||||
"settings.general.row.language.title": "اللغة",
|
||||
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
||||
"settings.general.row.appearance.title": "المظهر",
|
||||
@@ -522,10 +530,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||
"settings.general.row.font.title": "الخط",
|
||||
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
|
||||
|
||||
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
||||
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"على Linux مع شاشات بمعدلات تحديث مختلطة، يمكن أن يكون Wayland الأصلي أكثر استقرارًا.",
|
||||
"settings.general.row.releaseNotes.title": "ملاحظات الإصدار",
|
||||
"settings.general.row.releaseNotes.description": 'عرض نوافذ "ما الجديد" المنبثقة بعد التحديثات',
|
||||
|
||||
"settings.updates.row.startup.title": "التحقق من التحديثات عند بدء التشغيل",
|
||||
"settings.updates.row.startup.description": "التحقق تلقائيًا من التحديثات عند تشغيل OpenCode",
|
||||
"settings.updates.row.check.title": "التحقق من التحديثات",
|
||||
@@ -591,21 +601,18 @@ export const dict = {
|
||||
"sound.option.yup04": "نعم 04",
|
||||
"sound.option.yup05": "نعم 05",
|
||||
"sound.option.yup06": "نعم 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "وكيل",
|
||||
"settings.general.notifications.agent.description": "عرض إشعار النظام عندما يكتمل الوكيل أو يحتاج إلى اهتمام",
|
||||
"settings.general.notifications.permissions.title": "أذونات",
|
||||
"settings.general.notifications.permissions.description": "عرض إشعار النظام عند الحاجة إلى إذن",
|
||||
"settings.general.notifications.errors.title": "أخطاء",
|
||||
"settings.general.notifications.errors.description": "عرض إشعار النظام عند حدوث خطأ",
|
||||
|
||||
"settings.general.sounds.agent.title": "وكيل",
|
||||
"settings.general.sounds.agent.description": "تشغيل صوت عندما يكتمل الوكيل أو يحتاج إلى اهتمام",
|
||||
"settings.general.sounds.permissions.title": "أذونات",
|
||||
"settings.general.sounds.permissions.description": "تشغيل صوت عند الحاجة إلى إذن",
|
||||
"settings.general.sounds.errors.title": "أخطاء",
|
||||
"settings.general.sounds.errors.description": "تشغيل صوت عند حدوث خطأ",
|
||||
|
||||
"settings.shortcuts.title": "اختصارات لوحة المفاتيح",
|
||||
"settings.shortcuts.reset.button": "إعادة التعيين إلى الافتراضيات",
|
||||
"settings.shortcuts.reset.toast.title": "تم إعادة تعيين الاختصارات",
|
||||
@@ -616,14 +623,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "اضغط على المفاتيح",
|
||||
"settings.shortcuts.search.placeholder": "البحث في الاختصارات",
|
||||
"settings.shortcuts.search.empty": "لم يتم العثور على اختصارات",
|
||||
|
||||
"settings.shortcuts.group.general": "عام",
|
||||
"settings.shortcuts.group.session": "جلسة",
|
||||
"settings.shortcuts.group.navigation": "تصفح",
|
||||
"settings.shortcuts.group.modelAndAgent": "النموذج والوكيل",
|
||||
"settings.shortcuts.group.terminal": "المحطة الطرفية",
|
||||
"settings.shortcuts.group.prompt": "موجه",
|
||||
|
||||
"settings.providers.title": "الموفرون",
|
||||
"settings.providers.description": "ستكون إعدادات الموفر قابلة للتكوين هنا.",
|
||||
"settings.providers.section.connected": "الموفرون المتصلون",
|
||||
@@ -641,16 +646,13 @@ export const dict = {
|
||||
"settings.commands.description": "ستكون إعدادات الأمر قابلة للتكوين هنا.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "ستكون إعدادات MCP قابلة للتكوين هنا.",
|
||||
|
||||
"settings.permissions.title": "الأذونات",
|
||||
"settings.permissions.description": "تحكم في الأدوات التي يمكن للخادم استخدامها بشكل افتراضي.",
|
||||
"settings.permissions.section.tools": "الأدوات",
|
||||
"settings.permissions.toast.updateFailed.title": "فشل تحديث الأذونات",
|
||||
|
||||
"settings.permissions.action.allow": "سماح",
|
||||
"settings.permissions.action.ask": "سؤال",
|
||||
"settings.permissions.action.deny": "رفض",
|
||||
|
||||
"settings.permissions.tool.read.title": "قراءة",
|
||||
"settings.permissions.tool.read.description": "قراءة ملف (يطابق مسار الملف)",
|
||||
"settings.permissions.tool.edit.title": "تحرير",
|
||||
@@ -664,9 +666,9 @@ export const dict = {
|
||||
"settings.permissions.tool.list.description": "سرد الملفات داخل دليل",
|
||||
"settings.permissions.tool.bash.title": "Bash",
|
||||
"settings.permissions.tool.bash.description": "تشغيل أوامر shell",
|
||||
"settings.permissions.tool.task.title": "Task",
|
||||
"settings.permissions.tool.task.title": "مهمة",
|
||||
"settings.permissions.tool.task.description": "تشغيل الوكلاء الفرعيين",
|
||||
"settings.permissions.tool.skill.title": "Skill",
|
||||
"settings.permissions.tool.skill.title": "مهارة",
|
||||
"settings.permissions.tool.skill.description": "تحميل مهارة بالاسم",
|
||||
"settings.permissions.tool.lsp.title": "LSP",
|
||||
"settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة",
|
||||
@@ -684,12 +686,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "الوصول إلى الملفات خارج دليل المشروع",
|
||||
"settings.permissions.tool.doom_loop.title": "حلقة الموت",
|
||||
"settings.permissions.tool.doom_loop.description": "اكتشاف استدعاءات الأدوات المتكررة بمدخلات متطابقة",
|
||||
|
||||
"session.delete.failed.title": "فشل حذف الجلسة",
|
||||
"session.delete.title": "حذف الجلسة",
|
||||
"session.delete.confirm": 'حذف الجلسة "{{name}}"؟',
|
||||
"session.delete.button": "حذف الجلسة",
|
||||
|
||||
"workspace.new": "مساحة عمل جديدة",
|
||||
"workspace.type.local": "محلي",
|
||||
"workspace.type.sandbox": "صندوق رمل",
|
||||
|
||||
@@ -16,11 +16,9 @@ export const dict = {
|
||||
"command.category.permissions": "Permissões",
|
||||
"command.category.workspace": "Espaço de trabalho",
|
||||
"command.category.settings": "Configurações",
|
||||
|
||||
"theme.scheme.system": "Sistema",
|
||||
"theme.scheme.light": "Claro",
|
||||
"theme.scheme.dark": "Escuro",
|
||||
|
||||
"command.sidebar.toggle": "Alternar barra lateral",
|
||||
"command.project.open": "Abrir projeto",
|
||||
"command.provider.connect": "Conectar provedor",
|
||||
@@ -31,17 +29,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "Sessão não lida anterior",
|
||||
"command.session.next.unseen": "Próxima sessão não lida",
|
||||
"command.session.archive": "Arquivar sessão",
|
||||
|
||||
"command.palette": "Paleta de comandos",
|
||||
|
||||
"command.theme.cycle": "Alternar tema",
|
||||
"command.theme.set": "Usar tema: {{theme}}",
|
||||
"command.theme.scheme.cycle": "Alternar esquema de cores",
|
||||
"command.theme.scheme.set": "Usar esquema de cores: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "Alternar idioma",
|
||||
"command.language.set": "Usar idioma: {{language}}",
|
||||
|
||||
"command.session.new": "Nova sessão",
|
||||
"command.file.open": "Abrir arquivo",
|
||||
"command.tab.close": "Fechar aba",
|
||||
@@ -72,6 +66,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Aceitar edições automaticamente",
|
||||
"command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente",
|
||||
"command.workspace.toggle": "Alternar espaços de trabalho",
|
||||
"command.workspace.toggle.description": "Habilitar ou desabilitar múltiplos espaços de trabalho na barra lateral",
|
||||
"command.session.undo": "Desfazer",
|
||||
"command.session.undo.description": "Desfazer a última mensagem",
|
||||
"command.session.redo": "Refazer",
|
||||
@@ -84,32 +79,30 @@ export const dict = {
|
||||
"command.session.share.description": "Compartilhar esta sessão e copiar a URL para a área de transferência",
|
||||
"command.session.unshare": "Parar de compartilhar sessão",
|
||||
"command.session.unshare.description": "Parar de compartilhar esta sessão",
|
||||
|
||||
"palette.search.placeholder": "Buscar arquivos, comandos e sessões",
|
||||
"palette.empty": "Nenhum resultado encontrado",
|
||||
"palette.group.commands": "Comandos",
|
||||
"palette.group.files": "Arquivos",
|
||||
|
||||
"dialog.provider.search.placeholder": "Buscar provedores",
|
||||
"dialog.provider.empty": "Nenhum provedor encontrado",
|
||||
"dialog.provider.group.popular": "Popular",
|
||||
"dialog.provider.group.other": "Outro",
|
||||
"dialog.provider.tag.recommended": "Recomendado",
|
||||
"dialog.provider.opencode.note": "Modelos selecionados incluindo Claude, GPT, Gemini e mais",
|
||||
"dialog.provider.anthropic.note": "Conectar com Claude Pro/Max ou chave de API",
|
||||
"dialog.provider.openai.note": "Conectar com ChatGPT Pro/Plus ou chave de API",
|
||||
"dialog.provider.copilot.note": "Conectar com Copilot ou chave de API",
|
||||
|
||||
"dialog.provider.openai.note": "Conectar com ChatGPT Pro/Plus ou chave de API",
|
||||
"dialog.provider.google.note": "Modelos Gemini para respostas rápidas e estruturadas",
|
||||
"dialog.provider.openrouter.note": "Acesse todos os modelos suportados de um único provedor",
|
||||
"dialog.provider.vercel.note": "Acesso unificado a modelos de IA com roteamento inteligente",
|
||||
"dialog.model.select.title": "Selecionar modelo",
|
||||
"dialog.model.search.placeholder": "Buscar modelos",
|
||||
"dialog.model.empty": "Nenhum resultado de modelo",
|
||||
"dialog.model.manage": "Gerenciar modelos",
|
||||
"dialog.model.manage.description": "Personalizar quais modelos aparecem no seletor de modelos.",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo OpenCode",
|
||||
"dialog.model.unpaid.addMore.title": "Adicionar mais modelos de provedores populares",
|
||||
|
||||
"dialog.provider.viewAll": "Ver mais provedores",
|
||||
|
||||
"provider.connect.title": "Conectar {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Entrar com Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Selecionar método de login para {{provider}}.",
|
||||
@@ -144,7 +137,42 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "Código de confirmação",
|
||||
"provider.connect.toast.connected.title": "{{provider}} conectado",
|
||||
"provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.",
|
||||
|
||||
"provider.custom.title": "Provedor personalizado",
|
||||
"provider.custom.description.prefix": "Configure um provedor compatível com OpenAI. Veja a ",
|
||||
"provider.custom.description.link": "documentação de configuração do provedor",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID do Provedor",
|
||||
"provider.custom.field.providerID.placeholder": "meuprovedor",
|
||||
"provider.custom.field.providerID.description": "Letras minúsculas, números, hifens ou sublinhados",
|
||||
"provider.custom.field.name.label": "Nome de exibição",
|
||||
"provider.custom.field.name.placeholder": "Meu Provedor de IA",
|
||||
"provider.custom.field.baseURL.label": "URL Base",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.meuprovedor.com/v1",
|
||||
"provider.custom.field.apiKey.label": "Chave de API",
|
||||
"provider.custom.field.apiKey.placeholder": "Chave de API",
|
||||
"provider.custom.field.apiKey.description": "Opcional. Deixe em branco se gerenciar autenticação via cabeçalhos.",
|
||||
"provider.custom.models.label": "Modelos",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "id-do-modelo",
|
||||
"provider.custom.models.name.label": "Nome",
|
||||
"provider.custom.models.name.placeholder": "Nome de Exibição",
|
||||
"provider.custom.models.remove": "Remover modelo",
|
||||
"provider.custom.models.add": "Adicionar modelo",
|
||||
"provider.custom.headers.label": "Cabeçalhos (opcional)",
|
||||
"provider.custom.headers.key.label": "Cabeçalho",
|
||||
"provider.custom.headers.key.placeholder": "Nome-Do-Cabeçalho",
|
||||
"provider.custom.headers.value.label": "Valor",
|
||||
"provider.custom.headers.value.placeholder": "valor",
|
||||
"provider.custom.headers.remove": "Remover cabeçalho",
|
||||
"provider.custom.headers.add": "Adicionar cabeçalho",
|
||||
"provider.custom.error.providerID.required": "ID do Provedor é obrigatório",
|
||||
"provider.custom.error.providerID.format": "Use letras minúsculas, números, hifens ou sublinhados",
|
||||
"provider.custom.error.providerID.exists": "Esse ID de provedor já existe",
|
||||
"provider.custom.error.name.required": "Nome de exibição é obrigatório",
|
||||
"provider.custom.error.baseURL.required": "URL Base é obrigatória",
|
||||
"provider.custom.error.baseURL.format": "Deve começar com http:// ou https://",
|
||||
"provider.custom.error.required": "Obrigatório",
|
||||
"provider.custom.error.duplicate": "Duplicado",
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} desconectado",
|
||||
"provider.disconnect.toast.disconnected.description": "Os modelos de {{provider}} não estão mais disponíveis.",
|
||||
"model.tag.free": "Grátis",
|
||||
@@ -163,9 +191,9 @@ export const dict = {
|
||||
"model.tooltip.reasoning.allowed": "Permite raciocínio",
|
||||
"model.tooltip.reasoning.none": "Sem raciocínio",
|
||||
"model.tooltip.context": "Limite de contexto {{limit}}",
|
||||
|
||||
"common.search.placeholder": "Buscar",
|
||||
"common.goBack": "Voltar",
|
||||
"common.goForward": "Avançar",
|
||||
"common.loading": "Carregando",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Cancelar",
|
||||
@@ -176,14 +204,12 @@ export const dict = {
|
||||
"common.saving": "Salvando...",
|
||||
"common.default": "Padrão",
|
||||
"common.attachment": "anexo",
|
||||
|
||||
"prompt.placeholder.shell": "Digite comando do shell...",
|
||||
"prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "Resumir comentários…",
|
||||
"prompt.placeholder.summarizeComment": "Resumir comentário…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell.exit": "esc para sair",
|
||||
|
||||
"prompt.example.1": "Corrigir um TODO no código",
|
||||
"prompt.example.2": "Qual é a stack tecnológica deste projeto?",
|
||||
"prompt.example.3": "Corrigir testes quebrados",
|
||||
@@ -209,7 +235,6 @@ export const dict = {
|
||||
"prompt.example.23": "Adicionar paginação a esta lista",
|
||||
"prompt.example.24": "Criar um comando CLI para...",
|
||||
"prompt.example.25": "Como funcionam as variáveis de ambiente aqui?",
|
||||
|
||||
"prompt.popover.emptyResults": "Nenhum resultado correspondente",
|
||||
"prompt.popover.emptyCommands": "Nenhum comando correspondente",
|
||||
"prompt.dropzone.label": "Solte imagens ou PDFs aqui",
|
||||
@@ -225,7 +250,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Remover anexo",
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Parar",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Colagem não suportada",
|
||||
"prompt.toast.pasteUnsupported.description": "Somente imagens ou PDFs podem ser colados aqui.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
|
||||
@@ -236,23 +260,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "Falha ao enviar comando",
|
||||
"prompt.toast.promptSendFailed.title": "Falha ao enviar prompt",
|
||||
"prompt.toast.promptSendFailed.description": "Não foi possível recuperar a sessão",
|
||||
|
||||
"dialog.mcp.title": "MCPs",
|
||||
"dialog.mcp.description": "{{enabled}} de {{total}} habilitados",
|
||||
"dialog.mcp.description": "{{enabled}} of {{total}} habilitados",
|
||||
"dialog.mcp.empty": "Nenhum MCP configurado",
|
||||
|
||||
"dialog.lsp.empty": "LSPs detectados automaticamente pelos tipos de arquivo",
|
||||
"dialog.plugins.empty": "Plugins configurados em opencode.json",
|
||||
"mcp.status.connected": "conectado",
|
||||
"mcp.status.failed": "falhou",
|
||||
"mcp.status.needs_auth": "precisa de autenticação",
|
||||
"mcp.status.disabled": "desabilitado",
|
||||
|
||||
"dialog.fork.empty": "Nenhuma mensagem para bifurcar",
|
||||
|
||||
"dialog.directory.search.placeholder": "Buscar pastas",
|
||||
"dialog.directory.empty": "Nenhuma pasta encontrada",
|
||||
|
||||
"dialog.server.title": "Servidores",
|
||||
"dialog.server.description": "Trocar para qual servidor OpenCode este aplicativo se conecta.",
|
||||
"dialog.server.search.placeholder": "Buscar servidores",
|
||||
@@ -270,7 +289,6 @@ export const dict = {
|
||||
"dialog.server.default.set": "Definir servidor atual como padrão",
|
||||
"dialog.server.default.clear": "Limpar",
|
||||
"dialog.server.action.remove": "Remover servidor",
|
||||
|
||||
"dialog.server.menu.edit": "Editar",
|
||||
"dialog.server.menu.default": "Definir como padrão",
|
||||
"dialog.server.menu.defaultRemove": "Remover padrão",
|
||||
@@ -288,7 +306,6 @@ export const dict = {
|
||||
"dialog.project.edit.worktree.startup": "Script de inicialização do espaço de trabalho",
|
||||
"dialog.project.edit.worktree.startup.description": "Executa após criar um novo espaço de trabalho (worktree).",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "ex: bun install",
|
||||
|
||||
"context.breakdown.title": "Detalhamento do Contexto",
|
||||
"context.breakdown.note":
|
||||
'Detalhamento aproximado dos tokens de entrada. "Outros" inclui definições de ferramentas e overhead.',
|
||||
@@ -297,10 +314,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "Assistente",
|
||||
"context.breakdown.tool": "Chamadas de Ferramentas",
|
||||
"context.breakdown.other": "Outros",
|
||||
|
||||
"context.systemPrompt.title": "Prompt do Sistema",
|
||||
"context.rawMessages.title": "Mensagens brutas",
|
||||
|
||||
"context.stats.session": "Sessão",
|
||||
"context.stats.messages": "Mensagens",
|
||||
"context.stats.provider": "Provedor",
|
||||
@@ -317,34 +332,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "Custo Total",
|
||||
"context.stats.sessionCreated": "Sessão Criada",
|
||||
"context.stats.lastActivity": "Última Atividade",
|
||||
|
||||
"context.usage.tokens": "Tokens",
|
||||
"context.usage.usage": "Uso",
|
||||
"context.usage.cost": "Custo",
|
||||
"context.usage.clickToView": "Clique para ver o contexto",
|
||||
"context.usage.view": "Ver uso do contexto",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "Idioma",
|
||||
"toast.language.description": "Alterado para {{language}}",
|
||||
|
||||
"toast.theme.title": "Tema alterado",
|
||||
"toast.scheme.title": "Esquema de cores",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Aceitando edições automaticamente",
|
||||
"toast.permissions.autoaccept.on.description": "Permissões de edição e escrita serão aprovadas automaticamente",
|
||||
"toast.permissions.autoaccept.off.title": "Parou de aceitar edições automaticamente",
|
||||
"toast.permissions.autoaccept.off.description": "Permissões de edição e escrita exigirão aprovação",
|
||||
|
||||
"toast.workspace.enabled.title": "Espaços de trabalho ativados",
|
||||
"toast.workspace.enabled.description": "Várias worktrees agora são exibidas na barra lateral",
|
||||
"toast.workspace.disabled.title": "Espaços de trabalho desativados",
|
||||
"toast.workspace.disabled.description": "Apenas a worktree principal é exibida na barra lateral",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Aceitando edições automaticamente",
|
||||
"toast.permissions.autoaccept.on.description": "Permissões de edição e escrita serão aprovadas automaticamente",
|
||||
"toast.permissions.autoaccept.off.title": "Parou de aceitar edições automaticamente",
|
||||
"toast.permissions.autoaccept.off.description": "Permissões de edição e escrita exigirão aprovação",
|
||||
"toast.model.none.title": "Nenhum modelo selecionado",
|
||||
"toast.model.none.description": "Conecte um provedor para resumir esta sessão",
|
||||
|
||||
"toast.file.loadFailed.title": "Falha ao carregar arquivo",
|
||||
|
||||
"toast.file.listFailed.title": "Falha ao listar arquivos",
|
||||
"toast.context.noLineSelection.title": "Nenhuma seleção de linhas",
|
||||
"toast.context.noLineSelection.description": "Selecione primeiro um intervalo de linhas em uma aba de arquivo.",
|
||||
@@ -353,19 +376,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "URL compartilhada copiada para a área de transferência!",
|
||||
"toast.session.share.failed.title": "Falha ao compartilhar sessão",
|
||||
"toast.session.share.failed.description": "Ocorreu um erro ao compartilhar a sessão",
|
||||
|
||||
"toast.session.unshare.success.title": "Sessão não compartilhada",
|
||||
"toast.session.unshare.success.description": "Sessão deixou de ser compartilhada com sucesso!",
|
||||
"toast.session.unshare.failed.title": "Falha ao parar de compartilhar sessão",
|
||||
"toast.session.unshare.failed.description": "Ocorreu um erro ao parar de compartilhar a sessão",
|
||||
|
||||
"toast.session.listFailed.title": "Falha ao carregar sessões para {{project}}",
|
||||
|
||||
"toast.update.title": "Atualização disponível",
|
||||
"toast.update.description": "Uma nova versão do OpenCode ({{version}}) está disponível para instalação.",
|
||||
"toast.update.action.installRestart": "Instalar e reiniciar",
|
||||
"toast.update.action.notYet": "Agora não",
|
||||
|
||||
"error.page.title": "Algo deu errado",
|
||||
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
|
||||
"error.page.details.label": "Detalhes do Erro",
|
||||
@@ -376,12 +395,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "Por favor, reporte este erro para a equipe do OpenCode",
|
||||
"error.page.report.discord": "no Discord",
|
||||
"error.page.version": "Versão: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"Elemento raiz não encontrado. Você esqueceu de adicioná-lo ao seu index.html? Ou talvez o atributo id foi escrito incorretamente?",
|
||||
|
||||
"error.globalSync.connectFailed": "Não foi possível conectar ao servidor. Há um servidor executando em `{{url}}`?",
|
||||
|
||||
"directory.error.invalidUrl": "Diretório inválido na URL.",
|
||||
"error.chain.unknown": "Erro desconhecido",
|
||||
"error.chain.causedBy": "Causado por:",
|
||||
"error.chain.apiError": "Erro de API",
|
||||
@@ -403,21 +420,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "Falha ao analisar frontmatter em {{path}}:\n{{message}}",
|
||||
"error.chain.configInvalid": "Arquivo de configuração em {{path}} é inválido",
|
||||
"error.chain.configInvalidWithMessage": "Arquivo de configuração em {{path}} é inválido: {{message}}",
|
||||
|
||||
"notification.permission.title": "Permissão necessária",
|
||||
"notification.permission.description": "{{sessionTitle}} em {{projectName}} precisa de permissão",
|
||||
"notification.question.title": "Pergunta",
|
||||
"notification.question.description": "{{sessionTitle}} em {{projectName}} tem uma pergunta",
|
||||
"notification.action.goToSession": "Ir para sessão",
|
||||
|
||||
"notification.session.responseReady.title": "Resposta pronta",
|
||||
"notification.session.error.title": "Erro na sessão",
|
||||
"notification.session.error.fallbackDescription": "Ocorreu um erro",
|
||||
|
||||
"home.recentProjects": "Projetos recentes",
|
||||
"home.empty.title": "Nenhum projeto recente",
|
||||
"home.empty.description": "Comece abrindo um projeto local",
|
||||
|
||||
"session.tab.session": "Sessão",
|
||||
"session.tab.review": "Revisão",
|
||||
"session.tab.context": "Contexto",
|
||||
@@ -436,17 +449,18 @@ export const dict = {
|
||||
"session.messages.loadEarlier": "Carregar mensagens anteriores",
|
||||
"session.messages.loading": "Carregando mensagens...",
|
||||
"session.messages.jumpToLatest": "Ir para a mais recente",
|
||||
|
||||
"session.context.addToContext": "Adicionar {{selection}} ao contexto",
|
||||
|
||||
"session.new.worktree.main": "Branch principal",
|
||||
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
|
||||
"session.new.worktree.create": "Criar novo worktree",
|
||||
"session.new.lastModified": "Última modificação",
|
||||
|
||||
"session.header.search.placeholder": "Buscar {{project}}",
|
||||
"session.header.searchFiles": "Buscar arquivos",
|
||||
|
||||
"session.header.openIn": "Abrir em",
|
||||
"session.header.open.action": "Abrir {{app}}",
|
||||
"session.header.open.ariaLabel": "Abrir em {{app}}",
|
||||
"session.header.open.menu": "Opções de abertura",
|
||||
"session.header.open.copyPath": "Copiar caminho",
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Configurações de servidores",
|
||||
"status.popover.tab.servers": "Servidores",
|
||||
@@ -454,7 +468,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "Plugins",
|
||||
"status.popover.action.manageServers": "Gerenciar servidores",
|
||||
|
||||
"session.share.popover.title": "Publicar na web",
|
||||
"session.share.popover.description.shared":
|
||||
"Esta sessão é pública na web. Está acessível para qualquer pessoa com o link.",
|
||||
@@ -468,10 +481,8 @@ export const dict = {
|
||||
"session.share.action.view": "Ver",
|
||||
"session.share.copy.copied": "Copiado",
|
||||
"session.share.copy.copyLink": "Copiar link",
|
||||
|
||||
"lsp.tooltip.none": "Nenhum servidor LSP",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "Carregando prompt...",
|
||||
"terminal.loading": "Carregando terminal...",
|
||||
"terminal.title": "Terminal",
|
||||
@@ -480,7 +491,6 @@ export const dict = {
|
||||
"terminal.connectionLost.title": "Conexão Perdida",
|
||||
"terminal.connectionLost.description":
|
||||
"A conexão do terminal foi interrompida. Isso pode acontecer quando o servidor reinicia.",
|
||||
|
||||
"common.closeTab": "Fechar aba",
|
||||
"common.dismiss": "Descartar",
|
||||
"common.requestFailed": "Requisição falhou",
|
||||
@@ -494,7 +504,6 @@ export const dict = {
|
||||
"common.edit": "Editar",
|
||||
"common.loadMore": "Carregar mais",
|
||||
"common.key.esc": "ESC",
|
||||
|
||||
"sidebar.menu.toggle": "Alternar menu",
|
||||
"sidebar.nav.projectsAndSessions": "Projetos e sessões",
|
||||
"sidebar.settings": "Configurações",
|
||||
@@ -506,18 +515,19 @@ export const dict = {
|
||||
"sidebar.gettingStarted.line2": "Conecte qualquer provedor para usar modelos, incluindo Claude, GPT, Gemini etc.",
|
||||
"sidebar.project.recentSessions": "Sessões recentes",
|
||||
"sidebar.project.viewAllSessions": "Ver todas as sessões",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "Desktop",
|
||||
"settings.section.server": "Servidor",
|
||||
"settings.tab.general": "Geral",
|
||||
"settings.tab.shortcuts": "Atalhos",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.",
|
||||
"settings.general.section.appearance": "Aparência",
|
||||
"settings.general.section.notifications": "Notificações do sistema",
|
||||
"settings.general.section.updates": "Atualizações",
|
||||
"settings.general.section.sounds": "Efeitos sonoros",
|
||||
|
||||
"settings.general.section.display": "Tela",
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
||||
"settings.general.row.appearance.title": "Aparência",
|
||||
@@ -526,10 +536,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
||||
"settings.general.row.font.title": "Fonte",
|
||||
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
|
||||
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"No Linux com monitores de taxas de atualização mistas, Wayland nativo pode ser mais estável.",
|
||||
"settings.general.row.releaseNotes.title": "Notas da versão",
|
||||
"settings.general.row.releaseNotes.description": 'Mostrar pop-ups de "Novidades" após atualizações',
|
||||
|
||||
"settings.updates.row.startup.title": "Verificar atualizações ao iniciar",
|
||||
"settings.updates.row.startup.description": "Verificar atualizações automaticamente quando o OpenCode iniciar",
|
||||
"settings.updates.row.check.title": "Verificar atualizações",
|
||||
@@ -595,7 +607,6 @@ export const dict = {
|
||||
"sound.option.yup04": "Sim 04",
|
||||
"sound.option.yup05": "Sim 05",
|
||||
"sound.option.yup06": "Sim 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "Agente",
|
||||
"settings.general.notifications.agent.description":
|
||||
"Mostrar notificação do sistema quando o agente estiver completo ou precisar de atenção",
|
||||
@@ -604,14 +615,12 @@ export const dict = {
|
||||
"Mostrar notificação do sistema quando uma permissão for necessária",
|
||||
"settings.general.notifications.errors.title": "Erros",
|
||||
"settings.general.notifications.errors.description": "Mostrar notificação do sistema quando ocorrer um erro",
|
||||
|
||||
"settings.general.sounds.agent.title": "Agente",
|
||||
"settings.general.sounds.agent.description": "Reproduzir som quando o agente estiver completo ou precisar de atenção",
|
||||
"settings.general.sounds.permissions.title": "Permissões",
|
||||
"settings.general.sounds.permissions.description": "Reproduzir som quando uma permissão for necessária",
|
||||
"settings.general.sounds.errors.title": "Erros",
|
||||
"settings.general.sounds.errors.description": "Reproduzir som quando ocorrer um erro",
|
||||
|
||||
"settings.shortcuts.title": "Atalhos de teclado",
|
||||
"settings.shortcuts.reset.button": "Redefinir para padrões",
|
||||
"settings.shortcuts.reset.toast.title": "Atalhos redefinidos",
|
||||
@@ -622,14 +631,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "Pressione teclas",
|
||||
"settings.shortcuts.search.placeholder": "Buscar atalhos",
|
||||
"settings.shortcuts.search.empty": "Nenhum atalho encontrado",
|
||||
|
||||
"settings.shortcuts.group.general": "Geral",
|
||||
"settings.shortcuts.group.session": "Sessão",
|
||||
"settings.shortcuts.group.navigation": "Navegação",
|
||||
"settings.shortcuts.group.modelAndAgent": "Modelo e agente",
|
||||
"settings.shortcuts.group.terminal": "Terminal",
|
||||
"settings.shortcuts.group.prompt": "Prompt",
|
||||
|
||||
"settings.providers.title": "Provedores",
|
||||
"settings.providers.description": "Configurações de provedores estarão disponíveis aqui.",
|
||||
"settings.providers.section.connected": "Provedores conectados",
|
||||
@@ -647,16 +654,13 @@ export const dict = {
|
||||
"settings.commands.description": "Configurações de comandos estarão disponíveis aqui.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "Configurações de MCP estarão disponíveis aqui.",
|
||||
|
||||
"settings.permissions.title": "Permissões",
|
||||
"settings.permissions.description": "Controle quais ferramentas o servidor pode usar por padrão.",
|
||||
"settings.permissions.section.tools": "Ferramentas",
|
||||
"settings.permissions.toast.updateFailed.title": "Falha ao atualizar permissões",
|
||||
|
||||
"settings.permissions.action.allow": "Permitir",
|
||||
"settings.permissions.action.ask": "Perguntar",
|
||||
"settings.permissions.action.deny": "Negar",
|
||||
|
||||
"settings.permissions.tool.read.title": "Ler",
|
||||
"settings.permissions.tool.read.description": "Ler um arquivo (corresponde ao caminho do arquivo)",
|
||||
"settings.permissions.tool.edit.title": "Editar",
|
||||
@@ -690,12 +694,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "Acessar arquivos fora do diretório do projeto",
|
||||
"settings.permissions.tool.doom_loop.title": "Loop Infinito",
|
||||
"settings.permissions.tool.doom_loop.description": "Detectar chamadas de ferramentas repetidas com entrada idêntica",
|
||||
|
||||
"session.delete.failed.title": "Falha ao excluir sessão",
|
||||
"session.delete.title": "Excluir sessão",
|
||||
"session.delete.confirm": 'Excluir sessão "{{name}}"?',
|
||||
"session.delete.button": "Excluir sessão",
|
||||
|
||||
"workspace.new": "Novo espaço de trabalho",
|
||||
"workspace.type.local": "local",
|
||||
"workspace.type.sandbox": "sandbox",
|
||||
|
||||
@@ -150,6 +150,44 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} povezan",
|
||||
"provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.",
|
||||
|
||||
"provider.custom.title": "Prilagođeni provajder",
|
||||
"provider.custom.description.prefix": "Konfiguriši OpenAI-kompatibilnog provajdera. Pogledaj ",
|
||||
"provider.custom.description.link": "dokumentaciju za konfiguraciju provajdera",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID provajdera",
|
||||
"provider.custom.field.providerID.placeholder": "mojprovajder",
|
||||
"provider.custom.field.providerID.description": "Mala slova, brojevi, crtice ili donje crte",
|
||||
"provider.custom.field.name.label": "Prikazano ime",
|
||||
"provider.custom.field.name.placeholder": "Moj AI Provajder",
|
||||
"provider.custom.field.baseURL.label": "Bazni URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.mojprovajder.com/v1",
|
||||
"provider.custom.field.apiKey.label": "API ključ",
|
||||
"provider.custom.field.apiKey.placeholder": "API ključ",
|
||||
"provider.custom.field.apiKey.description":
|
||||
"Opcionalno. Ostavi prazno ako upravljaš autentifikacijom putem zaglavlja.",
|
||||
"provider.custom.models.label": "Modeli",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "Ime",
|
||||
"provider.custom.models.name.placeholder": "Prikazano ime",
|
||||
"provider.custom.models.remove": "Ukloni model",
|
||||
"provider.custom.models.add": "Dodaj model",
|
||||
"provider.custom.headers.label": "Zaglavlja (opcionalno)",
|
||||
"provider.custom.headers.key.label": "Zaglavlje",
|
||||
"provider.custom.headers.key.placeholder": "Ime-Zaglavlja",
|
||||
"provider.custom.headers.value.label": "Vrijednost",
|
||||
"provider.custom.headers.value.placeholder": "vrijednost",
|
||||
"provider.custom.headers.remove": "Ukloni zaglavlje",
|
||||
"provider.custom.headers.add": "Dodaj zaglavlje",
|
||||
"provider.custom.error.providerID.required": "ID provajdera je obavezan",
|
||||
"provider.custom.error.providerID.format": "Koristi mala slova, brojeve, crtice ili donje crte",
|
||||
"provider.custom.error.providerID.exists": "Taj ID provajdera već postoji",
|
||||
"provider.custom.error.name.required": "Prikazano ime je obavezno",
|
||||
"provider.custom.error.baseURL.required": "Bazni URL je obavezan",
|
||||
"provider.custom.error.baseURL.format": "Mora početi sa http:// ili https://",
|
||||
"provider.custom.error.required": "Obavezno",
|
||||
"provider.custom.error.duplicate": "Duplikat",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} odspojen",
|
||||
"provider.disconnect.toast.disconnected.description": "{{provider}} modeli više nisu dostupni.",
|
||||
|
||||
@@ -408,6 +446,7 @@ export const dict = {
|
||||
"Korijenski element nije pronađen. Da li si zaboravio da ga dodaš u index.html? Ili je možda id atribut pogrešno napisan?",
|
||||
|
||||
"error.globalSync.connectFailed": "Nije moguće povezati se na server. Da li server radi na `{{url}}`?",
|
||||
"directory.error.invalidUrl": "Nevažeći direktorij u URL-u.",
|
||||
|
||||
"error.chain.unknown": "Nepoznata greška",
|
||||
"error.chain.causedBy": "Uzrok:",
|
||||
@@ -417,7 +456,7 @@ export const dict = {
|
||||
"error.chain.responseBody": "Tijelo odgovora:\n{{body}}",
|
||||
"error.chain.didYouMean": "Da li si mislio: {{suggestions}}",
|
||||
"error.chain.modelNotFound": "Model nije pronađen: {{provider}}/{{model}}",
|
||||
"error.chain.checkConfig": "Provjeri konfiguraciju (opencode.json) - nazive provajdera/modela",
|
||||
"error.chain.checkConfig": "Provjeri konfiguraciju (opencode.json) provider/model names",
|
||||
"error.chain.mcpFailed": 'MCP server "{{name}}" nije uspio. Napomena: OpenCode još ne podržava MCP autentifikaciju.',
|
||||
"error.chain.providerAuthFailed": "Autentifikacija provajdera nije uspjela ({{provider}}): {{message}}",
|
||||
"error.chain.providerInitFailed":
|
||||
@@ -474,6 +513,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "Pretraži {{project}}",
|
||||
"session.header.searchFiles": "Pretraži datoteke",
|
||||
"session.header.openIn": "Otvori u",
|
||||
"session.header.open.action": "Otvori {{app}}",
|
||||
"session.header.open.ariaLabel": "Otvori u {{app}}",
|
||||
"session.header.open.menu": "Opcije otvaranja",
|
||||
"session.header.open.copyPath": "Kopiraj putanju",
|
||||
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Konfiguracije servera",
|
||||
@@ -539,11 +583,15 @@ export const dict = {
|
||||
"settings.section.server": "Server",
|
||||
"settings.tab.general": "Opšte",
|
||||
"settings.tab.shortcuts": "Prečice",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integracija",
|
||||
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
|
||||
|
||||
"settings.general.section.appearance": "Izgled",
|
||||
"settings.general.section.notifications": "Sistemske obavijesti",
|
||||
"settings.general.section.updates": "Ažuriranja",
|
||||
"settings.general.section.sounds": "Zvučni efekti",
|
||||
"settings.general.section.display": "Prikaz",
|
||||
|
||||
"settings.general.row.language.title": "Jezik",
|
||||
"settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
|
||||
@@ -554,6 +602,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Font",
|
||||
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
|
||||
|
||||
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
||||
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"Na Linuxu sa monitorima miješanih stopa osvježavanja, nativni Wayland može biti stabilniji.",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "Bilješke o izdanju",
|
||||
"settings.general.row.releaseNotes.description": 'Prikaži iskačuće prozore "Šta je novo" nakon ažuriranja',
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Accepter ændringer automatisk",
|
||||
"command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer",
|
||||
"command.workspace.toggle": "Skift arbejdsområder",
|
||||
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbejdsområder i sidebjælken",
|
||||
"command.session.undo": "Fortryd",
|
||||
"command.session.undo.description": "Fortryd den sidste besked",
|
||||
"command.session.redo": "Omgør",
|
||||
@@ -95,9 +96,13 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "Populære",
|
||||
"dialog.provider.group.other": "Andre",
|
||||
"dialog.provider.tag.recommended": "Anbefalet",
|
||||
"dialog.provider.anthropic.note": "Forbind med Claude Pro/Max eller API-nøgle",
|
||||
"dialog.provider.openai.note": "Forbind med ChatGPT Pro/Plus eller API-nøgle",
|
||||
"dialog.provider.copilot.note": "Forbind med Copilot eller API-nøgle",
|
||||
"dialog.provider.opencode.note": "Udvalgte modeller inklusive Claude, GPT, Gemini og flere",
|
||||
"dialog.provider.anthropic.note": "Direkte adgang til Claude-modeller, inklusive Pro og Max",
|
||||
"dialog.provider.copilot.note": "Claude-modeller til kodningsassistance",
|
||||
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
|
||||
"dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar",
|
||||
"dialog.provider.openrouter.note": "Få adgang til alle understøttede modeller fra én udbyder",
|
||||
"dialog.provider.vercel.note": "Samlet adgang til AI-modeller med smart routing",
|
||||
|
||||
"dialog.model.select.title": "Vælg model",
|
||||
"dialog.model.search.placeholder": "Søg modeller",
|
||||
@@ -145,6 +150,43 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} forbundet",
|
||||
"provider.connect.toast.connected.description": "{{provider}} modeller er nu tilgængelige.",
|
||||
|
||||
"provider.custom.title": "Brugerdefineret udbyder",
|
||||
"provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel udbyder. Se ",
|
||||
"provider.custom.description.link": "dokumentation for udbyderkonfiguration",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "Udbyder-ID",
|
||||
"provider.custom.field.providerID.placeholder": "minudbyder",
|
||||
"provider.custom.field.providerID.description": "Små bogstaver, tal, bindestreger eller understregninger",
|
||||
"provider.custom.field.name.label": "Visningsnavn",
|
||||
"provider.custom.field.name.placeholder": "Min AI-udbyder",
|
||||
"provider.custom.field.baseURL.label": "Basis-URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.minudbyder.dk/v1",
|
||||
"provider.custom.field.apiKey.label": "API-nøgle",
|
||||
"provider.custom.field.apiKey.placeholder": "API-nøgle",
|
||||
"provider.custom.field.apiKey.description": "Valgfri. Lad være tom, hvis du administrerer godkendelse via headers.",
|
||||
"provider.custom.models.label": "Modeller",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "Navn",
|
||||
"provider.custom.models.name.placeholder": "Visningsnavn",
|
||||
"provider.custom.models.remove": "Fjern model",
|
||||
"provider.custom.models.add": "Tilføj model",
|
||||
"provider.custom.headers.label": "Headers (valgfri)",
|
||||
"provider.custom.headers.key.label": "Header",
|
||||
"provider.custom.headers.key.placeholder": "Header-Navn",
|
||||
"provider.custom.headers.value.label": "Værdi",
|
||||
"provider.custom.headers.value.placeholder": "værdi",
|
||||
"provider.custom.headers.remove": "Fjern header",
|
||||
"provider.custom.headers.add": "Tilføj header",
|
||||
"provider.custom.error.providerID.required": "Udbyder-ID er påkrævet",
|
||||
"provider.custom.error.providerID.format": "Brug små bogstaver, tal, bindestreger eller understregninger",
|
||||
"provider.custom.error.providerID.exists": "Dette udbyder-ID findes allerede",
|
||||
"provider.custom.error.name.required": "Visningsnavn er påkrævet",
|
||||
"provider.custom.error.baseURL.required": "Basis-URL er påkrævet",
|
||||
"provider.custom.error.baseURL.format": "Skal starte med http:// eller https://",
|
||||
"provider.custom.error.required": "Påkrævet",
|
||||
"provider.custom.error.duplicate": "Duplikeret",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} frakoblet",
|
||||
"provider.disconnect.toast.disconnected.description": "Modeller fra {{provider}} er ikke længere tilgængelige.",
|
||||
"model.tag.free": "Gratis",
|
||||
@@ -166,6 +208,7 @@ export const dict = {
|
||||
"model.tooltip.context": "Kontekstgrænse {{limit}}",
|
||||
"common.search.placeholder": "Søg",
|
||||
"common.goBack": "Gå tilbage",
|
||||
"common.goForward": "Naviger fremad",
|
||||
"common.loading": "Indlæser",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Annuller",
|
||||
@@ -308,10 +351,10 @@ export const dict = {
|
||||
"context.stats.provider": "Udbyder",
|
||||
"context.stats.model": "Model",
|
||||
"context.stats.limit": "Kontekstgrænse",
|
||||
"context.stats.totalTokens": "Total Tokens",
|
||||
"context.stats.totalTokens": "Samlede tokens",
|
||||
"context.stats.usage": "Forbrug",
|
||||
"context.stats.inputTokens": "Input Tokens",
|
||||
"context.stats.outputTokens": "Output Tokens",
|
||||
"context.stats.inputTokens": "Input-tokens",
|
||||
"context.stats.outputTokens": "Output-tokens",
|
||||
"context.stats.reasoningTokens": "Tænke Tokens",
|
||||
"context.stats.cacheTokens": "Cache Tokens (læs/skriv)",
|
||||
"context.stats.userMessages": "Brugerbeskeder",
|
||||
@@ -326,6 +369,23 @@ export const dict = {
|
||||
"context.usage.clickToView": "Klik for at se kontekst",
|
||||
"context.usage.view": "Se kontekstforbrug",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "Sprog",
|
||||
"toast.language.description": "Skiftede til {{language}}",
|
||||
|
||||
@@ -383,6 +443,7 @@ export const dict = {
|
||||
"Rodelement ikke fundet. Har du glemt at tilføje det til din index.html? Eller måske er id-attributten stavet forkert?",
|
||||
|
||||
"error.globalSync.connectFailed": "Kunne ikke forbinde til server. Kører der en server på `{{url}}`?",
|
||||
"directory.error.invalidUrl": "Ugyldig mappe i URL.",
|
||||
|
||||
"error.chain.unknown": "Ukendt fejl",
|
||||
"error.chain.causedBy": "Forårsaget af:",
|
||||
@@ -447,6 +508,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "Søg {{project}}",
|
||||
"session.header.searchFiles": "Søg efter filer",
|
||||
"session.header.openIn": "Åbn i",
|
||||
"session.header.open.action": "Åbn {{app}}",
|
||||
"session.header.open.ariaLabel": "Åbn i {{app}}",
|
||||
"session.header.open.menu": "Åbningsmuligheder",
|
||||
"session.header.open.copyPath": "Kopier sti",
|
||||
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Serverkonfigurationer",
|
||||
@@ -512,11 +578,15 @@ export const dict = {
|
||||
"settings.section.server": "Server",
|
||||
"settings.tab.general": "Generelt",
|
||||
"settings.tab.shortcuts": "Genveje",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Udseende",
|
||||
"settings.general.section.notifications": "Systemmeddelelser",
|
||||
"settings.general.section.updates": "Opdateringer",
|
||||
"settings.general.section.sounds": "Lydeffekter",
|
||||
"settings.general.section.display": "Skærm",
|
||||
|
||||
"settings.general.row.language.title": "Sprog",
|
||||
"settings.general.row.language.description": "Ændr visningssproget for OpenCode",
|
||||
@@ -527,6 +597,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Skrifttype",
|
||||
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
|
||||
|
||||
"settings.general.row.wayland.title": "Brug native Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"På Linux med skærme med blandet opdateringshastighed kan native Wayland være mere stabilt.",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "Udgivelsesnoter",
|
||||
"settings.general.row.releaseNotes.description": 'Vis "Hvad er nyt"-popups efter opdateringer',
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@ export const dict = {
|
||||
"command.category.agent": "Agent",
|
||||
"command.category.permissions": "Berechtigungen",
|
||||
"command.category.workspace": "Arbeitsbereich",
|
||||
|
||||
"command.category.settings": "Einstellungen",
|
||||
"theme.scheme.system": "System",
|
||||
"theme.scheme.light": "Hell",
|
||||
"theme.scheme.dark": "Dunkel",
|
||||
|
||||
"command.sidebar.toggle": "Seitenleiste umschalten",
|
||||
"command.project.open": "Projekt öffnen",
|
||||
"command.provider.connect": "Anbieter verbinden",
|
||||
@@ -35,17 +33,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "Vorherige ungelesene Sitzung",
|
||||
"command.session.next.unseen": "Nächste ungelesene Sitzung",
|
||||
"command.session.archive": "Sitzung archivieren",
|
||||
|
||||
"command.palette": "Befehlspalette",
|
||||
|
||||
"command.theme.cycle": "Thema wechseln",
|
||||
"command.theme.set": "Thema verwenden: {{theme}}",
|
||||
"command.theme.scheme.cycle": "Farbschema wechseln",
|
||||
"command.theme.scheme.set": "Farbschema verwenden: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "Sprache wechseln",
|
||||
"command.language.set": "Sprache verwenden: {{language}}",
|
||||
|
||||
"command.session.new": "Neue Sitzung",
|
||||
"command.file.open": "Datei öffnen",
|
||||
"command.tab.close": "Tab schließen",
|
||||
@@ -76,6 +70,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren",
|
||||
"command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen",
|
||||
"command.workspace.toggle": "Arbeitsbereiche umschalten",
|
||||
"command.workspace.toggle.description": "Mehrere Arbeitsbereiche in der Seitenleiste aktivieren oder deaktivieren",
|
||||
"command.session.undo": "Rückgängig",
|
||||
"command.session.undo.description": "Letzte Nachricht rückgängig machen",
|
||||
"command.session.redo": "Wiederherstellen",
|
||||
@@ -88,32 +83,30 @@ export const dict = {
|
||||
"command.session.share.description": "Diese Sitzung teilen und URL in die Zwischenablage kopieren",
|
||||
"command.session.unshare": "Teilen der Sitzung aufheben",
|
||||
"command.session.unshare.description": "Teilen dieser Sitzung beenden",
|
||||
|
||||
"palette.search.placeholder": "Dateien, Befehle und Sitzungen durchsuchen",
|
||||
"palette.empty": "Keine Ergebnisse gefunden",
|
||||
"palette.group.commands": "Befehle",
|
||||
"palette.group.files": "Dateien",
|
||||
|
||||
"dialog.provider.search.placeholder": "Anbieter durchsuchen",
|
||||
"dialog.provider.empty": "Keine Anbieter gefunden",
|
||||
"dialog.provider.group.popular": "Beliebt",
|
||||
"dialog.provider.group.other": "Andere",
|
||||
"dialog.provider.tag.recommended": "Empfohlen",
|
||||
"dialog.provider.opencode.note": "Kuratierte Modelle inklusive Claude, GPT, Gemini und mehr",
|
||||
"dialog.provider.anthropic.note": "Mit Claude Pro/Max oder API-Schlüssel verbinden",
|
||||
"dialog.provider.openai.note": "Mit ChatGPT Pro/Plus oder API-Schlüssel verbinden",
|
||||
"dialog.provider.copilot.note": "Mit Copilot oder API-Schlüssel verbinden",
|
||||
|
||||
"dialog.provider.openai.note": "Mit ChatGPT Pro/Plus oder API-Schlüssel verbinden",
|
||||
"dialog.provider.google.note": "Gemini-Modelle für schnelle, strukturierte Antworten",
|
||||
"dialog.provider.openrouter.note": "Zugriff auf alle unterstützten Modelle über einen Anbieter",
|
||||
"dialog.provider.vercel.note": "Einheitlicher Zugriff auf KI-Modelle mit intelligentem Routing",
|
||||
"dialog.model.select.title": "Modell auswählen",
|
||||
"dialog.model.search.placeholder": "Modelle durchsuchen",
|
||||
"dialog.model.empty": "Keine Modellergebnisse",
|
||||
"dialog.model.manage": "Modelle verwalten",
|
||||
"dialog.model.manage.description": "Anpassen, welche Modelle in der Modellauswahl erscheinen.",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von OpenCode",
|
||||
"dialog.model.unpaid.addMore.title": "Weitere Modelle von beliebten Anbietern hinzufügen",
|
||||
|
||||
"dialog.provider.viewAll": "Mehr Anbieter anzeigen",
|
||||
|
||||
"provider.connect.title": "{{provider}} verbinden",
|
||||
"provider.connect.title.anthropicProMax": "Mit Claude Pro/Max anmelden",
|
||||
"provider.connect.selectMethod": "Anmeldemethode für {{provider}} auswählen.",
|
||||
@@ -148,7 +141,6 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "Bestätigungscode",
|
||||
"provider.connect.toast.connected.title": "{{provider}} verbunden",
|
||||
"provider.connect.toast.connected.description": "{{provider}} Modelle sind jetzt verfügbar.",
|
||||
|
||||
"provider.custom.title": "Benutzerdefinierter Anbieter",
|
||||
"provider.custom.description.prefix": "Konfigurieren Sie einen OpenAI-kompatiblen Anbieter. Siehe die ",
|
||||
"provider.custom.description.link": "Anbieter-Konfigurationsdokumente",
|
||||
@@ -186,12 +178,10 @@ export const dict = {
|
||||
"provider.custom.error.baseURL.format": "Muss mit http:// oder https:// beginnen",
|
||||
"provider.custom.error.required": "Erforderlich",
|
||||
"provider.custom.error.duplicate": "Duplikat",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} getrennt",
|
||||
"provider.disconnect.toast.disconnected.description": "Die {{provider}}-Modelle sind nicht mehr verfügbar.",
|
||||
"model.tag.free": "Kostenlos",
|
||||
"model.tag.latest": "Neueste",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -201,13 +191,14 @@ export const dict = {
|
||||
"model.input.image": "Bild",
|
||||
"model.input.audio": "Audio",
|
||||
"model.input.video": "Video",
|
||||
"model.input.pdf": "pdf",
|
||||
"model.input.pdf": "PDF",
|
||||
"model.tooltip.allows": "Erlaubt: {{inputs}}",
|
||||
"model.tooltip.reasoning.allowed": "Erlaubt Reasoning",
|
||||
"model.tooltip.reasoning.none": "Kein Reasoning",
|
||||
"model.tooltip.context": "Kontextlimit {{limit}}",
|
||||
"common.search.placeholder": "Suchen",
|
||||
"common.goBack": "Zurück",
|
||||
"common.goForward": "Vorwärts navigieren",
|
||||
"common.loading": "Laden",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Abbrechen",
|
||||
@@ -218,14 +209,12 @@ export const dict = {
|
||||
"common.saving": "Speichert...",
|
||||
"common.default": "Standard",
|
||||
"common.attachment": "Anhang",
|
||||
|
||||
"prompt.placeholder.shell": "Shell-Befehl eingeben...",
|
||||
"prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…",
|
||||
"prompt.placeholder.summarizeComment": "Kommentar zusammenfassen…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell.exit": "esc zum Verlassen",
|
||||
|
||||
"prompt.example.1": "Ein TODO in der Codebasis beheben",
|
||||
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
|
||||
"prompt.example.3": "Fehlerhafte Tests beheben",
|
||||
@@ -251,14 +240,13 @@ export const dict = {
|
||||
"prompt.example.23": "Paginierung zu dieser Liste hinzufügen",
|
||||
"prompt.example.24": "CLI-Befehl erstellen für...",
|
||||
"prompt.example.25": "Wie funktionieren Umgebungsvariablen hier?",
|
||||
|
||||
"prompt.popover.emptyResults": "Keine passenden Ergebnisse",
|
||||
"prompt.popover.emptyCommands": "Keine passenden Befehle",
|
||||
"prompt.dropzone.label": "Bilder oder PDFs hier ablegen",
|
||||
"prompt.dropzone.file.label": "Ablegen zum @Erwähnen der Datei",
|
||||
"prompt.slash.badge.custom": "benutzerdefiniert",
|
||||
"prompt.slash.badge.skill": "skill",
|
||||
"prompt.slash.badge.mcp": "mcp",
|
||||
"prompt.slash.badge.skill": "Skill",
|
||||
"prompt.slash.badge.mcp": "MCP",
|
||||
"prompt.context.active": "aktiv",
|
||||
"prompt.context.includeActiveFile": "Aktive Datei einbeziehen",
|
||||
"prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
|
||||
@@ -267,7 +255,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Anhang entfernen",
|
||||
"prompt.action.send": "Senden",
|
||||
"prompt.action.stop": "Stopp",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Nicht unterstütztes Einfügen",
|
||||
"prompt.toast.pasteUnsupported.description": "Hier können nur Bilder oder PDFs eingefügt werden.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
|
||||
@@ -279,24 +266,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "Befehl konnte nicht gesendet werden",
|
||||
"prompt.toast.promptSendFailed.title": "Eingabe konnte nicht gesendet werden",
|
||||
"prompt.toast.promptSendFailed.description": "Sitzung konnte nicht abgerufen werden",
|
||||
|
||||
"dialog.mcp.title": "MCPs",
|
||||
"dialog.mcp.description": "{{enabled}} von {{total}} aktiviert",
|
||||
"dialog.mcp.empty": "Keine MCPs konfiguriert",
|
||||
|
||||
"dialog.lsp.empty": "LSPs automatisch nach Dateityp erkannt",
|
||||
"dialog.plugins.empty": "In opencode.json konfigurierte Plugins",
|
||||
|
||||
"mcp.status.connected": "verbunden",
|
||||
"mcp.status.failed": "fehlgeschlagen",
|
||||
"mcp.status.needs_auth": "benötigt Authentifizierung",
|
||||
"mcp.status.disabled": "deaktiviert",
|
||||
|
||||
"dialog.fork.empty": "Keine Nachrichten zum Abzweigen vorhanden",
|
||||
|
||||
"dialog.directory.search.placeholder": "Ordner durchsuchen",
|
||||
"dialog.directory.empty": "Keine Ordner gefunden",
|
||||
|
||||
"dialog.server.title": "Server",
|
||||
"dialog.server.description": "Wechseln Sie den OpenCode-Server, mit dem sich diese App verbindet.",
|
||||
"dialog.server.search.placeholder": "Server durchsuchen",
|
||||
@@ -314,14 +295,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "Aktuellen Server als Standard setzen",
|
||||
"dialog.server.default.clear": "Löschen",
|
||||
"dialog.server.action.remove": "Server entfernen",
|
||||
|
||||
"dialog.server.menu.edit": "Bearbeiten",
|
||||
"dialog.server.menu.default": "Als Standard festlegen",
|
||||
"dialog.server.menu.defaultRemove": "Standard entfernen",
|
||||
"dialog.server.menu.delete": "Löschen",
|
||||
"dialog.server.current": "Aktueller Server",
|
||||
"dialog.server.status.default": "Standard",
|
||||
|
||||
"dialog.project.edit.title": "Projekt bearbeiten",
|
||||
"dialog.project.edit.name": "Name",
|
||||
"dialog.project.edit.icon": "Icon",
|
||||
@@ -330,7 +309,6 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Empfohlen: 128x128px",
|
||||
"dialog.project.edit.color": "Farbe",
|
||||
"dialog.project.edit.color.select": "{{color}}-Farbe auswählen",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "Startup-Skript für Arbeitsbereich",
|
||||
"dialog.project.edit.worktree.startup.description":
|
||||
"Wird nach dem Erstellen eines neuen Arbeitsbereichs (Worktree) ausgeführt.",
|
||||
@@ -343,10 +321,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "Assistent",
|
||||
"context.breakdown.tool": "Werkzeugaufrufe",
|
||||
"context.breakdown.other": "Andere",
|
||||
|
||||
"context.systemPrompt.title": "System-Prompt",
|
||||
"context.rawMessages.title": "Rohdaten der Nachrichten",
|
||||
|
||||
"context.stats.session": "Sitzung",
|
||||
"context.stats.messages": "Nachrichten",
|
||||
"context.stats.provider": "Anbieter",
|
||||
@@ -363,29 +339,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "Gesamtkosten",
|
||||
"context.stats.sessionCreated": "Sitzung erstellt",
|
||||
"context.stats.lastActivity": "Letzte Aktivität",
|
||||
|
||||
"context.usage.tokens": "Token",
|
||||
"context.usage.usage": "Nutzung",
|
||||
"context.usage.cost": "Kosten",
|
||||
"context.usage.clickToView": "Klicken, um Kontext anzuzeigen",
|
||||
"context.usage.view": "Kontextnutzung anzeigen",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "Sprache",
|
||||
"toast.language.description": "Zu {{language}} gewechselt",
|
||||
|
||||
"toast.theme.title": "Thema gewechselt",
|
||||
"toast.scheme.title": "Farbschema",
|
||||
|
||||
"toast.workspace.enabled.title": "Arbeitsbereiche aktiviert",
|
||||
"toast.workspace.enabled.description": "Mehrere Worktrees werden jetzt in der Seitenleiste angezeigt",
|
||||
"toast.workspace.disabled.title": "Arbeitsbereiche deaktiviert",
|
||||
"toast.workspace.disabled.description": "Nur der Haupt-Worktree wird in der Seitenleiste angezeigt",
|
||||
"toast.permissions.autoaccept.on.title": "Änderungen werden automatisch akzeptiert",
|
||||
"toast.permissions.autoaccept.on.description": "Bearbeitungs- und Schreibrechte werden automatisch genehmigt",
|
||||
"toast.permissions.autoaccept.off.title": "Automatische Annahme von Änderungen gestoppt",
|
||||
"toast.permissions.autoaccept.off.description": "Bearbeitungs- und Schreibrechte erfordern Genehmigung",
|
||||
|
||||
"toast.model.none.title": "Kein Modell ausgewählt",
|
||||
"toast.model.none.description": "Verbinden Sie einen Anbieter, um diese Sitzung zusammenzufassen",
|
||||
|
||||
"toast.file.loadFailed.title": "Datei konnte nicht geladen werden",
|
||||
|
||||
"toast.file.listFailed.title": "Dateien konnten nicht aufgelistet werden",
|
||||
"toast.context.noLineSelection.title": "Keine Zeilenauswahl",
|
||||
"toast.context.noLineSelection.description": "Wählen Sie zuerst einen Zeilenbereich in einem Datei-Tab aus.",
|
||||
@@ -394,19 +383,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "Teilen-URL in die Zwischenablage kopiert!",
|
||||
"toast.session.share.failed.title": "Sitzung konnte nicht geteilt werden",
|
||||
"toast.session.share.failed.description": "Beim Teilen der Sitzung ist ein Fehler aufgetreten",
|
||||
|
||||
"toast.session.unshare.success.title": "Teilen der Sitzung aufgehoben",
|
||||
"toast.session.unshare.success.description": "Teilen der Sitzung erfolgreich aufgehoben!",
|
||||
"toast.session.unshare.failed.title": "Aufheben des Teilens fehlgeschlagen",
|
||||
"toast.session.unshare.failed.description": "Beim Aufheben des Teilens ist ein Fehler aufgetreten",
|
||||
|
||||
"toast.session.listFailed.title": "Sitzungen für {{project}} konnten nicht geladen werden",
|
||||
|
||||
"toast.update.title": "Update verfügbar",
|
||||
"toast.update.description": "Eine neue Version von OpenCode ({{version}}) ist zur Installation verfügbar.",
|
||||
"toast.update.action.installRestart": "Installieren und neu starten",
|
||||
"toast.update.action.notYet": "Noch nicht",
|
||||
|
||||
"error.page.title": "Etwas ist schiefgelaufen",
|
||||
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
|
||||
"error.page.details.label": "Fehlerdetails",
|
||||
@@ -417,13 +402,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "Bitte melden Sie diesen Fehler dem OpenCode-Team",
|
||||
"error.page.report.discord": "auf Discord",
|
||||
"error.page.version": "Version: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"Wurzelelement nicht gefunden. Haben Sie vergessen, es in Ihre index.html aufzunehmen? Oder wurde das id-Attribut falsch geschrieben?",
|
||||
|
||||
"error.globalSync.connectFailed": "Verbindung zum Server fehlgeschlagen. Läuft ein Server unter `{{url}}`?",
|
||||
"directory.error.invalidUrl": "Ungültiges Verzeichnis in der URL.",
|
||||
|
||||
"error.chain.unknown": "Unbekannter Fehler",
|
||||
"error.chain.causedBy": "Verursacht durch:",
|
||||
"error.chain.apiError": "API-Fehler",
|
||||
@@ -446,21 +428,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "Frontmatter in {{path}} konnte nicht geparst werden:\n{{message}}",
|
||||
"error.chain.configInvalid": "Konfigurationsdatei unter {{path}} ist ungültig",
|
||||
"error.chain.configInvalidWithMessage": "Konfigurationsdatei unter {{path}} ist ungültig: {{message}}",
|
||||
|
||||
"notification.permission.title": "Berechtigung erforderlich",
|
||||
"notification.permission.description": "{{sessionTitle}} in {{projectName}} benötigt Berechtigung",
|
||||
"notification.question.title": "Frage",
|
||||
"notification.question.description": "{{sessionTitle}} in {{projectName}} hat eine Frage",
|
||||
"notification.action.goToSession": "Zur Sitzung gehen",
|
||||
|
||||
"notification.session.responseReady.title": "Antwort bereit",
|
||||
"notification.session.error.title": "Sitzungsfehler",
|
||||
"notification.session.error.fallbackDescription": "Ein Fehler ist aufgetreten",
|
||||
|
||||
"home.recentProjects": "Letzte Projekte",
|
||||
"home.empty.title": "Keine letzten Projekte",
|
||||
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
|
||||
|
||||
"session.tab.session": "Sitzung",
|
||||
"session.tab.review": "Überprüfung",
|
||||
"session.tab.context": "Kontext",
|
||||
@@ -478,18 +456,19 @@ export const dict = {
|
||||
"session.messages.loadingEarlier": "Lade frühere Nachrichten...",
|
||||
"session.messages.loadEarlier": "Frühere Nachrichten laden",
|
||||
"session.messages.loading": "Lade Nachrichten...",
|
||||
|
||||
"session.messages.jumpToLatest": "Zum neuesten springen",
|
||||
"session.context.addToContext": "{{selection}} zum Kontext hinzufügen",
|
||||
|
||||
"session.new.worktree.main": "Haupt-Branch",
|
||||
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
|
||||
"session.new.worktree.create": "Neuen Worktree erstellen",
|
||||
"session.new.lastModified": "Zuletzt geändert",
|
||||
|
||||
"session.header.search.placeholder": "{{project}} durchsuchen",
|
||||
"session.header.searchFiles": "Dateien suchen",
|
||||
|
||||
"session.header.openIn": "Öffnen in",
|
||||
"session.header.open.action": "{{app}} öffnen",
|
||||
"session.header.open.ariaLabel": "In {{app}} öffnen",
|
||||
"session.header.open.menu": "Öffnen-Optionen",
|
||||
"session.header.open.copyPath": "Pfad kopieren",
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Serverkonfigurationen",
|
||||
"status.popover.tab.servers": "Server",
|
||||
@@ -497,7 +476,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "Plugins",
|
||||
"status.popover.action.manageServers": "Server verwalten",
|
||||
|
||||
"session.share.popover.title": "Im Web veröffentlichen",
|
||||
"session.share.popover.description.shared":
|
||||
"Diese Sitzung ist öffentlich im Web. Sie ist für jeden mit dem Link zugänglich.",
|
||||
@@ -511,16 +489,13 @@ export const dict = {
|
||||
"session.share.action.view": "Ansehen",
|
||||
"session.share.copy.copied": "Kopiert",
|
||||
"session.share.copy.copyLink": "Link kopieren",
|
||||
|
||||
"lsp.tooltip.none": "Keine LSP-Server",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "Lade Prompt...",
|
||||
"terminal.loading": "Lade Terminal...",
|
||||
"terminal.title": "Terminal",
|
||||
"terminal.title.numbered": "Terminal {{number}}",
|
||||
"terminal.close": "Terminal schließen",
|
||||
|
||||
"terminal.connectionLost.title": "Verbindung verloren",
|
||||
"terminal.connectionLost.description":
|
||||
"Die Terminalverbindung wurde unterbrochen. Das kann passieren, wenn der Server neu startet.",
|
||||
@@ -536,7 +511,6 @@ export const dict = {
|
||||
"common.close": "Schließen",
|
||||
"common.edit": "Bearbeiten",
|
||||
"common.loadMore": "Mehr laden",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
"sidebar.menu.toggle": "Menü umschalten",
|
||||
"sidebar.nav.projectsAndSessions": "Projekte und Sitzungen",
|
||||
@@ -550,18 +524,19 @@ export const dict = {
|
||||
"Verbinden Sie einen beliebigen Anbieter, um Modelle wie Claude, GPT, Gemini usw. zu nutzen.",
|
||||
"sidebar.project.recentSessions": "Letzte Sitzungen",
|
||||
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "Desktop",
|
||||
"settings.section.server": "Server",
|
||||
"settings.tab.general": "Allgemein",
|
||||
"settings.tab.shortcuts": "Tastenkombinationen",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL-Integration",
|
||||
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
|
||||
"settings.general.section.appearance": "Erscheinungsbild",
|
||||
"settings.general.section.notifications": "Systembenachrichtigungen",
|
||||
"settings.general.section.updates": "Updates",
|
||||
"settings.general.section.sounds": "Soundeffekte",
|
||||
|
||||
"settings.general.section.display": "Anzeige",
|
||||
"settings.general.row.language.title": "Sprache",
|
||||
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
||||
"settings.general.row.appearance.title": "Erscheinungsbild",
|
||||
@@ -570,10 +545,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
||||
"settings.general.row.font.title": "Schriftart",
|
||||
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
|
||||
|
||||
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
||||
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"Unter Linux mit Monitoren unterschiedlicher Bildwiederholraten kann natives Wayland stabiler sein.",
|
||||
"settings.general.row.releaseNotes.title": "Versionshinweise",
|
||||
"settings.general.row.releaseNotes.description": '"Neuigkeiten"-Pop-ups nach Updates anzeigen',
|
||||
|
||||
"settings.updates.row.startup.title": "Beim Start nach Updates suchen",
|
||||
"settings.updates.row.startup.description": "Beim Start von OpenCode automatisch nach Updates suchen",
|
||||
"settings.updates.row.check.title": "Nach Updates suchen",
|
||||
@@ -582,7 +559,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "Wird geprüft...",
|
||||
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
|
||||
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -648,14 +624,12 @@ export const dict = {
|
||||
"Systembenachrichtigung anzeigen, wenn eine Berechtigung erforderlich ist",
|
||||
"settings.general.notifications.errors.title": "Fehler",
|
||||
"settings.general.notifications.errors.description": "Systembenachrichtigung anzeigen, wenn ein Fehler auftritt",
|
||||
|
||||
"settings.general.sounds.agent.title": "Agent",
|
||||
"settings.general.sounds.agent.description": "Ton abspielen, wenn der Agent fertig ist oder Aufmerksamkeit benötigt",
|
||||
"settings.general.sounds.permissions.title": "Berechtigungen",
|
||||
"settings.general.sounds.permissions.description": "Ton abspielen, wenn eine Berechtigung erforderlich ist",
|
||||
"settings.general.sounds.errors.title": "Fehler",
|
||||
"settings.general.sounds.errors.description": "Ton abspielen, wenn ein Fehler auftritt",
|
||||
|
||||
"settings.shortcuts.title": "Tastenkombinationen",
|
||||
"settings.shortcuts.reset.button": "Auf Standard zurücksetzen",
|
||||
"settings.shortcuts.reset.toast.title": "Tastenkombinationen zurückgesetzt",
|
||||
@@ -666,14 +640,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "Tasten drücken",
|
||||
"settings.shortcuts.search.placeholder": "Tastenkürzel suchen",
|
||||
"settings.shortcuts.search.empty": "Keine Tastenkürzel gefunden",
|
||||
|
||||
"settings.shortcuts.group.general": "Allgemein",
|
||||
"settings.shortcuts.group.session": "Sitzung",
|
||||
"settings.shortcuts.group.navigation": "Navigation",
|
||||
"settings.shortcuts.group.modelAndAgent": "Modell und Agent",
|
||||
"settings.shortcuts.group.terminal": "Terminal",
|
||||
"settings.shortcuts.group.prompt": "Prompt",
|
||||
|
||||
"settings.providers.title": "Anbieter",
|
||||
"settings.providers.description": "Anbietereinstellungen können hier konfiguriert werden.",
|
||||
"settings.providers.section.connected": "Verbundene Anbieter",
|
||||
@@ -691,16 +663,13 @@ export const dict = {
|
||||
"settings.commands.description": "Befehlseinstellungen können hier konfiguriert werden.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "MCP-Einstellungen können hier konfiguriert werden.",
|
||||
|
||||
"settings.permissions.title": "Berechtigungen",
|
||||
"settings.permissions.description": "Steuern Sie, welche Tools der Server standardmäßig verwenden darf.",
|
||||
"settings.permissions.section.tools": "Tools",
|
||||
"settings.permissions.toast.updateFailed.title": "Berechtigungen konnten nicht aktualisiert werden",
|
||||
|
||||
"settings.permissions.action.allow": "Erlauben",
|
||||
"settings.permissions.action.ask": "Fragen",
|
||||
"settings.permissions.action.deny": "Verweigern",
|
||||
|
||||
"settings.permissions.tool.read.title": "Lesen",
|
||||
"settings.permissions.tool.read.description": "Lesen einer Datei (stimmt mit dem Dateipfad überein)",
|
||||
"settings.permissions.tool.edit.title": "Bearbeiten",
|
||||
@@ -734,12 +703,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "Zugriff auf Dateien außerhalb des Projektverzeichnisses",
|
||||
"settings.permissions.tool.doom_loop.title": "Doom Loop",
|
||||
"settings.permissions.tool.doom_loop.description": "Wiederholte Tool-Aufrufe mit identischer Eingabe erkennen",
|
||||
|
||||
"session.delete.failed.title": "Sitzung konnte nicht gelöscht werden",
|
||||
"session.delete.title": "Sitzung löschen",
|
||||
"session.delete.confirm": 'Sitzung "{{name}}" löschen?',
|
||||
"session.delete.button": "Sitzung löschen",
|
||||
|
||||
"workspace.new": "Neuer Arbeitsbereich",
|
||||
"workspace.type.local": "lokal",
|
||||
"workspace.type.sandbox": "Sandbox",
|
||||
|
||||
@@ -583,6 +583,9 @@ export const dict = {
|
||||
"settings.section.server": "Server",
|
||||
"settings.tab.general": "General",
|
||||
"settings.tab.shortcuts": "Shortcuts",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Appearance",
|
||||
"settings.general.section.notifications": "System notifications",
|
||||
|
||||
@@ -15,8 +15,8 @@ export const dict = {
|
||||
"command.category.agent": "Agente",
|
||||
"command.category.permissions": "Permisos",
|
||||
"command.category.workspace": "Espacio de trabajo",
|
||||
|
||||
"command.category.settings": "Ajustes",
|
||||
|
||||
"theme.scheme.system": "Sistema",
|
||||
"theme.scheme.light": "Claro",
|
||||
"theme.scheme.dark": "Oscuro",
|
||||
@@ -72,6 +72,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente",
|
||||
"command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente",
|
||||
"command.workspace.toggle": "Alternar espacios de trabajo",
|
||||
"command.workspace.toggle.description": "Habilitar o deshabilitar múltiples espacios de trabajo en la barra lateral",
|
||||
"command.session.undo": "Deshacer",
|
||||
"command.session.undo.description": "Deshacer el último mensaje",
|
||||
"command.session.redo": "Rehacer",
|
||||
@@ -95,9 +96,13 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "Popular",
|
||||
"dialog.provider.group.other": "Otro",
|
||||
"dialog.provider.tag.recommended": "Recomendado",
|
||||
"dialog.provider.anthropic.note": "Conectar con Claude Pro/Max o clave API",
|
||||
"dialog.provider.openai.note": "Conectar con ChatGPT Pro/Plus o clave API",
|
||||
"dialog.provider.copilot.note": "Conectar con Copilot o clave API",
|
||||
"dialog.provider.opencode.note": "Modelos seleccionados incluyendo Claude, GPT, Gemini y más",
|
||||
"dialog.provider.anthropic.note": "Acceso directo a modelos Claude, incluyendo Pro y Max",
|
||||
"dialog.provider.copilot.note": "Modelos Claude para asistencia de codificación",
|
||||
"dialog.provider.openai.note": "Modelos GPT para tareas de IA generales rápidas y capaces",
|
||||
"dialog.provider.google.note": "Modelos Gemini para respuestas rápidas y estructuradas",
|
||||
"dialog.provider.openrouter.note": "Accede a todos los modelos soportados desde un solo proveedor",
|
||||
"dialog.provider.vercel.note": "Acceso unificado a modelos de IA con enrutamiento inteligente",
|
||||
|
||||
"dialog.model.select.title": "Seleccionar modelo",
|
||||
"dialog.model.search.placeholder": "Buscar modelos",
|
||||
@@ -145,11 +150,48 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} conectado",
|
||||
"provider.connect.toast.connected.description": "Los modelos de {{provider}} ahora están disponibles para usar.",
|
||||
|
||||
"provider.custom.title": "Proveedor personalizado",
|
||||
"provider.custom.description.prefix": "Configurar un proveedor compatible con OpenAI. Ver la ",
|
||||
"provider.custom.description.link": "documentación de configuración del proveedor",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID del proveedor",
|
||||
"provider.custom.field.providerID.placeholder": "miproveedor",
|
||||
"provider.custom.field.providerID.description": "Letras minúsculas, números, guiones o guiones bajos",
|
||||
"provider.custom.field.name.label": "Nombre para mostrar",
|
||||
"provider.custom.field.name.placeholder": "Mi Proveedor de IA",
|
||||
"provider.custom.field.baseURL.label": "URL base",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.miproveedor.com/v1",
|
||||
"provider.custom.field.apiKey.label": "Clave API",
|
||||
"provider.custom.field.apiKey.placeholder": "Clave API",
|
||||
"provider.custom.field.apiKey.description": "Opcional. Dejar vacío si gestionas la autenticación mediante cabeceras.",
|
||||
"provider.custom.models.label": "Modelos",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "id-modelo",
|
||||
"provider.custom.models.name.label": "Nombre",
|
||||
"provider.custom.models.name.placeholder": "Nombre para mostrar",
|
||||
"provider.custom.models.remove": "Eliminar modelo",
|
||||
"provider.custom.models.add": "Añadir modelo",
|
||||
"provider.custom.headers.label": "Cabeceras (opcional)",
|
||||
"provider.custom.headers.key.label": "Cabecera",
|
||||
"provider.custom.headers.key.placeholder": "Nombre-Cabecera",
|
||||
"provider.custom.headers.value.label": "Valor",
|
||||
"provider.custom.headers.value.placeholder": "valor",
|
||||
"provider.custom.headers.remove": "Eliminar cabecera",
|
||||
"provider.custom.headers.add": "Añadir cabecera",
|
||||
"provider.custom.error.providerID.required": "El ID del proveedor es obligatorio",
|
||||
"provider.custom.error.providerID.format": "Usa letras minúsculas, números, guiones o guiones bajos",
|
||||
"provider.custom.error.providerID.exists": "Ese ID de proveedor ya existe",
|
||||
"provider.custom.error.name.required": "El nombre para mostrar es obligatorio",
|
||||
"provider.custom.error.baseURL.required": "La URL base es obligatoria",
|
||||
"provider.custom.error.baseURL.format": "Debe comenzar con http:// o https://",
|
||||
"provider.custom.error.required": "Obligatorio",
|
||||
"provider.custom.error.duplicate": "Duplicado",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} desconectado",
|
||||
"provider.disconnect.toast.disconnected.description": "Los modelos de {{provider}} ya no están disponibles.",
|
||||
|
||||
"model.tag.free": "Gratis",
|
||||
"model.tag.latest": "Último",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -164,8 +206,10 @@ export const dict = {
|
||||
"model.tooltip.reasoning.allowed": "Permite razonamiento",
|
||||
"model.tooltip.reasoning.none": "Sin razonamiento",
|
||||
"model.tooltip.context": "Límite de contexto {{limit}}",
|
||||
|
||||
"common.search.placeholder": "Buscar",
|
||||
"common.goBack": "Volver",
|
||||
"common.goForward": "Avanzar",
|
||||
"common.loading": "Cargando",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Cancelar",
|
||||
@@ -287,11 +331,11 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Recomendado: 128x128px",
|
||||
"dialog.project.edit.color": "Color",
|
||||
"dialog.project.edit.color.select": "Seleccionar color {{color}}",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "Script de inicio del espacio de trabajo",
|
||||
"dialog.project.edit.worktree.startup.description":
|
||||
"Se ejecuta después de crear un nuevo espacio de trabajo (árbol de trabajo).",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "p. ej. bun install",
|
||||
|
||||
"context.breakdown.title": "Desglose de Contexto",
|
||||
"context.breakdown.note":
|
||||
'Desglose aproximado de tokens de entrada. "Otro" incluye definiciones de herramientas y sobrecarga.',
|
||||
@@ -327,30 +371,48 @@ export const dict = {
|
||||
"context.usage.clickToView": "Haz clic para ver contexto",
|
||||
"context.usage.view": "Ver uso del contexto",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "Idioma",
|
||||
"toast.language.description": "Cambiado a {{language}}",
|
||||
|
||||
"toast.theme.title": "Tema cambiado",
|
||||
"toast.scheme.title": "Esquema de color",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Aceptando ediciones automáticamente",
|
||||
"toast.permissions.autoaccept.on.description": "Los permisos de edición y escritura serán aprobados automáticamente",
|
||||
"toast.permissions.autoaccept.off.title": "Se dejó de aceptar ediciones automáticamente",
|
||||
"toast.permissions.autoaccept.off.description": "Los permisos de edición y escritura requerirán aprobación",
|
||||
|
||||
"toast.workspace.enabled.title": "Espacios de trabajo habilitados",
|
||||
"toast.workspace.enabled.description": "Ahora se muestran varios worktrees en la barra lateral",
|
||||
"toast.workspace.disabled.title": "Espacios de trabajo deshabilitados",
|
||||
"toast.workspace.disabled.description": "Solo se muestra el worktree principal en la barra lateral",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Aceptando ediciones automáticamente",
|
||||
"toast.permissions.autoaccept.on.description": "Los permisos de edición y escritura serán aprobados automáticamente",
|
||||
"toast.permissions.autoaccept.off.title": "Se dejó de aceptar ediciones automáticamente",
|
||||
"toast.permissions.autoaccept.off.description": "Los permisos de edición y escritura requerirán aprobación",
|
||||
|
||||
"toast.model.none.title": "Ningún modelo seleccionado",
|
||||
"toast.model.none.description": "Conecta un proveedor para resumir esta sesión",
|
||||
|
||||
"toast.file.loadFailed.title": "Fallo al cargar archivo",
|
||||
|
||||
"toast.file.listFailed.title": "Fallo al listar archivos",
|
||||
|
||||
"toast.context.noLineSelection.title": "Sin selección de líneas",
|
||||
"toast.context.noLineSelection.description": "Primero selecciona un rango de líneas en una pestaña de archivo.",
|
||||
|
||||
"toast.session.share.copyFailed.title": "Fallo al copiar URL al portapapeles",
|
||||
"toast.session.share.success.title": "Sesión compartida",
|
||||
"toast.session.share.success.description": "¡URL compartida copiada al portapapeles!",
|
||||
@@ -384,6 +446,7 @@ export const dict = {
|
||||
"Elemento raíz no encontrado. ¿Olvidaste añadirlo a tu index.html? ¿O tal vez el atributo id está mal escrito?",
|
||||
|
||||
"error.globalSync.connectFailed": "No se pudo conectar al servidor. ¿Hay un servidor ejecutándose en `{{url}}`?",
|
||||
"directory.error.invalidUrl": "URL de directorio inválida.",
|
||||
|
||||
"error.chain.unknown": "Error desconocido",
|
||||
"error.chain.causedBy": "Causado por:",
|
||||
@@ -431,15 +494,17 @@ export const dict = {
|
||||
"session.review.loadingChanges": "Cargando cambios...",
|
||||
"session.review.empty": "No hay cambios en esta sesión aún",
|
||||
"session.review.noChanges": "Sin cambios",
|
||||
|
||||
"session.files.selectToOpen": "Selecciona un archivo para abrir",
|
||||
"session.files.all": "Todos los archivos",
|
||||
"session.files.binaryContent": "Archivo binario (el contenido no puede ser mostrado)",
|
||||
|
||||
"session.messages.renderEarlier": "Renderizar mensajes anteriores",
|
||||
"session.messages.loadingEarlier": "Cargando mensajes anteriores...",
|
||||
"session.messages.loadEarlier": "Cargar mensajes anteriores",
|
||||
"session.messages.loading": "Cargando mensajes...",
|
||||
|
||||
"session.messages.jumpToLatest": "Ir al último",
|
||||
|
||||
"session.context.addToContext": "Añadir {{selection}} al contexto",
|
||||
|
||||
"session.new.worktree.main": "Rama principal",
|
||||
@@ -449,6 +514,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "Buscar {{project}}",
|
||||
"session.header.searchFiles": "Buscar archivos",
|
||||
"session.header.openIn": "Abrir en",
|
||||
"session.header.open.action": "Abrir {{app}}",
|
||||
"session.header.open.ariaLabel": "Abrir en {{app}}",
|
||||
"session.header.open.menu": "Opciones de apertura",
|
||||
"session.header.open.copyPath": "Copiar ruta",
|
||||
|
||||
"status.popover.trigger": "Estado",
|
||||
"status.popover.ariaLabel": "Configuraciones del servidor",
|
||||
@@ -480,10 +550,10 @@ export const dict = {
|
||||
"terminal.title": "Terminal",
|
||||
"terminal.title.numbered": "Terminal {{number}}",
|
||||
"terminal.close": "Cerrar terminal",
|
||||
|
||||
"terminal.connectionLost.title": "Conexión perdida",
|
||||
"terminal.connectionLost.description":
|
||||
"La conexión del terminal se interrumpió. Esto puede ocurrir cuando el servidor se reinicia.",
|
||||
|
||||
"common.closeTab": "Cerrar pestaña",
|
||||
"common.dismiss": "Descartar",
|
||||
"common.requestFailed": "Solicitud fallida",
|
||||
@@ -496,8 +566,8 @@ export const dict = {
|
||||
"common.close": "Cerrar",
|
||||
"common.edit": "Editar",
|
||||
"common.loadMore": "Cargar más",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
|
||||
"sidebar.menu.toggle": "Alternar menú",
|
||||
"sidebar.nav.projectsAndSessions": "Proyectos y sesiones",
|
||||
"sidebar.settings": "Ajustes",
|
||||
@@ -511,15 +581,20 @@ export const dict = {
|
||||
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
|
||||
"settings.section.desktop": "Escritorio",
|
||||
"settings.section.server": "Servidor",
|
||||
"settings.tab.general": "General",
|
||||
"settings.tab.shortcuts": "Atajos",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "Integración con WSL",
|
||||
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Apariencia",
|
||||
"settings.general.section.notifications": "Notificaciones del sistema",
|
||||
"settings.general.section.updates": "Actualizaciones",
|
||||
"settings.general.section.sounds": "Efectos de sonido",
|
||||
"settings.general.section.display": "Pantalla",
|
||||
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
|
||||
@@ -528,7 +603,12 @@ export const dict = {
|
||||
"settings.general.row.theme.title": "Tema",
|
||||
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
|
||||
"settings.general.row.font.title": "Fuente",
|
||||
"settings.general.row.font.description": "Personaliza la fuente mono usada en bloques de código",
|
||||
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
|
||||
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"En Linux con monitores de frecuencia de actualización mixta, Wayland nativo puede ser más estable.",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "Notas de la versión",
|
||||
"settings.general.row.releaseNotes.description":
|
||||
@@ -542,7 +622,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "Buscando...",
|
||||
"settings.updates.toast.latest.title": "Estás al día",
|
||||
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -600,6 +679,7 @@ export const dict = {
|
||||
"sound.option.yup04": "Sí 04",
|
||||
"sound.option.yup05": "Sí 05",
|
||||
"sound.option.yup06": "Sí 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "Agente",
|
||||
"settings.general.notifications.agent.description":
|
||||
"Mostrar notificación del sistema cuando el agente termine o necesite atención",
|
||||
|
||||
@@ -15,12 +15,10 @@ export const dict = {
|
||||
"command.category.agent": "Agent",
|
||||
"command.category.permissions": "Permissions",
|
||||
"command.category.workspace": "Espace de travail",
|
||||
|
||||
"command.category.settings": "Paramètres",
|
||||
"theme.scheme.system": "Système",
|
||||
"theme.scheme.light": "Clair",
|
||||
"theme.scheme.dark": "Sombre",
|
||||
|
||||
"command.sidebar.toggle": "Basculer la barre latérale",
|
||||
"command.project.open": "Ouvrir un projet",
|
||||
"command.provider.connect": "Connecter un fournisseur",
|
||||
@@ -31,23 +29,19 @@ export const dict = {
|
||||
"command.session.previous.unseen": "Session non lue précédente",
|
||||
"command.session.next.unseen": "Session non lue suivante",
|
||||
"command.session.archive": "Archiver la session",
|
||||
|
||||
"command.palette": "Palette de commandes",
|
||||
|
||||
"command.theme.cycle": "Changer de thème",
|
||||
"command.theme.set": "Utiliser le thème : {{theme}}",
|
||||
"command.theme.scheme.cycle": "Changer de schéma de couleurs",
|
||||
"command.theme.scheme.set": "Utiliser le schéma de couleurs : {{scheme}}",
|
||||
|
||||
"command.language.cycle": "Changer de langue",
|
||||
"command.language.set": "Utiliser la langue : {{language}}",
|
||||
|
||||
"command.session.new": "Nouvelle session",
|
||||
"command.file.open": "Ouvrir un fichier",
|
||||
"command.tab.close": "Fermer l'onglet",
|
||||
"command.context.addSelection": "Ajouter la sélection au contexte",
|
||||
"command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel",
|
||||
"command.input.focus": "Focus input",
|
||||
"command.input.focus": "Focus sur l'entrée",
|
||||
"command.terminal.toggle": "Basculer le terminal",
|
||||
"command.fileTree.toggle": "Basculer l'arborescence des fichiers",
|
||||
"command.review.toggle": "Basculer la revue",
|
||||
@@ -72,6 +66,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Accepter automatiquement les modifications",
|
||||
"command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications",
|
||||
"command.workspace.toggle": "Basculer les espaces de travail",
|
||||
"command.workspace.toggle.description": "Activer ou désactiver plusieurs espaces de travail dans la barre latérale",
|
||||
"command.session.undo": "Annuler",
|
||||
"command.session.undo.description": "Annuler le dernier message",
|
||||
"command.session.redo": "Rétablir",
|
||||
@@ -84,32 +79,30 @@ export const dict = {
|
||||
"command.session.share.description": "Partager cette session et copier l'URL dans le presse-papiers",
|
||||
"command.session.unshare": "Ne plus partager la session",
|
||||
"command.session.unshare.description": "Arrêter de partager cette session",
|
||||
|
||||
"palette.search.placeholder": "Rechercher des fichiers, des commandes et des sessions",
|
||||
"palette.empty": "Aucun résultat trouvé",
|
||||
"palette.group.commands": "Commandes",
|
||||
"palette.group.files": "Fichiers",
|
||||
|
||||
"dialog.provider.search.placeholder": "Rechercher des fournisseurs",
|
||||
"dialog.provider.empty": "Aucun fournisseur trouvé",
|
||||
"dialog.provider.group.popular": "Populaire",
|
||||
"dialog.provider.group.other": "Autre",
|
||||
"dialog.provider.tag.recommended": "Recommandé",
|
||||
"dialog.provider.opencode.note": "Modèles sélectionnés incluant Claude, GPT, Gemini et plus",
|
||||
"dialog.provider.anthropic.note": "Connectez-vous avec Claude Pro/Max ou une clé API",
|
||||
"dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clé API",
|
||||
"dialog.provider.copilot.note": "Connectez-vous avec Copilot ou une clé API",
|
||||
|
||||
"dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clé API",
|
||||
"dialog.provider.google.note": "Modèles Gemini pour des réponses rapides et structurées",
|
||||
"dialog.provider.openrouter.note": "Accédez à tous les modèles pris en charge depuis un seul fournisseur",
|
||||
"dialog.provider.vercel.note": "Accès unifié aux modèles d'IA avec routage intelligent",
|
||||
"dialog.model.select.title": "Sélectionner un modèle",
|
||||
"dialog.model.search.placeholder": "Rechercher des modèles",
|
||||
"dialog.model.empty": "Aucun résultat de modèle",
|
||||
"dialog.model.manage": "Gérer les modèles",
|
||||
"dialog.model.manage.description": "Personnalisez les modèles qui apparaissent dans le sélecteur.",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par OpenCode",
|
||||
"dialog.model.unpaid.addMore.title": "Ajouter plus de modèles de fournisseurs populaires",
|
||||
|
||||
"dialog.provider.viewAll": "Voir plus de fournisseurs",
|
||||
|
||||
"provider.connect.title": "Connecter {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Connexion avec Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Sélectionnez la méthode de connexion pour {{provider}}.",
|
||||
@@ -144,12 +137,46 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "Code de confirmation",
|
||||
"provider.connect.toast.connected.title": "{{provider}} connecté",
|
||||
"provider.connect.toast.connected.description": "Les modèles {{provider}} sont maintenant disponibles.",
|
||||
|
||||
"provider.custom.title": "Fournisseur personnalisé",
|
||||
"provider.custom.description.prefix": "Configurer un fournisseur compatible OpenAI. Voir la ",
|
||||
"provider.custom.description.link": "doc de config fournisseur",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID du fournisseur",
|
||||
"provider.custom.field.providerID.placeholder": "monfournisseur",
|
||||
"provider.custom.field.providerID.description": "Lettres minuscules, chiffres, traits d'union ou tirets bas",
|
||||
"provider.custom.field.name.label": "Nom d'affichage",
|
||||
"provider.custom.field.name.placeholder": "Mon fournisseur IA",
|
||||
"provider.custom.field.baseURL.label": "URL de base",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.monfournisseur.com/v1",
|
||||
"provider.custom.field.apiKey.label": "Clé API",
|
||||
"provider.custom.field.apiKey.placeholder": "Clé API",
|
||||
"provider.custom.field.apiKey.description": "Optionnel. Laisser vide si vous gérez l'auth via les en-têtes.",
|
||||
"provider.custom.models.label": "Modèles",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "id-modele",
|
||||
"provider.custom.models.name.label": "Nom",
|
||||
"provider.custom.models.name.placeholder": "Nom d'affichage",
|
||||
"provider.custom.models.remove": "Supprimer le modèle",
|
||||
"provider.custom.models.add": "Ajouter un modèle",
|
||||
"provider.custom.headers.label": "En-têtes (optionnel)",
|
||||
"provider.custom.headers.key.label": "En-tête",
|
||||
"provider.custom.headers.key.placeholder": "Nom-En-Tête",
|
||||
"provider.custom.headers.value.label": "Valeur",
|
||||
"provider.custom.headers.value.placeholder": "valeur",
|
||||
"provider.custom.headers.remove": "Supprimer l'en-tête",
|
||||
"provider.custom.headers.add": "Ajouter un en-tête",
|
||||
"provider.custom.error.providerID.required": "L'ID du fournisseur est requis",
|
||||
"provider.custom.error.providerID.format": "Utilisez des lettres minuscules, chiffres, traits d'union ou tirets bas",
|
||||
"provider.custom.error.providerID.exists": "Cet ID de fournisseur existe déjà",
|
||||
"provider.custom.error.name.required": "Le nom d'affichage est requis",
|
||||
"provider.custom.error.baseURL.required": "L'URL de base est requise",
|
||||
"provider.custom.error.baseURL.format": "Doit commencer par http:// ou https://",
|
||||
"provider.custom.error.required": "Requis",
|
||||
"provider.custom.error.duplicate": "Doublon",
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} déconnecté",
|
||||
"provider.disconnect.toast.disconnected.description": "Les modèles {{provider}} ne sont plus disponibles.",
|
||||
"model.tag.free": "Gratuit",
|
||||
"model.tag.latest": "Dernier",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -166,6 +193,7 @@ export const dict = {
|
||||
"model.tooltip.context": "Limite de contexte {{limit}}",
|
||||
"common.search.placeholder": "Rechercher",
|
||||
"common.goBack": "Retour",
|
||||
"common.goForward": "Avancer",
|
||||
"common.loading": "Chargement",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Annuler",
|
||||
@@ -176,14 +204,12 @@ export const dict = {
|
||||
"common.saving": "Enregistrement...",
|
||||
"common.default": "Défaut",
|
||||
"common.attachment": "pièce jointe",
|
||||
|
||||
"prompt.placeholder.shell": "Entrez une commande shell...",
|
||||
"prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "Résumer les commentaires…",
|
||||
"prompt.placeholder.summarizeComment": "Résumer le commentaire…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell.exit": "esc pour quitter",
|
||||
|
||||
"prompt.example.1": "Corriger un TODO dans la base de code",
|
||||
"prompt.example.2": "Quelle est la pile technique de ce projet ?",
|
||||
"prompt.example.3": "Réparer les tests échoués",
|
||||
@@ -209,7 +235,6 @@ export const dict = {
|
||||
"prompt.example.23": "Ajouter la pagination à cette liste",
|
||||
"prompt.example.24": "Créer une commande CLI pour...",
|
||||
"prompt.example.25": "Comment fonctionnent les variables d'environnement ici ?",
|
||||
|
||||
"prompt.popover.emptyResults": "Aucun résultat correspondant",
|
||||
"prompt.popover.emptyCommands": "Aucune commande correspondante",
|
||||
"prompt.dropzone.label": "Déposez des images ou des PDF ici",
|
||||
@@ -225,7 +250,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Supprimer la pièce jointe",
|
||||
"prompt.action.send": "Envoyer",
|
||||
"prompt.action.stop": "Arrêter",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Collage non supporté",
|
||||
"prompt.toast.pasteUnsupported.description": "Seules les images ou les PDF peuvent être collés ici.",
|
||||
"prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle",
|
||||
@@ -236,24 +260,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "Échec de l'envoi de la commande",
|
||||
"prompt.toast.promptSendFailed.title": "Échec de l'envoi du message",
|
||||
"prompt.toast.promptSendFailed.description": "Impossible de récupérer la session",
|
||||
|
||||
"dialog.mcp.title": "MCPs",
|
||||
"dialog.mcp.description": "{{enabled}} sur {{total}} activés",
|
||||
"dialog.mcp.empty": "Aucun MCP configuré",
|
||||
|
||||
"dialog.lsp.empty": "LSPs détectés automatiquement par type de fichier",
|
||||
"dialog.plugins.empty": "Plugins configurés dans opencode.json",
|
||||
|
||||
"mcp.status.connected": "connecté",
|
||||
"mcp.status.failed": "échoué",
|
||||
"mcp.status.needs_auth": "nécessite auth",
|
||||
"mcp.status.disabled": "désactivé",
|
||||
|
||||
"dialog.fork.empty": "Aucun message à partir duquel bifurquer",
|
||||
|
||||
"dialog.directory.search.placeholder": "Rechercher des dossiers",
|
||||
"dialog.directory.empty": "Aucun dossier trouvé",
|
||||
|
||||
"dialog.server.title": "Serveurs",
|
||||
"dialog.server.description": "Changez le serveur OpenCode auquel cette application se connecte.",
|
||||
"dialog.server.search.placeholder": "Rechercher des serveurs",
|
||||
@@ -271,14 +289,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "Définir le serveur actuel comme défaut",
|
||||
"dialog.server.default.clear": "Effacer",
|
||||
"dialog.server.action.remove": "Supprimer le serveur",
|
||||
|
||||
"dialog.server.menu.edit": "Modifier",
|
||||
"dialog.server.menu.default": "Définir par défaut",
|
||||
"dialog.server.menu.defaultRemove": "Supprimer par défaut",
|
||||
"dialog.server.menu.delete": "Supprimer",
|
||||
"dialog.server.current": "Serveur actuel",
|
||||
"dialog.server.status.default": "Défaut",
|
||||
|
||||
"dialog.project.edit.title": "Modifier le projet",
|
||||
"dialog.project.edit.name": "Nom",
|
||||
"dialog.project.edit.icon": "Icône",
|
||||
@@ -287,7 +303,6 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Recommandé : 128x128px",
|
||||
"dialog.project.edit.color": "Couleur",
|
||||
"dialog.project.edit.color.select": "Sélectionner la couleur {{color}}",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "Script de démarrage de l'espace de travail",
|
||||
"dialog.project.edit.worktree.startup.description":
|
||||
"S'exécute après la création d'un nouvel espace de travail (arbre de travail).",
|
||||
@@ -300,10 +315,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "Assistant",
|
||||
"context.breakdown.tool": "Appels d'outils",
|
||||
"context.breakdown.other": "Autre",
|
||||
|
||||
"context.systemPrompt.title": "Prompt système",
|
||||
"context.rawMessages.title": "Messages bruts",
|
||||
|
||||
"context.stats.session": "Session",
|
||||
"context.stats.messages": "Messages",
|
||||
"context.stats.provider": "Fournisseur",
|
||||
@@ -320,36 +333,44 @@ export const dict = {
|
||||
"context.stats.totalCost": "Coût total",
|
||||
"context.stats.sessionCreated": "Session créée",
|
||||
"context.stats.lastActivity": "Dernière activité",
|
||||
|
||||
"context.usage.tokens": "Jetons",
|
||||
"context.usage.usage": "Utilisation",
|
||||
"context.usage.cost": "Coût",
|
||||
"context.usage.clickToView": "Cliquez pour voir le contexte",
|
||||
"context.usage.view": "Voir l'utilisation du contexte",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "Langue",
|
||||
"toast.language.description": "Passé à {{language}}",
|
||||
|
||||
"toast.theme.title": "Thème changé",
|
||||
"toast.scheme.title": "Schéma de couleurs",
|
||||
|
||||
"toast.workspace.enabled.title": "Espaces de travail activés",
|
||||
"toast.workspace.enabled.description": "Plusieurs worktrees sont désormais affichés dans la barre latérale",
|
||||
"toast.workspace.disabled.title": "Espaces de travail désactivés",
|
||||
"toast.workspace.disabled.description": "Seul le worktree principal est affiché dans la barre latérale",
|
||||
"toast.permissions.autoaccept.on.title": "Acceptation auto des modifications",
|
||||
"toast.permissions.autoaccept.on.description":
|
||||
"Les permissions de modification et d'écriture seront automatiquement approuvées",
|
||||
"toast.permissions.autoaccept.off.title": "Arrêt acceptation auto des modifications",
|
||||
"toast.permissions.autoaccept.off.description":
|
||||
"Les permissions de modification et d'écriture nécessiteront une approbation",
|
||||
|
||||
"toast.workspace.enabled.title": "Espaces de travail activés",
|
||||
"toast.workspace.enabled.description": "Plusieurs worktrees sont désormais affichés dans la barre latérale",
|
||||
"toast.workspace.disabled.title": "Espaces de travail désactivés",
|
||||
"toast.workspace.disabled.description": "Seul le worktree principal est affiché dans la barre latérale",
|
||||
|
||||
"toast.model.none.title": "Aucun modèle sélectionné",
|
||||
"toast.model.none.description": "Connectez un fournisseur pour résumer cette session",
|
||||
|
||||
"toast.file.loadFailed.title": "Échec du chargement du fichier",
|
||||
|
||||
"toast.file.listFailed.title": "Échec de la liste des fichiers",
|
||||
"toast.context.noLineSelection.title": "Aucune sélection de lignes",
|
||||
"toast.context.noLineSelection.description": "Sélectionnez d'abord une plage de lignes dans un onglet de fichier.",
|
||||
@@ -358,20 +379,16 @@ export const dict = {
|
||||
"toast.session.share.success.description": "URL de partage copiée dans le presse-papiers !",
|
||||
"toast.session.share.failed.title": "Échec du partage de la session",
|
||||
"toast.session.share.failed.description": "Une erreur s'est produite lors du partage de la session",
|
||||
|
||||
"toast.session.unshare.success.title": "Session non partagée",
|
||||
"toast.session.unshare.success.description": "Session non partagée avec succès !",
|
||||
"toast.session.unshare.failed.title": "Échec de l'annulation du partage",
|
||||
"toast.session.unshare.failed.description": "Une erreur s'est produite lors de l'annulation du partage de la session",
|
||||
|
||||
"toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}",
|
||||
|
||||
"toast.update.title": "Mise à jour disponible",
|
||||
"toast.update.description":
|
||||
"Une nouvelle version d'OpenCode ({{version}}) est maintenant disponible pour installation.",
|
||||
"toast.update.action.installRestart": "Installer et redémarrer",
|
||||
"toast.update.action.notYet": "Pas encore",
|
||||
|
||||
"error.page.title": "Quelque chose s'est mal passé",
|
||||
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
|
||||
"error.page.details.label": "Détails de l'erreur",
|
||||
@@ -382,13 +399,11 @@ export const dict = {
|
||||
"error.page.report.prefix": "Veuillez signaler cette erreur à l'équipe OpenCode",
|
||||
"error.page.report.discord": "sur Discord",
|
||||
"error.page.version": "Version : {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"Élément racine introuvable. Avez-vous oublié de l'ajouter à votre index.html ? Ou peut-être que l'attribut id est mal orthographié ?",
|
||||
|
||||
"error.globalSync.connectFailed":
|
||||
"Impossible de se connecter au serveur. Y a-t-il un serveur en cours d'exécution à `{{url}}` ?",
|
||||
|
||||
"directory.error.invalidUrl": "Répertoire invalide dans l'URL.",
|
||||
"error.chain.unknown": "Erreur inconnue",
|
||||
"error.chain.causedBy": "Causé par :",
|
||||
"error.chain.apiError": "Erreur API",
|
||||
@@ -411,21 +426,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "Échec de l'analyse du frontmatter dans {{path}} :\n{{message}}",
|
||||
"error.chain.configInvalid": "Le fichier de configuration à {{path}} est invalide",
|
||||
"error.chain.configInvalidWithMessage": "Le fichier de configuration à {{path}} est invalide : {{message}}",
|
||||
|
||||
"notification.permission.title": "Permission requise",
|
||||
"notification.permission.description": "{{sessionTitle}} dans {{projectName}} a besoin d'une permission",
|
||||
"notification.question.title": "Question",
|
||||
"notification.question.description": "{{sessionTitle}} dans {{projectName}} a une question",
|
||||
"notification.action.goToSession": "Aller à la session",
|
||||
|
||||
"notification.session.responseReady.title": "Réponse prête",
|
||||
"notification.session.error.title": "Erreur de session",
|
||||
"notification.session.error.fallbackDescription": "Une erreur s'est produite",
|
||||
|
||||
"home.recentProjects": "Projets récents",
|
||||
"home.empty.title": "Aucun projet récent",
|
||||
"home.empty.description": "Commencez par ouvrir un projet local",
|
||||
|
||||
"session.tab.session": "Session",
|
||||
"session.tab.review": "Revue",
|
||||
"session.tab.context": "Contexte",
|
||||
@@ -443,18 +454,19 @@ export const dict = {
|
||||
"session.messages.loadingEarlier": "Chargement des messages précédents...",
|
||||
"session.messages.loadEarlier": "Charger les messages précédents",
|
||||
"session.messages.loading": "Chargement des messages...",
|
||||
|
||||
"session.messages.jumpToLatest": "Aller au dernier",
|
||||
"session.context.addToContext": "Ajouter {{selection}} au contexte",
|
||||
|
||||
"session.new.worktree.main": "Branche principale",
|
||||
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
|
||||
"session.new.worktree.create": "Créer un nouvel arbre de travail",
|
||||
"session.new.lastModified": "Dernière modification",
|
||||
|
||||
"session.header.search.placeholder": "Rechercher {{project}}",
|
||||
"session.header.searchFiles": "Rechercher des fichiers",
|
||||
|
||||
"session.header.openIn": "Ouvrir dans",
|
||||
"session.header.open.action": "Ouvrir {{app}}",
|
||||
"session.header.open.ariaLabel": "Ouvrir dans {{app}}",
|
||||
"session.header.open.menu": "Options d'ouverture",
|
||||
"session.header.open.copyPath": "Copier le chemin",
|
||||
"status.popover.trigger": "Statut",
|
||||
"status.popover.ariaLabel": "Configurations des serveurs",
|
||||
"status.popover.tab.servers": "Serveurs",
|
||||
@@ -462,7 +474,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "Plugins",
|
||||
"status.popover.action.manageServers": "Gérer les serveurs",
|
||||
|
||||
"session.share.popover.title": "Publier sur le web",
|
||||
"session.share.popover.description.shared":
|
||||
"Cette session est publique sur le web. Elle est accessible à toute personne disposant du lien.",
|
||||
@@ -476,16 +487,13 @@ export const dict = {
|
||||
"session.share.action.view": "Voir",
|
||||
"session.share.copy.copied": "Copié",
|
||||
"session.share.copy.copyLink": "Copier le lien",
|
||||
|
||||
"lsp.tooltip.none": "Aucun serveur LSP",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "Chargement du prompt...",
|
||||
"terminal.loading": "Chargement du terminal...",
|
||||
"terminal.title": "Terminal",
|
||||
"terminal.title.numbered": "Terminal {{number}}",
|
||||
"terminal.close": "Fermer le terminal",
|
||||
|
||||
"terminal.connectionLost.title": "Connexion perdue",
|
||||
"terminal.connectionLost.description":
|
||||
"La connexion au terminal a été interrompue. Cela peut arriver lorsque le serveur redémarre.",
|
||||
@@ -501,7 +509,6 @@ export const dict = {
|
||||
"common.close": "Fermer",
|
||||
"common.edit": "Modifier",
|
||||
"common.loadMore": "Charger plus",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
"sidebar.menu.toggle": "Basculer le menu",
|
||||
"sidebar.nav.projectsAndSessions": "Projets et sessions",
|
||||
@@ -516,18 +523,19 @@ export const dict = {
|
||||
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
|
||||
"sidebar.project.recentSessions": "Sessions récentes",
|
||||
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "Bureau",
|
||||
"settings.section.server": "Serveur",
|
||||
"settings.tab.general": "Général",
|
||||
"settings.tab.shortcuts": "Raccourcis",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "Intégration WSL",
|
||||
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
|
||||
"settings.general.section.appearance": "Apparence",
|
||||
"settings.general.section.notifications": "Notifications système",
|
||||
"settings.general.section.updates": "Mises à jour",
|
||||
"settings.general.section.sounds": "Effets sonores",
|
||||
|
||||
"settings.general.section.display": "Affichage",
|
||||
"settings.general.row.language.title": "Langue",
|
||||
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
||||
"settings.general.row.appearance.title": "Apparence",
|
||||
@@ -536,10 +544,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
||||
"settings.general.row.font.title": "Police",
|
||||
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
|
||||
|
||||
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
||||
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"Sur Linux avec des moniteurs à taux de rafraîchissement mixte, Wayland natif peut être plus stable.",
|
||||
"settings.general.row.releaseNotes.title": "Notes de version",
|
||||
"settings.general.row.releaseNotes.description": 'Afficher des pop-ups "Quoi de neuf" après les mises à jour',
|
||||
|
||||
"settings.updates.row.startup.title": "Vérifier les mises à jour au démarrage",
|
||||
"settings.updates.row.startup.description": "Vérifier automatiquement les mises à jour au lancement d'OpenCode",
|
||||
"settings.updates.row.check.title": "Vérifier les mises à jour",
|
||||
@@ -548,7 +558,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "Vérification...",
|
||||
"settings.updates.toast.latest.title": "Vous êtes à jour",
|
||||
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -614,14 +623,12 @@ export const dict = {
|
||||
"Afficher une notification système lorsqu'une permission est requise",
|
||||
"settings.general.notifications.errors.title": "Erreurs",
|
||||
"settings.general.notifications.errors.description": "Afficher une notification système lorsqu'une erreur se produit",
|
||||
|
||||
"settings.general.sounds.agent.title": "Agent",
|
||||
"settings.general.sounds.agent.description": "Jouer un son lorsque l'agent a terminé ou nécessite une attention",
|
||||
"settings.general.sounds.permissions.title": "Permissions",
|
||||
"settings.general.sounds.permissions.description": "Jouer un son lorsqu'une permission est requise",
|
||||
"settings.general.sounds.errors.title": "Erreurs",
|
||||
"settings.general.sounds.errors.description": "Jouer un son lorsqu'une erreur se produit",
|
||||
|
||||
"settings.shortcuts.title": "Raccourcis clavier",
|
||||
"settings.shortcuts.reset.button": "Rétablir les défauts",
|
||||
"settings.shortcuts.reset.toast.title": "Raccourcis réinitialisés",
|
||||
@@ -632,14 +639,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "Appuyez sur les touches",
|
||||
"settings.shortcuts.search.placeholder": "Rechercher des raccourcis",
|
||||
"settings.shortcuts.search.empty": "Aucun raccourci trouvé",
|
||||
|
||||
"settings.shortcuts.group.general": "Général",
|
||||
"settings.shortcuts.group.session": "Session",
|
||||
"settings.shortcuts.group.navigation": "Navigation",
|
||||
"settings.shortcuts.group.modelAndAgent": "Modèle et agent",
|
||||
"settings.shortcuts.group.terminal": "Terminal",
|
||||
"settings.shortcuts.group.prompt": "Prompt",
|
||||
|
||||
"settings.providers.title": "Fournisseurs",
|
||||
"settings.providers.description": "Les paramètres des fournisseurs seront configurables ici.",
|
||||
"settings.providers.section.connected": "Fournisseurs connectés",
|
||||
@@ -657,16 +662,13 @@ export const dict = {
|
||||
"settings.commands.description": "Les paramètres des commandes seront configurables ici.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "Les paramètres MCP seront configurables ici.",
|
||||
|
||||
"settings.permissions.title": "Permissions",
|
||||
"settings.permissions.description": "Contrôlez les outils que le serveur peut utiliser par défaut.",
|
||||
"settings.permissions.section.tools": "Outils",
|
||||
"settings.permissions.toast.updateFailed.title": "Échec de la mise à jour des permissions",
|
||||
|
||||
"settings.permissions.action.allow": "Autoriser",
|
||||
"settings.permissions.action.ask": "Demander",
|
||||
"settings.permissions.action.deny": "Refuser",
|
||||
|
||||
"settings.permissions.tool.read.title": "Lire",
|
||||
"settings.permissions.tool.read.description": "Lecture d'un fichier (correspond au chemin du fichier)",
|
||||
"settings.permissions.tool.edit.title": "Modifier",
|
||||
@@ -701,12 +703,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "Accéder aux fichiers en dehors du répertoire du projet",
|
||||
"settings.permissions.tool.doom_loop.title": "Boucle infernale",
|
||||
"settings.permissions.tool.doom_loop.description": "Détecter les appels d'outils répétés avec une entrée identique",
|
||||
|
||||
"session.delete.failed.title": "Échec de la suppression de la session",
|
||||
"session.delete.title": "Supprimer la session",
|
||||
"session.delete.confirm": 'Supprimer la session "{{name}}" ?',
|
||||
"session.delete.button": "Supprimer la session",
|
||||
|
||||
"workspace.new": "Nouvel espace de travail",
|
||||
"workspace.type.local": "local",
|
||||
"workspace.type.sandbox": "bac à sable",
|
||||
|
||||
@@ -15,12 +15,10 @@ export const dict = {
|
||||
"command.category.agent": "エージェント",
|
||||
"command.category.permissions": "権限",
|
||||
"command.category.workspace": "ワークスペース",
|
||||
|
||||
"command.category.settings": "設定",
|
||||
"theme.scheme.system": "システム",
|
||||
"theme.scheme.light": "ライト",
|
||||
"theme.scheme.dark": "ダーク",
|
||||
|
||||
"command.sidebar.toggle": "サイドバーの切り替え",
|
||||
"command.project.open": "プロジェクトを開く",
|
||||
"command.provider.connect": "プロバイダーに接続",
|
||||
@@ -31,17 +29,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "前の未読セッション",
|
||||
"command.session.next.unseen": "次の未読セッション",
|
||||
"command.session.archive": "セッションをアーカイブ",
|
||||
|
||||
"command.palette": "コマンドパレット",
|
||||
|
||||
"command.theme.cycle": "テーマの切り替え",
|
||||
"command.theme.set": "テーマを使用: {{theme}}",
|
||||
"command.theme.scheme.cycle": "配色の切り替え",
|
||||
"command.theme.scheme.set": "配色を使用: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "言語の切り替え",
|
||||
"command.language.set": "言語を使用: {{language}}",
|
||||
|
||||
"command.session.new": "新しいセッション",
|
||||
"command.file.open": "ファイルを開く",
|
||||
"command.tab.close": "タブを閉じる",
|
||||
@@ -72,6 +66,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "編集を自動承認",
|
||||
"command.permissions.autoaccept.disable": "編集の自動承認を停止",
|
||||
"command.workspace.toggle": "ワークスペースを切り替え",
|
||||
"command.workspace.toggle.description": "サイドバーでの複数のワークスペースの有効化・無効化",
|
||||
"command.session.undo": "元に戻す",
|
||||
"command.session.undo.description": "最後のメッセージを元に戻す",
|
||||
"command.session.redo": "やり直す",
|
||||
@@ -84,32 +79,30 @@ export const dict = {
|
||||
"command.session.share.description": "このセッションを共有しURLをクリップボードにコピー",
|
||||
"command.session.unshare": "セッションの共有を停止",
|
||||
"command.session.unshare.description": "このセッションの共有を停止",
|
||||
|
||||
"palette.search.placeholder": "ファイル、コマンド、セッションを検索",
|
||||
"palette.empty": "結果が見つかりません",
|
||||
"palette.group.commands": "コマンド",
|
||||
"palette.group.files": "ファイル",
|
||||
|
||||
"dialog.provider.search.placeholder": "プロバイダーを検索",
|
||||
"dialog.provider.empty": "プロバイダーが見つかりません",
|
||||
"dialog.provider.group.popular": "人気",
|
||||
"dialog.provider.group.other": "その他",
|
||||
"dialog.provider.tag.recommended": "推奨",
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Geminiなどを含む厳選されたモデル",
|
||||
"dialog.provider.anthropic.note": "Claude Pro/MaxまたはAPIキーで接続",
|
||||
"dialog.provider.openai.note": "ChatGPT Pro/PlusまたはAPIキーで接続",
|
||||
"dialog.provider.copilot.note": "CopilotまたはAPIキーで接続",
|
||||
|
||||
"dialog.provider.openai.note": "ChatGPT Pro/PlusまたはAPIキーで接続",
|
||||
"dialog.provider.google.note": "高速で構造化された応答のためのGeminiモデル",
|
||||
"dialog.provider.openrouter.note": "1つのプロバイダーからすべてのサポートされているモデルにアクセス",
|
||||
"dialog.provider.vercel.note": "スマートルーターによるAIモデルへの統合アクセス",
|
||||
"dialog.model.select.title": "モデルを選択",
|
||||
"dialog.model.search.placeholder": "モデルを検索",
|
||||
"dialog.model.empty": "モデルが見つかりません",
|
||||
"dialog.model.manage": "モデルを管理",
|
||||
"dialog.model.manage.description": "モデルセレクターに表示するモデルをカスタマイズします。",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "OpenCodeが提供する無料モデル",
|
||||
"dialog.model.unpaid.addMore.title": "人気のプロバイダーからモデルを追加",
|
||||
|
||||
"dialog.provider.viewAll": "さらにプロバイダーを表示",
|
||||
|
||||
"provider.connect.title": "{{provider}}を接続",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Maxでログイン",
|
||||
"provider.connect.selectMethod": "{{provider}}のログイン方法を選択してください。",
|
||||
@@ -143,12 +136,46 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "確認コード",
|
||||
"provider.connect.toast.connected.title": "{{provider}}が接続されました",
|
||||
"provider.connect.toast.connected.description": "{{provider}}モデルが使用可能になりました。",
|
||||
|
||||
"provider.custom.title": "カスタムプロバイダー",
|
||||
"provider.custom.description.prefix": "OpenAI互換のプロバイダーを設定します。詳細は",
|
||||
"provider.custom.description.link": "プロバイダー設定ドキュメント",
|
||||
"provider.custom.description.suffix": "をご覧ください。",
|
||||
"provider.custom.field.providerID.label": "プロバイダーID",
|
||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
|
||||
"provider.custom.field.name.label": "表示名",
|
||||
"provider.custom.field.name.placeholder": "My AI Provider",
|
||||
"provider.custom.field.baseURL.label": "ベースURL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "APIキー",
|
||||
"provider.custom.field.apiKey.placeholder": "APIキー",
|
||||
"provider.custom.field.apiKey.description": "オプション。ヘッダーで認証を管理する場合は空のままにしてください。",
|
||||
"provider.custom.models.label": "モデル",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "名前",
|
||||
"provider.custom.models.name.placeholder": "表示名",
|
||||
"provider.custom.models.remove": "モデルを削除",
|
||||
"provider.custom.models.add": "モデルを追加",
|
||||
"provider.custom.headers.label": "ヘッダー (オプション)",
|
||||
"provider.custom.headers.key.label": "ヘッダー",
|
||||
"provider.custom.headers.key.placeholder": "Header-Name",
|
||||
"provider.custom.headers.value.label": "値",
|
||||
"provider.custom.headers.value.placeholder": "value",
|
||||
"provider.custom.headers.remove": "ヘッダーを削除",
|
||||
"provider.custom.headers.add": "ヘッダーを追加",
|
||||
"provider.custom.error.providerID.required": "プロバイダーIDが必要です",
|
||||
"provider.custom.error.providerID.format": "小文字、数字、ハイフン、アンダースコアを使用してください",
|
||||
"provider.custom.error.providerID.exists": "そのプロバイダーIDは既に存在します",
|
||||
"provider.custom.error.name.required": "表示名が必要です",
|
||||
"provider.custom.error.baseURL.required": "ベースURLが必要です",
|
||||
"provider.custom.error.baseURL.format": "http:// または https:// で始まる必要があります",
|
||||
"provider.custom.error.required": "必須",
|
||||
"provider.custom.error.duplicate": "重複",
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}}が切断されました",
|
||||
"provider.disconnect.toast.disconnected.description": "{{provider}}のモデルは利用できなくなりました。",
|
||||
"model.tag.free": "無料",
|
||||
"model.tag.latest": "最新",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -165,6 +192,7 @@ export const dict = {
|
||||
"model.tooltip.context": "コンテキスト上限 {{limit}}",
|
||||
"common.search.placeholder": "検索",
|
||||
"common.goBack": "戻る",
|
||||
"common.goForward": "進む",
|
||||
"common.loading": "読み込み中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "キャンセル",
|
||||
@@ -175,14 +203,12 @@ export const dict = {
|
||||
"common.saving": "保存中...",
|
||||
"common.default": "デフォルト",
|
||||
"common.attachment": "添付ファイル",
|
||||
|
||||
"prompt.placeholder.shell": "シェルコマンドを入力...",
|
||||
"prompt.placeholder.normal": '何でも聞いてください... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "コメントを要約…",
|
||||
"prompt.placeholder.summarizeComment": "コメントを要約…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell": "シェル",
|
||||
"prompt.mode.shell.exit": "escで終了",
|
||||
|
||||
"prompt.example.1": "コードベースのTODOを修正",
|
||||
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
|
||||
"prompt.example.3": "壊れたテストを修正",
|
||||
@@ -208,7 +234,6 @@ export const dict = {
|
||||
"prompt.example.23": "このリストにページネーションを追加",
|
||||
"prompt.example.24": "〜のCLIコマンドを作成",
|
||||
"prompt.example.25": "ここでは環境変数はどう機能しますか?",
|
||||
|
||||
"prompt.popover.emptyResults": "一致する結果がありません",
|
||||
"prompt.popover.emptyCommands": "一致するコマンドがありません",
|
||||
"prompt.dropzone.label": "画像またはPDFをここにドロップ",
|
||||
@@ -224,7 +249,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "添付ファイルを削除",
|
||||
"prompt.action.send": "送信",
|
||||
"prompt.action.stop": "停止",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "サポートされていない貼り付け",
|
||||
"prompt.toast.pasteUnsupported.description": "ここでは画像またはPDFのみ貼り付け可能です。",
|
||||
"prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択",
|
||||
@@ -235,24 +259,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "コマンドの送信に失敗しました",
|
||||
"prompt.toast.promptSendFailed.title": "プロンプトの送信に失敗しました",
|
||||
"prompt.toast.promptSendFailed.description": "セッションを取得できませんでした",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "{{total}}個中{{enabled}}個が有効",
|
||||
"dialog.mcp.empty": "MCPが設定されていません",
|
||||
|
||||
"dialog.lsp.empty": "ファイルタイプから自動検出されたLSP",
|
||||
"dialog.plugins.empty": "opencode.jsonで設定されたプラグイン",
|
||||
|
||||
"mcp.status.connected": "接続済み",
|
||||
"mcp.status.failed": "失敗",
|
||||
"mcp.status.needs_auth": "認証が必要",
|
||||
"mcp.status.disabled": "無効",
|
||||
|
||||
"dialog.fork.empty": "フォーク元のメッセージがありません",
|
||||
|
||||
"dialog.directory.search.placeholder": "フォルダを検索",
|
||||
"dialog.directory.empty": "フォルダが見つかりません",
|
||||
|
||||
"dialog.server.title": "サーバー",
|
||||
"dialog.server.description": "このアプリが接続するOpenCodeサーバーを切り替えます。",
|
||||
"dialog.server.search.placeholder": "サーバーを検索",
|
||||
@@ -270,14 +288,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "現在のサーバーをデフォルトに設定",
|
||||
"dialog.server.default.clear": "クリア",
|
||||
"dialog.server.action.remove": "サーバーを削除",
|
||||
|
||||
"dialog.server.menu.edit": "編集",
|
||||
"dialog.server.menu.default": "デフォルトに設定",
|
||||
"dialog.server.menu.defaultRemove": "デフォルト設定を解除",
|
||||
"dialog.server.menu.delete": "削除",
|
||||
"dialog.server.current": "現在のサーバー",
|
||||
"dialog.server.status.default": "デフォルト",
|
||||
|
||||
"dialog.project.edit.title": "プロジェクトを編集",
|
||||
"dialog.project.edit.name": "名前",
|
||||
"dialog.project.edit.icon": "アイコン",
|
||||
@@ -286,7 +302,6 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "推奨: 128x128px",
|
||||
"dialog.project.edit.color": "色",
|
||||
"dialog.project.edit.color.select": "{{color}}の色を選択",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "ワークスペース起動スクリプト",
|
||||
"dialog.project.edit.worktree.startup.description":
|
||||
"新しいワークスペース (ワークツリー) を作成した後に実行されます。",
|
||||
@@ -298,10 +313,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "アシスタント",
|
||||
"context.breakdown.tool": "ツール呼び出し",
|
||||
"context.breakdown.other": "その他",
|
||||
|
||||
"context.systemPrompt.title": "システムプロンプト",
|
||||
"context.rawMessages.title": "生のメッセージ",
|
||||
|
||||
"context.stats.session": "セッション",
|
||||
"context.stats.messages": "メッセージ",
|
||||
"context.stats.provider": "プロバイダー",
|
||||
@@ -318,29 +331,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "総コスト",
|
||||
"context.stats.sessionCreated": "セッション作成日時",
|
||||
"context.stats.lastActivity": "最終アクティビティ",
|
||||
|
||||
"context.usage.tokens": "トークン",
|
||||
"context.usage.usage": "使用量",
|
||||
"context.usage.cost": "コスト",
|
||||
"context.usage.clickToView": "クリックしてコンテキストを表示",
|
||||
"context.usage.view": "コンテキスト使用量を表示",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "言語",
|
||||
"toast.language.description": "{{language}}に切り替えました",
|
||||
|
||||
"toast.theme.title": "テーマが切り替わりました",
|
||||
"toast.scheme.title": "配色",
|
||||
|
||||
"toast.workspace.enabled.title": "ワークスペースが有効になりました",
|
||||
"toast.workspace.enabled.description": "サイドバーに複数のワークツリーが表示されます",
|
||||
"toast.workspace.disabled.title": "ワークスペースが無効になりました",
|
||||
"toast.workspace.disabled.description": "サイドバーにはメインのワークツリーのみが表示されます",
|
||||
"toast.permissions.autoaccept.on.title": "編集を自動承認中",
|
||||
"toast.permissions.autoaccept.on.description": "編集と書き込みの権限は自動的に承認されます",
|
||||
"toast.permissions.autoaccept.off.title": "編集の自動承認を停止しました",
|
||||
"toast.permissions.autoaccept.off.description": "編集と書き込みの権限には承認が必要です",
|
||||
|
||||
"toast.model.none.title": "モデルが選択されていません",
|
||||
"toast.model.none.description": "このセッションを要約するにはプロバイダーを接続してください",
|
||||
|
||||
"toast.file.loadFailed.title": "ファイルの読み込みに失敗しました",
|
||||
|
||||
"toast.file.listFailed.title": "ファイル一覧の取得に失敗しました",
|
||||
"toast.context.noLineSelection.title": "行が選択されていません",
|
||||
"toast.context.noLineSelection.description": "まずファイルタブで行範囲を選択してください。",
|
||||
@@ -349,19 +375,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "共有URLをクリップボードにコピーしました!",
|
||||
"toast.session.share.failed.title": "セッションの共有に失敗しました",
|
||||
"toast.session.share.failed.description": "セッションの共有中にエラーが発生しました",
|
||||
|
||||
"toast.session.unshare.success.title": "セッションの共有を解除しました",
|
||||
"toast.session.unshare.success.description": "セッションの共有解除に成功しました!",
|
||||
"toast.session.unshare.failed.title": "セッションの共有解除に失敗しました",
|
||||
"toast.session.unshare.failed.description": "セッションの共有解除中にエラーが発生しました",
|
||||
|
||||
"toast.session.listFailed.title": "{{project}}のセッション読み込みに失敗しました",
|
||||
|
||||
"toast.update.title": "アップデートが利用可能です",
|
||||
"toast.update.description": "OpenCodeの新しいバージョン ({{version}}) がインストール可能です。",
|
||||
"toast.update.action.installRestart": "インストールして再起動",
|
||||
"toast.update.action.notYet": "今はしない",
|
||||
|
||||
"error.page.title": "問題が発生しました",
|
||||
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
||||
"error.page.details.label": "エラー詳細",
|
||||
@@ -372,12 +394,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "このエラーをOpenCodeチームに報告してください: ",
|
||||
"error.page.report.discord": "Discord",
|
||||
"error.page.version": "バージョン: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"ルート要素が見つかりません。index.htmlに追加するのを忘れていませんか?またはid属性のスペルが間違っていませんか?",
|
||||
|
||||
"error.globalSync.connectFailed": "サーバーに接続できませんでした。`{{url}}`でサーバーが実行されていますか?",
|
||||
|
||||
"directory.error.invalidUrl": "URL内のディレクトリが無効です。",
|
||||
"error.chain.unknown": "不明なエラー",
|
||||
"error.chain.causedBy": "原因:",
|
||||
"error.chain.apiError": "APIエラー",
|
||||
@@ -398,21 +418,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "{{path}} のフロントマターの解析に失敗しました:\n{{message}}",
|
||||
"error.chain.configInvalid": "{{path}} の設定ファイルが無効です",
|
||||
"error.chain.configInvalidWithMessage": "{{path}} の設定ファイルが無効です: {{message}}",
|
||||
|
||||
"notification.permission.title": "権限が必要です",
|
||||
"notification.permission.description": "{{projectName}} の {{sessionTitle}} が権限を必要としています",
|
||||
"notification.question.title": "質問",
|
||||
"notification.question.description": "{{projectName}} の {{sessionTitle}} から質問があります",
|
||||
"notification.action.goToSession": "セッションへ移動",
|
||||
|
||||
"notification.session.responseReady.title": "応答の準備ができました",
|
||||
"notification.session.error.title": "セッションエラー",
|
||||
"notification.session.error.fallbackDescription": "エラーが発生しました",
|
||||
|
||||
"home.recentProjects": "最近のプロジェクト",
|
||||
"home.empty.title": "最近のプロジェクトはありません",
|
||||
"home.empty.description": "ローカルプロジェクトを開いて始めましょう",
|
||||
|
||||
"session.tab.session": "セッション",
|
||||
"session.tab.review": "レビュー",
|
||||
"session.tab.context": "コンテキスト",
|
||||
@@ -430,18 +446,19 @@ export const dict = {
|
||||
"session.messages.loadingEarlier": "以前のメッセージを読み込み中...",
|
||||
"session.messages.loadEarlier": "以前のメッセージを読み込む",
|
||||
"session.messages.loading": "メッセージを読み込み中...",
|
||||
|
||||
"session.messages.jumpToLatest": "最新へジャンプ",
|
||||
"session.context.addToContext": "{{selection}}をコンテキストに追加",
|
||||
|
||||
"session.new.worktree.main": "メインブランチ",
|
||||
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
||||
"session.new.worktree.create": "新しいワークツリーを作成",
|
||||
"session.new.lastModified": "最終更新",
|
||||
|
||||
"session.header.search.placeholder": "{{project}}を検索",
|
||||
"session.header.searchFiles": "ファイルを検索",
|
||||
|
||||
"session.header.openIn": "で開く",
|
||||
"session.header.open.action": "{{app}}を開く",
|
||||
"session.header.open.ariaLabel": "{{app}}で開く",
|
||||
"session.header.open.menu": "開くオプション",
|
||||
"session.header.open.copyPath": "パスをコピー",
|
||||
"status.popover.trigger": "ステータス",
|
||||
"status.popover.ariaLabel": "サーバー設定",
|
||||
"status.popover.tab.servers": "サーバー",
|
||||
@@ -449,7 +466,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "プラグイン",
|
||||
"status.popover.action.manageServers": "サーバーを管理",
|
||||
|
||||
"session.share.popover.title": "ウェブで公開",
|
||||
"session.share.popover.description.shared":
|
||||
"このセッションはウェブで公開されています。リンクを知っている人なら誰でもアクセスできます。",
|
||||
@@ -463,16 +479,13 @@ export const dict = {
|
||||
"session.share.action.view": "表示",
|
||||
"session.share.copy.copied": "コピーしました",
|
||||
"session.share.copy.copyLink": "リンクをコピー",
|
||||
|
||||
"lsp.tooltip.none": "LSPサーバーなし",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "プロンプトを読み込み中...",
|
||||
"terminal.loading": "ターミナルを読み込み中...",
|
||||
"terminal.title": "ターミナル",
|
||||
"terminal.title.numbered": "ターミナル {{number}}",
|
||||
"terminal.close": "ターミナルを閉じる",
|
||||
|
||||
"terminal.connectionLost.title": "接続が失われました",
|
||||
"terminal.connectionLost.description":
|
||||
"ターミナルの接続が中断されました。これはサーバーが再起動したときに発生することがあります。",
|
||||
@@ -488,7 +501,6 @@ export const dict = {
|
||||
"common.close": "閉じる",
|
||||
"common.edit": "編集",
|
||||
"common.loadMore": "さらに読み込む",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
"sidebar.menu.toggle": "メニューを切り替え",
|
||||
"sidebar.nav.projectsAndSessions": "プロジェクトとセッション",
|
||||
@@ -501,18 +513,19 @@ export const dict = {
|
||||
"sidebar.gettingStarted.line2": "プロバイダーを接続して、Claude、GPT、Geminiなどのモデルを使用できます。",
|
||||
"sidebar.project.recentSessions": "最近のセッション",
|
||||
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "デスクトップ",
|
||||
"settings.section.server": "サーバー",
|
||||
"settings.tab.general": "一般",
|
||||
"settings.tab.shortcuts": "ショートカット",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL統合",
|
||||
"settings.desktop.wsl.description": "Windows上のWSL内でOpenCodeサーバーを実行します。",
|
||||
"settings.general.section.appearance": "外観",
|
||||
"settings.general.section.notifications": "システム通知",
|
||||
"settings.general.section.updates": "アップデート",
|
||||
"settings.general.section.sounds": "効果音",
|
||||
|
||||
"settings.general.section.display": "ディスプレイ",
|
||||
"settings.general.row.language.title": "言語",
|
||||
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
||||
"settings.general.row.appearance.title": "外観",
|
||||
@@ -521,10 +534,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||
"settings.general.row.font.title": "フォント",
|
||||
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
|
||||
|
||||
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
||||
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"リフレッシュレートが混在するモニターを使用しているLinuxでは、ネイティブWaylandの方が安定する場合があります。",
|
||||
"settings.general.row.releaseNotes.title": "リリースノート",
|
||||
"settings.general.row.releaseNotes.description": "アップデート後に「新機能」ポップアップを表示",
|
||||
|
||||
"settings.updates.row.startup.title": "起動時にアップデートを確認",
|
||||
"settings.updates.row.startup.description": "OpenCode の起動時に自動でアップデートを確認します",
|
||||
"settings.updates.row.check.title": "アップデートを確認",
|
||||
@@ -533,7 +548,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "確認中...",
|
||||
"settings.updates.toast.latest.title": "最新です",
|
||||
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -598,14 +612,12 @@ export const dict = {
|
||||
"settings.general.notifications.permissions.description": "権限が必要な場合にシステム通知を表示します",
|
||||
"settings.general.notifications.errors.title": "エラー",
|
||||
"settings.general.notifications.errors.description": "エラーが発生した場合にシステム通知を表示します",
|
||||
|
||||
"settings.general.sounds.agent.title": "エージェント",
|
||||
"settings.general.sounds.agent.description": "エージェントが完了したか、注意が必要な場合に音を再生します",
|
||||
"settings.general.sounds.permissions.title": "権限",
|
||||
"settings.general.sounds.permissions.description": "権限が必要な場合に音を再生します",
|
||||
"settings.general.sounds.errors.title": "エラー",
|
||||
"settings.general.sounds.errors.description": "エラーが発生した場合に音を再生します",
|
||||
|
||||
"settings.shortcuts.title": "キーボードショートカット",
|
||||
"settings.shortcuts.reset.button": "デフォルトにリセット",
|
||||
"settings.shortcuts.reset.toast.title": "ショートカットをリセットしました",
|
||||
@@ -616,14 +628,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "キーを押してください",
|
||||
"settings.shortcuts.search.placeholder": "ショートカットを検索",
|
||||
"settings.shortcuts.search.empty": "ショートカットが見つかりません",
|
||||
|
||||
"settings.shortcuts.group.general": "一般",
|
||||
"settings.shortcuts.group.session": "セッション",
|
||||
"settings.shortcuts.group.navigation": "ナビゲーション",
|
||||
"settings.shortcuts.group.modelAndAgent": "モデルとエージェント",
|
||||
"settings.shortcuts.group.terminal": "ターミナル",
|
||||
"settings.shortcuts.group.prompt": "プロンプト",
|
||||
|
||||
"settings.providers.title": "プロバイダー",
|
||||
"settings.providers.description": "プロバイダー設定はここで構成できます。",
|
||||
"settings.providers.section.connected": "接続済みプロバイダー",
|
||||
@@ -641,16 +651,13 @@ export const dict = {
|
||||
"settings.commands.description": "コマンド設定はここで構成できます。",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "MCP設定はここで構成できます。",
|
||||
|
||||
"settings.permissions.title": "権限",
|
||||
"settings.permissions.description": "サーバーがデフォルトで使用できるツールを制御します。",
|
||||
"settings.permissions.section.tools": "ツール",
|
||||
"settings.permissions.toast.updateFailed.title": "権限の更新に失敗しました",
|
||||
|
||||
"settings.permissions.action.allow": "許可",
|
||||
"settings.permissions.action.ask": "確認",
|
||||
"settings.permissions.action.deny": "拒否",
|
||||
|
||||
"settings.permissions.tool.read.title": "読み込み",
|
||||
"settings.permissions.tool.read.description": "ファイルの読み込み (ファイルパスに一致)",
|
||||
"settings.permissions.tool.edit.title": "編集",
|
||||
@@ -673,22 +680,20 @@ export const dict = {
|
||||
"settings.permissions.tool.todoread.description": "Todoリストの読み込み",
|
||||
"settings.permissions.tool.todowrite.title": "Todo書き込み",
|
||||
"settings.permissions.tool.todowrite.description": "Todoリストの更新",
|
||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||
"settings.permissions.tool.webfetch.title": "Web取得",
|
||||
"settings.permissions.tool.webfetch.description": "URLからコンテンツを取得",
|
||||
"settings.permissions.tool.websearch.title": "Web Search",
|
||||
"settings.permissions.tool.websearch.title": "Web検索",
|
||||
"settings.permissions.tool.websearch.description": "ウェブを検索",
|
||||
"settings.permissions.tool.codesearch.title": "Code Search",
|
||||
"settings.permissions.tool.codesearch.title": "コード検索",
|
||||
"settings.permissions.tool.codesearch.description": "ウェブ上のコードを検索",
|
||||
"settings.permissions.tool.external_directory.title": "外部ディレクトリ",
|
||||
"settings.permissions.tool.external_directory.description": "プロジェクトディレクトリ外のファイルへのアクセス",
|
||||
"settings.permissions.tool.doom_loop.title": "Doom Loop",
|
||||
"settings.permissions.tool.doom_loop.title": "無限ループ",
|
||||
"settings.permissions.tool.doom_loop.description": "同一入力による繰り返しのツール呼び出しを検出",
|
||||
|
||||
"session.delete.failed.title": "セッションの削除に失敗しました",
|
||||
"session.delete.title": "セッションの削除",
|
||||
"session.delete.confirm": 'セッション "{{name}}" を削除しますか?',
|
||||
"session.delete.button": "セッションを削除",
|
||||
|
||||
"workspace.new": "新しいワークスペース",
|
||||
"workspace.type.local": "ローカル",
|
||||
"workspace.type.sandbox": "サンドボックス",
|
||||
|
||||
@@ -19,12 +19,10 @@ export const dict = {
|
||||
"command.category.agent": "에이전트",
|
||||
"command.category.permissions": "권한",
|
||||
"command.category.workspace": "작업 공간",
|
||||
|
||||
"command.category.settings": "설정",
|
||||
"theme.scheme.system": "시스템",
|
||||
"theme.scheme.light": "라이트",
|
||||
"theme.scheme.dark": "다크",
|
||||
|
||||
"command.sidebar.toggle": "사이드바 토글",
|
||||
"command.project.open": "프로젝트 열기",
|
||||
"command.provider.connect": "공급자 연결",
|
||||
@@ -35,17 +33,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "이전 읽지 않은 세션",
|
||||
"command.session.next.unseen": "다음 읽지 않은 세션",
|
||||
"command.session.archive": "세션 보관",
|
||||
|
||||
"command.palette": "명령 팔레트",
|
||||
|
||||
"command.theme.cycle": "테마 순환",
|
||||
"command.theme.set": "테마 사용: {{theme}}",
|
||||
"command.theme.scheme.cycle": "색상 테마 순환",
|
||||
"command.theme.scheme.set": "색상 테마 사용: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "언어 순환",
|
||||
"command.language.set": "언어 사용: {{language}}",
|
||||
|
||||
"command.session.new": "새 세션",
|
||||
"command.file.open": "파일 열기",
|
||||
"command.tab.close": "탭 닫기",
|
||||
@@ -76,6 +70,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "편집 자동 수락",
|
||||
"command.permissions.autoaccept.disable": "편집 자동 수락 중지",
|
||||
"command.workspace.toggle": "작업 공간 전환",
|
||||
"command.workspace.toggle.description": "사이드바에서 다중 작업 공간 활성화 또는 비활성화",
|
||||
"command.session.undo": "실행 취소",
|
||||
"command.session.undo.description": "마지막 메시지 실행 취소",
|
||||
"command.session.redo": "다시 실행",
|
||||
@@ -88,32 +83,30 @@ export const dict = {
|
||||
"command.session.share.description": "이 세션을 공유하고 URL을 클립보드에 복사",
|
||||
"command.session.unshare": "세션 공유 중지",
|
||||
"command.session.unshare.description": "이 세션 공유 중지",
|
||||
|
||||
"palette.search.placeholder": "파일, 명령어 및 세션 검색",
|
||||
"palette.empty": "결과 없음",
|
||||
"palette.group.commands": "명령어",
|
||||
"palette.group.files": "파일",
|
||||
|
||||
"dialog.provider.search.placeholder": "공급자 검색",
|
||||
"dialog.provider.empty": "공급자 없음",
|
||||
"dialog.provider.group.popular": "인기",
|
||||
"dialog.provider.group.other": "기타",
|
||||
"dialog.provider.tag.recommended": "추천",
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini 등을 포함한 엄선된 모델",
|
||||
"dialog.provider.anthropic.note": "Claude Pro/Max 또는 API 키로 연결",
|
||||
"dialog.provider.openai.note": "ChatGPT Pro/Plus 또는 API 키로 연결",
|
||||
"dialog.provider.copilot.note": "Copilot 또는 API 키로 연결",
|
||||
|
||||
"dialog.provider.openai.note": "ChatGPT Pro/Plus 또는 API 키로 연결",
|
||||
"dialog.provider.google.note": "빠르고 구조화된 응답을 위한 Gemini 모델",
|
||||
"dialog.provider.openrouter.note": "모든 지원 모델을 단일 공급자에서 액세스",
|
||||
"dialog.provider.vercel.note": "스마트 라우팅을 통한 AI 모델 통합 액세스",
|
||||
"dialog.model.select.title": "모델 선택",
|
||||
"dialog.model.search.placeholder": "모델 검색",
|
||||
"dialog.model.empty": "모델 결과 없음",
|
||||
"dialog.model.manage": "모델 관리",
|
||||
"dialog.model.manage.description": "모델 선택기에 표시할 모델 사용자 지정",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "OpenCode에서 제공하는 무료 모델",
|
||||
"dialog.model.unpaid.addMore.title": "인기 공급자의 모델 추가",
|
||||
|
||||
"dialog.provider.viewAll": "더 많은 공급자 보기",
|
||||
|
||||
"provider.connect.title": "{{provider}} 연결",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max로 로그인",
|
||||
"provider.connect.selectMethod": "{{provider}} 로그인 방법 선택",
|
||||
@@ -129,10 +122,10 @@ export const dict = {
|
||||
"provider.connect.opencodeZen.line1":
|
||||
"OpenCode Zen은 코딩 에이전트를 위해 최적화된 신뢰할 수 있는 엄선된 모델에 대한 액세스를 제공합니다.",
|
||||
"provider.connect.opencodeZen.line2": "단일 API 키로 Claude, GPT, Gemini, GLM 등 다양한 모델에 액세스할 수 있습니다.",
|
||||
"provider.connect.opencodeZen.visit.prefix": "",
|
||||
"provider.connect.opencodeZen.visit.prefix": "다음 ",
|
||||
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
|
||||
"provider.connect.opencodeZen.visit.suffix": "를 방문하여 API 키를 받으세요.",
|
||||
"provider.connect.oauth.code.visit.prefix": "",
|
||||
"provider.connect.opencodeZen.visit.suffix": "을 방문하여 API 키를 받으세요.",
|
||||
"provider.connect.oauth.code.visit.prefix": "다음 ",
|
||||
"provider.connect.oauth.code.visit.link": "이 링크",
|
||||
"provider.connect.oauth.code.visit.suffix":
|
||||
"를 방문하여 인증 코드를 받아 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.",
|
||||
@@ -140,19 +133,53 @@ export const dict = {
|
||||
"provider.connect.oauth.code.placeholder": "인증 코드",
|
||||
"provider.connect.oauth.code.required": "인증 코드가 필요합니다",
|
||||
"provider.connect.oauth.code.invalid": "유효하지 않은 인증 코드",
|
||||
"provider.connect.oauth.auto.visit.prefix": "",
|
||||
"provider.connect.oauth.auto.visit.prefix": "다음 ",
|
||||
"provider.connect.oauth.auto.visit.link": "이 링크",
|
||||
"provider.connect.oauth.auto.visit.suffix":
|
||||
"를 방문하고 아래 코드를 입력하여 계정을 연결하고 OpenCode에서 {{provider}} 모델을 사용하세요.",
|
||||
"provider.connect.oauth.auto.confirmationCode": "확인 코드",
|
||||
"provider.connect.toast.connected.title": "{{provider}} 연결됨",
|
||||
"provider.connect.toast.connected.description": "이제 {{provider}} 모델을 사용할 수 있습니다.",
|
||||
|
||||
"provider.custom.title": "사용자 지정 공급자",
|
||||
"provider.custom.description.prefix": "OpenAI 호환 공급자를 구성합니다. ",
|
||||
"provider.custom.description.link": "공급자 구성 문서",
|
||||
"provider.custom.description.suffix": "를 참조하세요.",
|
||||
"provider.custom.field.providerID.label": "공급자 ID",
|
||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "소문자, 숫자, 하이픈 또는 밑줄",
|
||||
"provider.custom.field.name.label": "표시 이름",
|
||||
"provider.custom.field.name.placeholder": "내 AI 공급자",
|
||||
"provider.custom.field.baseURL.label": "기본 URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "API 키",
|
||||
"provider.custom.field.apiKey.placeholder": "API 키",
|
||||
"provider.custom.field.apiKey.description": "선택 사항입니다. 헤더를 통해 인증을 관리하는 경우 비워 두세요.",
|
||||
"provider.custom.models.label": "모델",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "이름",
|
||||
"provider.custom.models.name.placeholder": "표시 이름",
|
||||
"provider.custom.models.remove": "모델 제거",
|
||||
"provider.custom.models.add": "모델 추가",
|
||||
"provider.custom.headers.label": "헤더 (선택 사항)",
|
||||
"provider.custom.headers.key.label": "헤더",
|
||||
"provider.custom.headers.key.placeholder": "헤더 이름",
|
||||
"provider.custom.headers.value.label": "값",
|
||||
"provider.custom.headers.value.placeholder": "값",
|
||||
"provider.custom.headers.remove": "헤더 제거",
|
||||
"provider.custom.headers.add": "헤더 추가",
|
||||
"provider.custom.error.providerID.required": "공급자 ID가 필요합니다",
|
||||
"provider.custom.error.providerID.format": "소문자, 숫자, 하이픈 또는 밑줄을 사용하세요",
|
||||
"provider.custom.error.providerID.exists": "해당 공급자 ID가 이미 존재합니다",
|
||||
"provider.custom.error.name.required": "표시 이름이 필요합니다",
|
||||
"provider.custom.error.baseURL.required": "기본 URL이 필요합니다",
|
||||
"provider.custom.error.baseURL.format": "http:// 또는 https://로 시작해야 합니다",
|
||||
"provider.custom.error.required": "필수",
|
||||
"provider.custom.error.duplicate": "중복",
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} 연결 해제됨",
|
||||
"provider.disconnect.toast.disconnected.description": "{{provider}} 모델을 더 이상 사용할 수 없습니다.",
|
||||
"model.tag.free": "무료",
|
||||
"model.tag.latest": "최신",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -169,6 +196,7 @@ export const dict = {
|
||||
"model.tooltip.context": "컨텍스트 제한 {{limit}}",
|
||||
"common.search.placeholder": "검색",
|
||||
"common.goBack": "뒤로 가기",
|
||||
"common.goForward": "앞으로 가기",
|
||||
"common.loading": "로딩 중",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "취소",
|
||||
@@ -179,14 +207,12 @@ export const dict = {
|
||||
"common.saving": "저장 중...",
|
||||
"common.default": "기본값",
|
||||
"common.attachment": "첨부 파일",
|
||||
|
||||
"prompt.placeholder.shell": "셸 명령어 입력...",
|
||||
"prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "댓글 요약…",
|
||||
"prompt.placeholder.summarizeComment": "댓글 요약…",
|
||||
"prompt.mode.shell": "셸",
|
||||
"prompt.mode.shell.exit": "종료하려면 esc",
|
||||
|
||||
"prompt.example.1": "코드베이스의 TODO 수정",
|
||||
"prompt.example.2": "이 프로젝트의 기술 스택이 무엇인가요?",
|
||||
"prompt.example.3": "고장 난 테스트 수정",
|
||||
@@ -212,7 +238,6 @@ export const dict = {
|
||||
"prompt.example.23": "이 목록에 페이지네이션 추가",
|
||||
"prompt.example.24": "...를 위한 CLI 명령어 생성",
|
||||
"prompt.example.25": "여기서 환경 변수는 어떻게 작동하나요?",
|
||||
|
||||
"prompt.popover.emptyResults": "일치하는 결과 없음",
|
||||
"prompt.popover.emptyCommands": "일치하는 명령어 없음",
|
||||
"prompt.dropzone.label": "이미지나 PDF를 여기에 드롭하세요",
|
||||
@@ -228,7 +253,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "첨부 파일 제거",
|
||||
"prompt.action.send": "전송",
|
||||
"prompt.action.stop": "중지",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "지원되지 않는 붙여넣기",
|
||||
"prompt.toast.pasteUnsupported.description": "이미지나 PDF만 붙여넣을 수 있습니다.",
|
||||
"prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택",
|
||||
@@ -239,24 +263,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "명령 전송 실패",
|
||||
"prompt.toast.promptSendFailed.title": "프롬프트 전송 실패",
|
||||
"prompt.toast.promptSendFailed.description": "세션을 가져올 수 없습니다",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "{{total}}개 중 {{enabled}}개 활성화됨",
|
||||
"dialog.mcp.empty": "구성된 MCP 없음",
|
||||
|
||||
"dialog.lsp.empty": "파일 유형에서 자동 감지된 LSP",
|
||||
"dialog.plugins.empty": "opencode.json에 구성된 플러그인",
|
||||
|
||||
"mcp.status.connected": "연결됨",
|
||||
"mcp.status.failed": "실패",
|
||||
"mcp.status.needs_auth": "인증 필요",
|
||||
"mcp.status.disabled": "비활성화됨",
|
||||
|
||||
"dialog.fork.empty": "분기할 메시지 없음",
|
||||
|
||||
"dialog.directory.search.placeholder": "폴더 검색",
|
||||
"dialog.directory.empty": "폴더 없음",
|
||||
|
||||
"dialog.server.title": "서버",
|
||||
"dialog.server.description": "이 앱이 연결할 OpenCode 서버를 전환합니다.",
|
||||
"dialog.server.search.placeholder": "서버 검색",
|
||||
@@ -274,14 +292,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "현재 서버를 기본값으로 설정",
|
||||
"dialog.server.default.clear": "지우기",
|
||||
"dialog.server.action.remove": "서버 제거",
|
||||
|
||||
"dialog.server.menu.edit": "편집",
|
||||
"dialog.server.menu.default": "기본값으로 설정",
|
||||
"dialog.server.menu.defaultRemove": "기본값 제거",
|
||||
"dialog.server.menu.delete": "삭제",
|
||||
"dialog.server.current": "현재 서버",
|
||||
"dialog.server.status.default": "기본값",
|
||||
|
||||
"dialog.project.edit.title": "프로젝트 편집",
|
||||
"dialog.project.edit.name": "이름",
|
||||
"dialog.project.edit.icon": "아이콘",
|
||||
@@ -290,7 +306,6 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "권장: 128x128px",
|
||||
"dialog.project.edit.color": "색상",
|
||||
"dialog.project.edit.color.select": "{{color}} 색상 선택",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "작업 공간 시작 스크립트",
|
||||
"dialog.project.edit.worktree.startup.description": "새 작업 공간(작업 트리)을 만든 뒤 실행됩니다.",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "예: bun install",
|
||||
@@ -301,10 +316,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "어시스턴트",
|
||||
"context.breakdown.tool": "도구 호출",
|
||||
"context.breakdown.other": "기타",
|
||||
|
||||
"context.systemPrompt.title": "시스템 프롬프트",
|
||||
"context.rawMessages.title": "원시 메시지",
|
||||
|
||||
"context.stats.session": "세션",
|
||||
"context.stats.messages": "메시지",
|
||||
"context.stats.provider": "공급자",
|
||||
@@ -321,34 +334,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "총 비용",
|
||||
"context.stats.sessionCreated": "세션 생성됨",
|
||||
"context.stats.lastActivity": "최근 활동",
|
||||
|
||||
"context.usage.tokens": "토큰",
|
||||
"context.usage.usage": "사용량",
|
||||
"context.usage.cost": "비용",
|
||||
"context.usage.clickToView": "컨텍스트를 보려면 클릭",
|
||||
"context.usage.view": "컨텍스트 사용량 보기",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "언어",
|
||||
"toast.language.description": "{{language}}(으)로 전환됨",
|
||||
|
||||
"toast.theme.title": "테마 전환됨",
|
||||
"toast.scheme.title": "색상 테마",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "편집 자동 수락 중",
|
||||
"toast.permissions.autoaccept.on.description": "편집 및 쓰기 권한이 자동으로 승인됩니다",
|
||||
"toast.permissions.autoaccept.off.title": "편집 자동 수락 중지됨",
|
||||
"toast.permissions.autoaccept.off.description": "편집 및 쓰기 권한 승인이 필요합니다",
|
||||
|
||||
"toast.workspace.enabled.title": "작업 공간 활성화됨",
|
||||
"toast.workspace.enabled.description": "이제 사이드바에 여러 작업 트리가 표시됩니다",
|
||||
"toast.workspace.disabled.title": "작업 공간 비활성화됨",
|
||||
"toast.workspace.disabled.description": "사이드바에 메인 작업 트리만 표시됩니다",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "편집 자동 수락 중",
|
||||
"toast.permissions.autoaccept.on.description": "편집 및 쓰기 권한이 자동으로 승인됩니다",
|
||||
"toast.permissions.autoaccept.off.title": "편집 자동 수락 중지됨",
|
||||
"toast.permissions.autoaccept.off.description": "편집 및 쓰기 권한 승인이 필요합니다",
|
||||
"toast.model.none.title": "선택된 모델 없음",
|
||||
"toast.model.none.description": "이 세션을 요약하려면 공급자를 연결하세요",
|
||||
|
||||
"toast.file.loadFailed.title": "파일 로드 실패",
|
||||
|
||||
"toast.file.listFailed.title": "파일 목록을 불러오지 못했습니다",
|
||||
"toast.context.noLineSelection.title": "줄 선택 없음",
|
||||
"toast.context.noLineSelection.description": "먼저 파일 탭에서 줄 범위를 선택하세요.",
|
||||
@@ -357,19 +378,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "공유 URL이 클립보드에 복사되었습니다!",
|
||||
"toast.session.share.failed.title": "세션 공유 실패",
|
||||
"toast.session.share.failed.description": "세션을 공유하는 동안 오류가 발생했습니다",
|
||||
|
||||
"toast.session.unshare.success.title": "세션 공유 해제됨",
|
||||
"toast.session.unshare.success.description": "세션 공유가 성공적으로 해제되었습니다!",
|
||||
"toast.session.unshare.failed.title": "세션 공유 해제 실패",
|
||||
"toast.session.unshare.failed.description": "세션 공유를 해제하는 동안 오류가 발생했습니다",
|
||||
|
||||
"toast.session.listFailed.title": "{{project}}에 대한 세션을 로드하지 못했습니다",
|
||||
|
||||
"toast.update.title": "업데이트 가능",
|
||||
"toast.update.description": "OpenCode의 새 버전({{version}})을 설치할 수 있습니다.",
|
||||
"toast.update.action.installRestart": "설치 및 다시 시작",
|
||||
"toast.update.action.notYet": "나중에",
|
||||
|
||||
"error.page.title": "문제가 발생했습니다",
|
||||
"error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.",
|
||||
"error.page.details.label": "오류 세부 정보",
|
||||
@@ -380,12 +397,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "이 오류를 OpenCode 팀에 제보해 주세요: ",
|
||||
"error.page.report.discord": "Discord",
|
||||
"error.page.version": "버전: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"루트 요소를 찾을 수 없습니다. index.html에 추가하는 것을 잊으셨나요? 또는 id 속성의 철자가 틀렸을 수 있습니다.",
|
||||
|
||||
"error.globalSync.connectFailed": "서버에 연결할 수 없습니다. `{{url}}`에서 서버가 실행 중인가요?",
|
||||
|
||||
"directory.error.invalidUrl": "URL에 유효하지 않은 디렉터리가 있습니다.",
|
||||
"error.chain.unknown": "알 수 없는 오류",
|
||||
"error.chain.causedBy": "원인:",
|
||||
"error.chain.apiError": "API 오류",
|
||||
@@ -405,21 +420,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "{{path}}의 frontmatter 파싱 실패:\n{{message}}",
|
||||
"error.chain.configInvalid": "{{path}}의 구성 파일이 유효하지 않습니다",
|
||||
"error.chain.configInvalidWithMessage": "{{path}}의 구성 파일이 유효하지 않습니다: {{message}}",
|
||||
|
||||
"notification.permission.title": "권한 필요",
|
||||
"notification.permission.description": "{{projectName}}의 {{sessionTitle}}에서 권한이 필요합니다",
|
||||
"notification.question.title": "질문",
|
||||
"notification.question.description": "{{projectName}}의 {{sessionTitle}}에서 질문이 있습니다",
|
||||
"notification.action.goToSession": "세션으로 이동",
|
||||
|
||||
"notification.session.responseReady.title": "응답 준비됨",
|
||||
"notification.session.error.title": "세션 오류",
|
||||
"notification.session.error.fallbackDescription": "오류가 발생했습니다",
|
||||
|
||||
"home.recentProjects": "최근 프로젝트",
|
||||
"home.empty.title": "최근 프로젝트 없음",
|
||||
"home.empty.description": "로컬 프로젝트를 열어 시작하세요",
|
||||
|
||||
"session.tab.session": "세션",
|
||||
"session.tab.review": "검토",
|
||||
"session.tab.context": "컨텍스트",
|
||||
@@ -437,18 +448,19 @@ export const dict = {
|
||||
"session.messages.loadingEarlier": "이전 메시지 로드 중...",
|
||||
"session.messages.loadEarlier": "이전 메시지 로드",
|
||||
"session.messages.loading": "메시지 로드 중...",
|
||||
|
||||
"session.messages.jumpToLatest": "최신으로 이동",
|
||||
"session.context.addToContext": "컨텍스트에 {{selection}} 추가",
|
||||
|
||||
"session.new.worktree.main": "메인 브랜치",
|
||||
"session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
|
||||
"session.new.worktree.create": "새 작업 트리 생성",
|
||||
"session.new.lastModified": "최근 수정",
|
||||
|
||||
"session.header.search.placeholder": "{{project}} 검색",
|
||||
"session.header.searchFiles": "파일 검색",
|
||||
|
||||
"session.header.openIn": "다음에서 열기",
|
||||
"session.header.open.action": "{{app}} 열기",
|
||||
"session.header.open.ariaLabel": "{{app}}에서 열기",
|
||||
"session.header.open.menu": "열기 옵션",
|
||||
"session.header.open.copyPath": "경로 복사",
|
||||
"status.popover.trigger": "상태",
|
||||
"status.popover.ariaLabel": "서버 구성",
|
||||
"status.popover.tab.servers": "서버",
|
||||
@@ -456,7 +468,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "플러그인",
|
||||
"status.popover.action.manageServers": "서버 관리",
|
||||
|
||||
"session.share.popover.title": "웹에 게시",
|
||||
"session.share.popover.description.shared": "이 세션은 웹에 공개되었습니다. 링크가 있는 누구나 액세스할 수 있습니다.",
|
||||
"session.share.popover.description.unshared":
|
||||
@@ -469,16 +480,13 @@ export const dict = {
|
||||
"session.share.action.view": "보기",
|
||||
"session.share.copy.copied": "복사됨",
|
||||
"session.share.copy.copyLink": "링크 복사",
|
||||
|
||||
"lsp.tooltip.none": "LSP 서버 없음",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "프롬프트 로드 중...",
|
||||
"terminal.loading": "터미널 로드 중...",
|
||||
"terminal.title": "터미널",
|
||||
"terminal.title.numbered": "터미널 {{number}}",
|
||||
"terminal.close": "터미널 닫기",
|
||||
|
||||
"terminal.connectionLost.title": "연결 끊김",
|
||||
"terminal.connectionLost.description":
|
||||
"터미널 연결이 중단되었습니다. 서버가 재시작하면 이런 일이 발생할 수 있습니다.",
|
||||
@@ -494,7 +502,6 @@ export const dict = {
|
||||
"common.close": "닫기",
|
||||
"common.edit": "편집",
|
||||
"common.loadMore": "더 불러오기",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
"sidebar.menu.toggle": "메뉴 토글",
|
||||
"sidebar.nav.projectsAndSessions": "프로젝트 및 세션",
|
||||
@@ -507,18 +514,19 @@ export const dict = {
|
||||
"sidebar.gettingStarted.line2": "Claude, GPT, Gemini 등을 포함한 모델을 사용하려면 공급자를 연결하세요.",
|
||||
"sidebar.project.recentSessions": "최근 세션",
|
||||
"sidebar.project.viewAllSessions": "모든 세션 보기",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "데스크톱",
|
||||
"settings.section.server": "서버",
|
||||
"settings.tab.general": "일반",
|
||||
"settings.tab.shortcuts": "단축키",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL 통합",
|
||||
"settings.desktop.wsl.description": "Windows의 WSL 내부에서 OpenCode 서버를 실행합니다.",
|
||||
"settings.general.section.appearance": "모양",
|
||||
"settings.general.section.notifications": "시스템 알림",
|
||||
"settings.general.section.updates": "업데이트",
|
||||
"settings.general.section.sounds": "효과음",
|
||||
|
||||
"settings.general.section.display": "디스플레이",
|
||||
"settings.general.row.language.title": "언어",
|
||||
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
|
||||
"settings.general.row.appearance.title": "모양",
|
||||
@@ -527,10 +535,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||
"settings.general.row.font.title": "글꼴",
|
||||
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
|
||||
|
||||
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
|
||||
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"혼합 주사율 모니터가 있는 Linux에서는 네이티브 Wayland가 더 안정적일 수 있습니다.",
|
||||
"settings.general.row.releaseNotes.title": "릴리스 노트",
|
||||
"settings.general.row.releaseNotes.description": "업데이트 후 '새 소식' 팝업 표시",
|
||||
|
||||
"settings.updates.row.startup.title": "시작 시 업데이트 확인",
|
||||
"settings.updates.row.startup.description": "OpenCode를 실행할 때 업데이트를 자동으로 확인합니다",
|
||||
"settings.updates.row.check.title": "업데이트 확인",
|
||||
@@ -539,7 +549,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "확인 중...",
|
||||
"settings.updates.toast.latest.title": "최신 상태입니다",
|
||||
"settings.updates.toast.latest.description": "현재 최신 버전의 OpenCode를 사용 중입니다.",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -603,14 +612,12 @@ export const dict = {
|
||||
"settings.general.notifications.permissions.description": "권한이 필요할 때 시스템 알림 표시",
|
||||
"settings.general.notifications.errors.title": "오류",
|
||||
"settings.general.notifications.errors.description": "오류가 발생했을 때 시스템 알림 표시",
|
||||
|
||||
"settings.general.sounds.agent.title": "에이전트",
|
||||
"settings.general.sounds.agent.description": "에이전트가 완료되거나 주의가 필요할 때 소리 재생",
|
||||
"settings.general.sounds.permissions.title": "권한",
|
||||
"settings.general.sounds.permissions.description": "권한이 필요할 때 소리 재생",
|
||||
"settings.general.sounds.errors.title": "오류",
|
||||
"settings.general.sounds.errors.description": "오류가 발생했을 때 소리 재생",
|
||||
|
||||
"settings.shortcuts.title": "키보드 단축키",
|
||||
"settings.shortcuts.reset.button": "기본값으로 초기화",
|
||||
"settings.shortcuts.reset.toast.title": "단축키 초기화됨",
|
||||
@@ -621,14 +628,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "키 누르기",
|
||||
"settings.shortcuts.search.placeholder": "단축키 검색",
|
||||
"settings.shortcuts.search.empty": "단축키를 찾을 수 없습니다",
|
||||
|
||||
"settings.shortcuts.group.general": "일반",
|
||||
"settings.shortcuts.group.session": "세션",
|
||||
"settings.shortcuts.group.navigation": "탐색",
|
||||
"settings.shortcuts.group.modelAndAgent": "모델 및 에이전트",
|
||||
"settings.shortcuts.group.terminal": "터미널",
|
||||
"settings.shortcuts.group.prompt": "프롬프트",
|
||||
|
||||
"settings.providers.title": "공급자",
|
||||
"settings.providers.description": "공급자 설정은 여기서 구성할 수 있습니다.",
|
||||
"settings.providers.section.connected": "연결된 공급자",
|
||||
@@ -646,16 +651,13 @@ export const dict = {
|
||||
"settings.commands.description": "명령어 설정은 여기서 구성할 수 있습니다.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "MCP 설정은 여기서 구성할 수 있습니다.",
|
||||
|
||||
"settings.permissions.title": "권한",
|
||||
"settings.permissions.description": "서버가 기본적으로 사용할 수 있는 도구를 제어합니다.",
|
||||
"settings.permissions.section.tools": "도구",
|
||||
"settings.permissions.toast.updateFailed.title": "권한 업데이트 실패",
|
||||
|
||||
"settings.permissions.action.allow": "허용",
|
||||
"settings.permissions.action.ask": "묻기",
|
||||
"settings.permissions.action.deny": "거부",
|
||||
|
||||
"settings.permissions.tool.read.title": "읽기",
|
||||
"settings.permissions.tool.read.description": "파일 읽기 (파일 경로와 일치)",
|
||||
"settings.permissions.tool.edit.title": "편집",
|
||||
@@ -688,12 +690,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "프로젝트 디렉터리 외부의 파일에 액세스",
|
||||
"settings.permissions.tool.doom_loop.title": "무한 반복",
|
||||
"settings.permissions.tool.doom_loop.description": "동일한 입력으로 반복되는 도구 호출 감지",
|
||||
|
||||
"session.delete.failed.title": "세션 삭제 실패",
|
||||
"session.delete.title": "세션 삭제",
|
||||
"session.delete.confirm": '"{{name}}" 세션을 삭제하시겠습니까?',
|
||||
"session.delete.button": "세션 삭제",
|
||||
|
||||
"workspace.new": "새 작업 공간",
|
||||
"workspace.type.local": "로컬",
|
||||
"workspace.type.sandbox": "샌드박스",
|
||||
|
||||
@@ -75,6 +75,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Godta endringer automatisk",
|
||||
"command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk",
|
||||
"command.workspace.toggle": "Veksle arbeidsområder",
|
||||
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
|
||||
"command.session.undo": "Angre",
|
||||
"command.session.undo.description": "Angre siste melding",
|
||||
"command.session.redo": "Gjør om",
|
||||
@@ -98,9 +99,13 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "Populære",
|
||||
"dialog.provider.group.other": "Andre",
|
||||
"dialog.provider.tag.recommended": "Anbefalt",
|
||||
"dialog.provider.anthropic.note": "Koble til med Claude Pro/Max eller API-nøkkel",
|
||||
"dialog.provider.openai.note": "Koble til med ChatGPT Pro/Plus eller API-nøkkel",
|
||||
"dialog.provider.copilot.note": "Koble til med Copilot eller API-nøkkel",
|
||||
"dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og mer",
|
||||
"dialog.provider.anthropic.note": "Direkte tilgang til Claude-modeller, inkludert Pro og Max",
|
||||
"dialog.provider.copilot.note": "Claude-modeller for kodeassistanse",
|
||||
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
|
||||
"dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar",
|
||||
"dialog.provider.openrouter.note": "Tilgang til alle støttede modeller fra én leverandør",
|
||||
"dialog.provider.vercel.note": "Enhetlig tilgang til AI-modeller med smart ruting",
|
||||
|
||||
"dialog.model.select.title": "Velg modell",
|
||||
"dialog.model.search.placeholder": "Søk etter modeller",
|
||||
@@ -148,8 +153,46 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} tilkoblet",
|
||||
"provider.connect.toast.connected.description": "{{provider}}-modeller er nå tilgjengelige.",
|
||||
|
||||
"provider.custom.title": "Egendefinert leverandør",
|
||||
"provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel leverandør. Se ",
|
||||
"provider.custom.description.link": "dokumentasjon for leverandørkonfigurasjon",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "Leverandør-ID",
|
||||
"provider.custom.field.providerID.placeholder": "minleverandør",
|
||||
"provider.custom.field.providerID.description": "Små bokstaver, tall, bindestreker eller understreker",
|
||||
"provider.custom.field.name.label": "Visningsnavn",
|
||||
"provider.custom.field.name.placeholder": "Min AI-leverandør",
|
||||
"provider.custom.field.baseURL.label": "Base-URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "API-nøkkel",
|
||||
"provider.custom.field.apiKey.placeholder": "API-nøkkel",
|
||||
"provider.custom.field.apiKey.description": "Valgfritt. La stå tomt hvis du administrerer autentisering via headers.",
|
||||
"provider.custom.models.label": "Modeller",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "modell-id",
|
||||
"provider.custom.models.name.label": "Navn",
|
||||
"provider.custom.models.name.placeholder": "Visningsnavn",
|
||||
"provider.custom.models.remove": "Fjern modell",
|
||||
"provider.custom.models.add": "Legg til modell",
|
||||
"provider.custom.headers.label": "Headers (valgfritt)",
|
||||
"provider.custom.headers.key.label": "Header",
|
||||
"provider.custom.headers.key.placeholder": "Header-Navn",
|
||||
"provider.custom.headers.value.label": "Verdi",
|
||||
"provider.custom.headers.value.placeholder": "verdi",
|
||||
"provider.custom.headers.remove": "Fjern header",
|
||||
"provider.custom.headers.add": "Legg til header",
|
||||
"provider.custom.error.providerID.required": "Leverandør-ID er påkrevd",
|
||||
"provider.custom.error.providerID.format": "Bruk små bokstaver, tall, bindestreker eller understreker",
|
||||
"provider.custom.error.providerID.exists": "Den leverandør-IDen finnes allerede",
|
||||
"provider.custom.error.name.required": "Visningsnavn er påkrevd",
|
||||
"provider.custom.error.baseURL.required": "Base-URL er påkrevd",
|
||||
"provider.custom.error.baseURL.format": "Må starte med http:// eller https://",
|
||||
"provider.custom.error.required": "Påkrevd",
|
||||
"provider.custom.error.duplicate": "Duplikat",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} frakoblet",
|
||||
"provider.disconnect.toast.disconnected.description": "Modeller fra {{provider}} er ikke lenger tilgjengelige.",
|
||||
|
||||
"model.tag.free": "Gratis",
|
||||
"model.tag.latest": "Nyeste",
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
@@ -169,6 +212,7 @@ export const dict = {
|
||||
|
||||
"common.search.placeholder": "Søk",
|
||||
"common.goBack": "Gå tilbake",
|
||||
"common.goForward": "Navigate forward",
|
||||
"common.loading": "Laster",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Avbryt",
|
||||
@@ -290,10 +334,10 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Anbefalt: 128x128px",
|
||||
"dialog.project.edit.color": "Farge",
|
||||
"dialog.project.edit.color.select": "Velg fargen {{color}}",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "Oppstartsskript for arbeidsområde",
|
||||
"dialog.project.edit.worktree.startup.description": "Kjører etter at et nytt arbeidsområde (worktree) er opprettet.",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "f.eks. bun install",
|
||||
|
||||
"context.breakdown.title": "Kontekstfordeling",
|
||||
"context.breakdown.note": 'Omtrentlig fordeling av input-tokens. "Annet" inkluderer verktøydefinisjoner og overhead.',
|
||||
"context.breakdown.system": "System",
|
||||
@@ -328,30 +372,48 @@ export const dict = {
|
||||
"context.usage.clickToView": "Klikk for å se kontekst",
|
||||
"context.usage.view": "Se kontekstforbruk",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "Språk",
|
||||
"toast.language.description": "Byttet til {{language}}",
|
||||
|
||||
"toast.theme.title": "Tema byttet",
|
||||
"toast.scheme.title": "Fargevalg",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Godtar endringer automatisk",
|
||||
"toast.permissions.autoaccept.on.description": "Redigerings- og skrivetillatelser vil bli godkjent automatisk",
|
||||
"toast.permissions.autoaccept.off.title": "Sluttet å godta endringer automatisk",
|
||||
"toast.permissions.autoaccept.off.description": "Redigerings- og skrivetillatelser vil kreve godkjenning",
|
||||
|
||||
"toast.workspace.enabled.title": "Arbeidsområder aktivert",
|
||||
"toast.workspace.enabled.description": "Flere worktrees vises nå i sidefeltet",
|
||||
"toast.workspace.disabled.title": "Arbeidsområder deaktivert",
|
||||
"toast.workspace.disabled.description": "Kun hoved-worktree vises i sidefeltet",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Godtar endringer automatisk",
|
||||
"toast.permissions.autoaccept.on.description": "Redigerings- og skrivetillatelser vil bli godkjent automatisk",
|
||||
"toast.permissions.autoaccept.off.title": "Sluttet å godta endringer automatisk",
|
||||
"toast.permissions.autoaccept.off.description": "Redigerings- og skrivetillatelser vil kreve godkjenning",
|
||||
|
||||
"toast.model.none.title": "Ingen modell valgt",
|
||||
"toast.model.none.description": "Koble til en leverandør for å oppsummere denne sesjonen",
|
||||
|
||||
"toast.file.loadFailed.title": "Kunne ikke laste fil",
|
||||
|
||||
"toast.file.listFailed.title": "Kunne ikke liste filer",
|
||||
|
||||
"toast.context.noLineSelection.title": "Ingen linjevalg",
|
||||
"toast.context.noLineSelection.description": "Velg først et linjeområde i en filfane.",
|
||||
|
||||
"toast.session.share.copyFailed.title": "Kunne ikke kopiere URL til utklippstavlen",
|
||||
"toast.session.share.success.title": "Sesjon delt",
|
||||
"toast.session.share.success.description": "Delings-URL kopiert til utklippstavlen!",
|
||||
@@ -385,6 +447,7 @@ export const dict = {
|
||||
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
|
||||
|
||||
"error.globalSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
|
||||
"directory.error.invalidUrl": "Invalid directory in URL.",
|
||||
|
||||
"error.chain.unknown": "Ukjent feil",
|
||||
"error.chain.causedBy": "Forårsaket av:",
|
||||
@@ -431,9 +494,11 @@ export const dict = {
|
||||
"session.review.loadingChanges": "Laster endringer...",
|
||||
"session.review.empty": "Ingen endringer i denne sesjonen ennå",
|
||||
"session.review.noChanges": "Ingen endringer",
|
||||
|
||||
"session.files.selectToOpen": "Velg en fil å åpne",
|
||||
"session.files.all": "Alle filer",
|
||||
"session.files.binaryContent": "Binær fil (innhold kan ikke vises)",
|
||||
|
||||
"session.messages.renderEarlier": "Vis tidligere meldinger",
|
||||
"session.messages.loadingEarlier": "Laster inn tidligere meldinger...",
|
||||
"session.messages.loadEarlier": "Last inn tidligere meldinger",
|
||||
@@ -449,6 +514,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "Søk i {{project}}",
|
||||
"session.header.searchFiles": "Søk etter filer",
|
||||
"session.header.openIn": "Åpne i",
|
||||
"session.header.open.action": "Åpne {{app}}",
|
||||
"session.header.open.ariaLabel": "Åpne i {{app}}",
|
||||
"session.header.open.menu": "Åpne alternativer",
|
||||
"session.header.open.copyPath": "Kopier bane",
|
||||
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Serverkonfigurasjoner",
|
||||
@@ -511,15 +581,20 @@ export const dict = {
|
||||
"sidebar.project.viewAllSessions": "Vis alle sesjoner",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
|
||||
"settings.section.desktop": "Skrivebord",
|
||||
"settings.section.server": "Server",
|
||||
"settings.tab.general": "Generelt",
|
||||
"settings.tab.shortcuts": "Snarveier",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL-integrasjon",
|
||||
"settings.desktop.wsl.description": "Kjør OpenCode-serveren i WSL på Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Utseende",
|
||||
"settings.general.section.notifications": "Systemvarsler",
|
||||
"settings.general.section.updates": "Oppdateringer",
|
||||
"settings.general.section.sounds": "Lydeffekter",
|
||||
"settings.general.section.display": "Skjerm",
|
||||
|
||||
"settings.general.row.language.title": "Språk",
|
||||
"settings.general.row.language.description": "Endre visningsspråket for OpenCode",
|
||||
@@ -530,6 +605,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Skrift",
|
||||
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
|
||||
|
||||
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"På Linux med skjermer med blandet oppdateringsfrekvens kan innebygd Wayland være mer stabilt.",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "Utgivelsesnotater",
|
||||
"settings.general.row.releaseNotes.description": 'Vis "Hva er nytt"-vinduer etter oppdateringer',
|
||||
|
||||
@@ -541,7 +621,6 @@ export const dict = {
|
||||
"settings.updates.action.checking": "Sjekker...",
|
||||
"settings.updates.toast.latest.title": "Du er oppdatert",
|
||||
"settings.updates.toast.latest.description": "Du bruker den nyeste versjonen av OpenCode.",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
@@ -599,6 +678,7 @@ export const dict = {
|
||||
"sound.option.yup04": "Ja 04",
|
||||
"sound.option.yup05": "Ja 05",
|
||||
"sound.option.yup06": "Ja 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "Agent",
|
||||
"settings.general.notifications.agent.description":
|
||||
"Vis systemvarsel når agenten er ferdig eller trenger oppmerksomhet",
|
||||
@@ -697,6 +777,7 @@ export const dict = {
|
||||
"session.delete.title": "Slett sesjon",
|
||||
"session.delete.confirm": 'Slette sesjonen "{{name}}"?',
|
||||
"session.delete.button": "Slett sesjon",
|
||||
|
||||
"workspace.new": "Nytt arbeidsområde",
|
||||
"workspace.type.local": "lokal",
|
||||
"workspace.type.sandbox": "sandkasse",
|
||||
|
||||
@@ -16,11 +16,9 @@ export const dict = {
|
||||
"command.category.permissions": "Uprawnienia",
|
||||
"command.category.workspace": "Przestrzeń robocza",
|
||||
"command.category.settings": "Ustawienia",
|
||||
|
||||
"theme.scheme.system": "Systemowy",
|
||||
"theme.scheme.light": "Jasny",
|
||||
"theme.scheme.dark": "Ciemny",
|
||||
|
||||
"command.sidebar.toggle": "Przełącz pasek boczny",
|
||||
"command.project.open": "Otwórz projekt",
|
||||
"command.provider.connect": "Połącz dostawcę",
|
||||
@@ -31,17 +29,13 @@ export const dict = {
|
||||
"command.session.previous.unseen": "Poprzednia nieprzeczytana sesja",
|
||||
"command.session.next.unseen": "Następna nieprzeczytana sesja",
|
||||
"command.session.archive": "Zarchiwizuj sesję",
|
||||
|
||||
"command.palette": "Paleta poleceń",
|
||||
|
||||
"command.theme.cycle": "Przełącz motyw",
|
||||
"command.theme.set": "Użyj motywu: {{theme}}",
|
||||
"command.theme.scheme.cycle": "Przełącz schemat kolorów",
|
||||
"command.theme.scheme.set": "Użyj schematu kolorów: {{scheme}}",
|
||||
|
||||
"command.language.cycle": "Przełącz język",
|
||||
"command.language.set": "Użyj języka: {{language}}",
|
||||
|
||||
"command.session.new": "Nowa sesja",
|
||||
"command.file.open": "Otwórz plik",
|
||||
"command.tab.close": "Zamknij kartę",
|
||||
@@ -72,6 +66,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji",
|
||||
"command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji",
|
||||
"command.workspace.toggle": "Przełącz przestrzenie robocze",
|
||||
"command.workspace.toggle.description": "Włącz lub wyłącz wiele przestrzeni roboczych na pasku bocznym",
|
||||
"command.session.undo": "Cofnij",
|
||||
"command.session.undo.description": "Cofnij ostatnią wiadomość",
|
||||
"command.session.redo": "Ponów",
|
||||
@@ -84,32 +79,30 @@ export const dict = {
|
||||
"command.session.share.description": "Udostępnij tę sesję i skopiuj URL do schowka",
|
||||
"command.session.unshare": "Przestań udostępniać sesję",
|
||||
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
|
||||
|
||||
"palette.search.placeholder": "Szukaj plików, poleceń i sesji",
|
||||
"palette.empty": "Brak wyników",
|
||||
"palette.group.commands": "Polecenia",
|
||||
"palette.group.files": "Pliki",
|
||||
|
||||
"dialog.provider.search.placeholder": "Szukaj dostawców",
|
||||
"dialog.provider.empty": "Nie znaleziono dostawców",
|
||||
"dialog.provider.group.popular": "Popularne",
|
||||
"dialog.provider.group.other": "Inne",
|
||||
"dialog.provider.tag.recommended": "Zalecane",
|
||||
"dialog.provider.anthropic.note": "Połącz z Claude Pro/Max lub kluczem API",
|
||||
"dialog.provider.openai.note": "Połącz z ChatGPT Pro/Plus lub kluczem API",
|
||||
"dialog.provider.copilot.note": "Połącz z Copilot lub kluczem API",
|
||||
|
||||
"dialog.provider.opencode.note": "Wyselekcjonowane modele, w tym Claude, GPT, Gemini i inne",
|
||||
"dialog.provider.anthropic.note": "Bezpośredni dostęp do modeli Claude, w tym Pro i Max",
|
||||
"dialog.provider.copilot.note": "Modele Claude do pomocy w kodowaniu",
|
||||
"dialog.provider.openai.note": "Modele GPT do szybkich i wszechstronnych zadań AI",
|
||||
"dialog.provider.google.note": "Modele Gemini do szybkich i ustrukturyzowanych odpowiedzi",
|
||||
"dialog.provider.openrouter.note": "Dostęp do wszystkich obsługiwanych modeli od jednego dostawcy",
|
||||
"dialog.provider.vercel.note": "Ujednolicony dostęp do modeli AI z inteligentnym routingiem",
|
||||
"dialog.model.select.title": "Wybierz model",
|
||||
"dialog.model.search.placeholder": "Szukaj modeli",
|
||||
"dialog.model.empty": "Brak wyników modelu",
|
||||
"dialog.model.manage": "Zarządzaj modelami",
|
||||
"dialog.model.manage.description": "Dostosuj, które modele pojawiają się w wyborze modelu.",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "Darmowe modele dostarczane przez OpenCode",
|
||||
"dialog.model.unpaid.addMore.title": "Dodaj więcej modeli od popularnych dostawców",
|
||||
|
||||
"dialog.provider.viewAll": "Zobacz więcej dostawców",
|
||||
|
||||
"provider.connect.title": "Połącz {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Zaloguj się z Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Wybierz metodę logowania dla {{provider}}.",
|
||||
@@ -144,7 +137,43 @@ export const dict = {
|
||||
"provider.connect.oauth.auto.confirmationCode": "Kod potwierdzający",
|
||||
"provider.connect.toast.connected.title": "Połączono {{provider}}",
|
||||
"provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.",
|
||||
|
||||
"provider.custom.title": "Dostawca niestandardowy",
|
||||
"provider.custom.description.prefix": "Skonfiguruj dostawcę zgodnego z OpenAI. Zobacz ",
|
||||
"provider.custom.description.link": "dokumentację konfiguracji dostawcy",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID dostawcy",
|
||||
"provider.custom.field.providerID.placeholder": "mojdostawca",
|
||||
"provider.custom.field.providerID.description": "Małe litery, cyfry, łączniki lub podkreślenia",
|
||||
"provider.custom.field.name.label": "Nazwa wyświetlana",
|
||||
"provider.custom.field.name.placeholder": "Mój Dostawca AI",
|
||||
"provider.custom.field.baseURL.label": "Bazowy URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.mojdostawca.com/v1",
|
||||
"provider.custom.field.apiKey.label": "Klucz API",
|
||||
"provider.custom.field.apiKey.placeholder": "Klucz API",
|
||||
"provider.custom.field.apiKey.description":
|
||||
"Opcjonalne. Pozostaw puste, jeśli zarządzasz autoryzacją przez nagłówki.",
|
||||
"provider.custom.models.label": "Modele",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "Nazwa",
|
||||
"provider.custom.models.name.placeholder": "Nazwa wyświetlana",
|
||||
"provider.custom.models.remove": "Usuń model",
|
||||
"provider.custom.models.add": "Dodaj model",
|
||||
"provider.custom.headers.label": "Nagłówki (opcjonalne)",
|
||||
"provider.custom.headers.key.label": "Nagłówek",
|
||||
"provider.custom.headers.key.placeholder": "Nazwa-Naglowka",
|
||||
"provider.custom.headers.value.label": "Wartość",
|
||||
"provider.custom.headers.value.placeholder": "wartość",
|
||||
"provider.custom.headers.remove": "Usuń nagłówek",
|
||||
"provider.custom.headers.add": "Dodaj nagłówek",
|
||||
"provider.custom.error.providerID.required": "ID dostawcy jest wymagane",
|
||||
"provider.custom.error.providerID.format": "Używaj małych liter, cyfr, łączników lub podkreśleń",
|
||||
"provider.custom.error.providerID.exists": "To ID dostawcy już istnieje",
|
||||
"provider.custom.error.name.required": "Nazwa wyświetlana jest wymagana",
|
||||
"provider.custom.error.baseURL.required": "Bazowy URL jest wymagany",
|
||||
"provider.custom.error.baseURL.format": "Musi zaczynać się od http:// lub https://",
|
||||
"provider.custom.error.required": "Wymagane",
|
||||
"provider.custom.error.duplicate": "Duplikat",
|
||||
"provider.disconnect.toast.disconnected.title": "Rozłączono {{provider}}",
|
||||
"provider.disconnect.toast.disconnected.description": "Modele {{provider}} nie są już dostępne.",
|
||||
"model.tag.free": "Darmowy",
|
||||
@@ -163,9 +192,9 @@ export const dict = {
|
||||
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
|
||||
"model.tooltip.reasoning.none": "Brak wnioskowania",
|
||||
"model.tooltip.context": "Limit kontekstu {{limit}}",
|
||||
|
||||
"common.search.placeholder": "Szukaj",
|
||||
"common.goBack": "Wstecz",
|
||||
"common.goForward": "Dalej",
|
||||
"common.loading": "Ładowanie",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Anuluj",
|
||||
@@ -176,14 +205,12 @@ export const dict = {
|
||||
"common.saving": "Zapisywanie...",
|
||||
"common.default": "Domyślny",
|
||||
"common.attachment": "załącznik",
|
||||
|
||||
"prompt.placeholder.shell": "Wpisz polecenie terminala...",
|
||||
"prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"',
|
||||
"prompt.placeholder.summarizeComments": "Podsumuj komentarze…",
|
||||
"prompt.placeholder.summarizeComment": "Podsumuj komentarz…",
|
||||
"prompt.mode.shell": "Terminal",
|
||||
"prompt.mode.shell.exit": "esc aby wyjść",
|
||||
|
||||
"prompt.example.1": "Napraw TODO w bazie kodu",
|
||||
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
|
||||
"prompt.example.3": "Napraw zepsute testy",
|
||||
@@ -209,7 +236,6 @@ export const dict = {
|
||||
"prompt.example.23": "Dodaj stronicowanie do tej listy",
|
||||
"prompt.example.24": "Utwórz polecenie CLI dla...",
|
||||
"prompt.example.25": "Jak działają tutaj zmienne środowiskowe?",
|
||||
|
||||
"prompt.popover.emptyResults": "Brak pasujących wyników",
|
||||
"prompt.popover.emptyCommands": "Brak pasujących poleceń",
|
||||
"prompt.dropzone.label": "Upuść obrazy lub pliki PDF tutaj",
|
||||
@@ -225,7 +251,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Usuń załącznik",
|
||||
"prompt.action.send": "Wyślij",
|
||||
"prompt.action.stop": "Zatrzymaj",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Nieobsługiwane wklejanie",
|
||||
"prompt.toast.pasteUnsupported.description": "Tylko obrazy lub pliki PDF mogą być tutaj wklejane.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
|
||||
@@ -236,24 +261,18 @@ export const dict = {
|
||||
"prompt.toast.commandSendFailed.title": "Nie udało się wysłać polecenia",
|
||||
"prompt.toast.promptSendFailed.title": "Nie udało się wysłać zapytania",
|
||||
"prompt.toast.promptSendFailed.description": "Nie udało się pobrać sesji",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "{{enabled}} z {{total}} włączone",
|
||||
"dialog.mcp.empty": "Brak skonfigurowanych MCP",
|
||||
|
||||
"dialog.lsp.empty": "LSP wykryte automatycznie na podstawie typów plików",
|
||||
"dialog.plugins.empty": "Wtyczki skonfigurowane w opencode.json",
|
||||
|
||||
"mcp.status.connected": "połączono",
|
||||
"mcp.status.failed": "niepowodzenie",
|
||||
"mcp.status.needs_auth": "wymaga autoryzacji",
|
||||
"mcp.status.disabled": "wyłączone",
|
||||
|
||||
"dialog.fork.empty": "Brak wiadomości do rozwidlenia",
|
||||
|
||||
"dialog.directory.search.placeholder": "Szukaj folderów",
|
||||
"dialog.directory.empty": "Nie znaleziono folderów",
|
||||
|
||||
"dialog.server.title": "Serwery",
|
||||
"dialog.server.description": "Przełącz serwer OpenCode, z którym łączy się ta aplikacja.",
|
||||
"dialog.server.search.placeholder": "Szukaj serwerów",
|
||||
@@ -271,14 +290,12 @@ export const dict = {
|
||||
"dialog.server.default.set": "Ustaw bieżący serwer jako domyślny",
|
||||
"dialog.server.default.clear": "Wyczyść",
|
||||
"dialog.server.action.remove": "Usuń serwer",
|
||||
|
||||
"dialog.server.menu.edit": "Edytuj",
|
||||
"dialog.server.menu.default": "Ustaw jako domyślny",
|
||||
"dialog.server.menu.defaultRemove": "Usuń domyślny",
|
||||
"dialog.server.menu.delete": "Usuń",
|
||||
"dialog.server.current": "Obecny serwer",
|
||||
"dialog.server.status.default": "Domyślny",
|
||||
|
||||
"dialog.project.edit.title": "Edytuj projekt",
|
||||
"dialog.project.edit.name": "Nazwa",
|
||||
"dialog.project.edit.icon": "Ikona",
|
||||
@@ -287,10 +304,8 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "Zalecane: 128x128px",
|
||||
"dialog.project.edit.color": "Kolor",
|
||||
"dialog.project.edit.color.select": "Wybierz kolor {{color}}",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
|
||||
"dialog.project.edit.worktree.startup.description":
|
||||
"Uruchamiany po utworzeniu nowej przestrzeni roboczej (drzewa roboczego).",
|
||||
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "np. bun install",
|
||||
"context.breakdown.title": "Podział kontekstu",
|
||||
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
|
||||
@@ -299,10 +314,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "Asystent",
|
||||
"context.breakdown.tool": "Wywołania narzędzi",
|
||||
"context.breakdown.other": "Inne",
|
||||
|
||||
"context.systemPrompt.title": "Prompt systemowy",
|
||||
"context.rawMessages.title": "Surowe wiadomości",
|
||||
|
||||
"context.stats.session": "Sesja",
|
||||
"context.stats.messages": "Wiadomości",
|
||||
"context.stats.provider": "Dostawca",
|
||||
@@ -319,34 +332,42 @@ export const dict = {
|
||||
"context.stats.totalCost": "Całkowity koszt",
|
||||
"context.stats.sessionCreated": "Utworzono sesję",
|
||||
"context.stats.lastActivity": "Ostatnia aktywność",
|
||||
|
||||
"context.usage.tokens": "Tokeny",
|
||||
"context.usage.usage": "Użycie",
|
||||
"context.usage.cost": "Koszt",
|
||||
"context.usage.clickToView": "Kliknij, aby zobaczyć kontekst",
|
||||
"context.usage.view": "Pokaż użycie kontekstu",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
"toast.language.title": "Język",
|
||||
"toast.language.description": "Przełączono na {{language}}",
|
||||
|
||||
"toast.theme.title": "Przełączono motyw",
|
||||
"toast.scheme.title": "Schemat kolorów",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Automatyczne akceptowanie edycji",
|
||||
"toast.permissions.autoaccept.on.description": "Uprawnienia do edycji i zapisu będą automatycznie zatwierdzane",
|
||||
"toast.permissions.autoaccept.off.title": "Zatrzymano automatyczne akceptowanie edycji",
|
||||
"toast.permissions.autoaccept.off.description": "Uprawnienia do edycji i zapisu będą wymagały zatwierdzenia",
|
||||
|
||||
"toast.workspace.enabled.title": "Przestrzenie robocze włączone",
|
||||
"toast.workspace.enabled.description": "Kilka worktree jest teraz wyświetlanych na pasku bocznym",
|
||||
"toast.workspace.disabled.title": "Przestrzenie robocze wyłączone",
|
||||
"toast.workspace.disabled.description": "Tylko główny worktree jest wyświetlany na pasku bocznym",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "Automatyczne akceptowanie edycji",
|
||||
"toast.permissions.autoaccept.on.description": "Uprawnienia do edycji i zapisu będą automatycznie zatwierdzane",
|
||||
"toast.permissions.autoaccept.off.title": "Zatrzymano automatyczne akceptowanie edycji",
|
||||
"toast.permissions.autoaccept.off.description": "Uprawnienia do edycji i zapisu będą wymagały zatwierdzenia",
|
||||
"toast.model.none.title": "Nie wybrano modelu",
|
||||
"toast.model.none.description": "Połącz dostawcę, aby podsumować tę sesję",
|
||||
|
||||
"toast.file.loadFailed.title": "Nie udało się załadować pliku",
|
||||
|
||||
"toast.file.listFailed.title": "Nie udało się wyświetlić listy plików",
|
||||
"toast.context.noLineSelection.title": "Brak zaznaczenia linii",
|
||||
"toast.context.noLineSelection.description": "Najpierw wybierz zakres linii w zakładce pliku.",
|
||||
@@ -355,19 +376,15 @@ export const dict = {
|
||||
"toast.session.share.success.description": "Link udostępniania skopiowany do schowka!",
|
||||
"toast.session.share.failed.title": "Nie udało się udostępnić sesji",
|
||||
"toast.session.share.failed.description": "Wystąpił błąd podczas udostępniania sesji",
|
||||
|
||||
"toast.session.unshare.success.title": "Zatrzymano udostępnianie sesji",
|
||||
"toast.session.unshare.success.description": "Udostępnianie sesji zostało pomyślnie zatrzymane!",
|
||||
"toast.session.unshare.failed.title": "Nie udało się zatrzymać udostępniania sesji",
|
||||
"toast.session.unshare.failed.description": "Wystąpił błąd podczas zatrzymywania udostępniania sesji",
|
||||
|
||||
"toast.session.listFailed.title": "Nie udało się załadować sesji dla {{project}}",
|
||||
|
||||
"toast.update.title": "Dostępna aktualizacja",
|
||||
"toast.update.description": "Nowa wersja OpenCode ({{version}}) jest teraz dostępna do instalacji.",
|
||||
"toast.update.action.installRestart": "Zainstaluj i zrestartuj",
|
||||
"toast.update.action.notYet": "Jeszcze nie",
|
||||
|
||||
"error.page.title": "Coś poszło nie tak",
|
||||
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
|
||||
"error.page.details.label": "Szczegóły błędu",
|
||||
@@ -378,12 +395,10 @@ export const dict = {
|
||||
"error.page.report.prefix": "Proszę zgłosić ten błąd do zespołu OpenCode",
|
||||
"error.page.report.discord": "na Discordzie",
|
||||
"error.page.version": "Wersja: {{version}}",
|
||||
|
||||
"error.dev.rootNotFound":
|
||||
"Nie znaleziono elementu głównego. Czy zapomniałeś dodać go do swojego index.html? A może atrybut id został błędnie wpisany?",
|
||||
|
||||
"error.globalSync.connectFailed": "Nie można połączyć się z serwerem. Czy serwer działa pod adresem `{{url}}`?",
|
||||
|
||||
"directory.error.invalidUrl": "Nieprawidłowy katalog w URL.",
|
||||
"error.chain.unknown": "Nieznany błąd",
|
||||
"error.chain.causedBy": "Spowodowany przez:",
|
||||
"error.chain.apiError": "Błąd API",
|
||||
@@ -393,8 +408,7 @@ export const dict = {
|
||||
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
|
||||
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
|
||||
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
|
||||
"error.chain.mcpFailed":
|
||||
'Serwer MCP "{{name}}" nie powiódł się. Uwaga, OpenCode nie obsługuje jeszcze uwierzytelniania MCP.',
|
||||
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.',
|
||||
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
|
||||
"error.chain.providerInitFailed":
|
||||
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
|
||||
@@ -405,21 +419,17 @@ export const dict = {
|
||||
"error.chain.configFrontmatterError": "Nie udało się przetworzyć frontmatter w {{path}}:\n{{message}}",
|
||||
"error.chain.configInvalid": "Plik konfiguracyjny w {{path}} jest nieprawidłowy",
|
||||
"error.chain.configInvalidWithMessage": "Plik konfiguracyjny w {{path}} jest nieprawidłowy: {{message}}",
|
||||
|
||||
"notification.permission.title": "Wymagane uprawnienie",
|
||||
"notification.permission.description": "{{sessionTitle}} w {{projectName}} potrzebuje uprawnienia",
|
||||
"notification.question.title": "Pytanie",
|
||||
"notification.question.description": "{{sessionTitle}} w {{projectName}} ma pytanie",
|
||||
"notification.action.goToSession": "Przejdź do sesji",
|
||||
|
||||
"notification.session.responseReady.title": "Odpowiedź gotowa",
|
||||
"notification.session.error.title": "Błąd sesji",
|
||||
"notification.session.error.fallbackDescription": "Wystąpił błąd",
|
||||
|
||||
"home.recentProjects": "Ostatnie projekty",
|
||||
"home.empty.title": "Brak ostatnich projektów",
|
||||
"home.empty.description": "Zacznij od otwarcia lokalnego projektu",
|
||||
|
||||
"session.tab.session": "Sesja",
|
||||
"session.tab.review": "Przegląd",
|
||||
"session.tab.context": "Kontekst",
|
||||
@@ -438,17 +448,18 @@ export const dict = {
|
||||
"session.messages.loadEarlier": "Załaduj wcześniejsze wiadomości",
|
||||
"session.messages.loading": "Ładowanie wiadomości...",
|
||||
"session.messages.jumpToLatest": "Przejdź do najnowszych",
|
||||
|
||||
"session.context.addToContext": "Dodaj {{selection}} do kontekstu",
|
||||
|
||||
"session.new.worktree.main": "Główna gałąź",
|
||||
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
|
||||
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
|
||||
"session.new.lastModified": "Ostatnio zmodyfikowano",
|
||||
|
||||
"session.header.search.placeholder": "Szukaj {{project}}",
|
||||
"session.header.searchFiles": "Szukaj plików",
|
||||
|
||||
"session.header.openIn": "Otwórz w",
|
||||
"session.header.open.action": "Otwórz {{app}}",
|
||||
"session.header.open.ariaLabel": "Otwórz w {{app}}",
|
||||
"session.header.open.menu": "Opcje otwierania",
|
||||
"session.header.open.copyPath": "Kopiuj ścieżkę",
|
||||
"status.popover.trigger": "Status",
|
||||
"status.popover.ariaLabel": "Konfiguracje serwerów",
|
||||
"status.popover.tab.servers": "Serwery",
|
||||
@@ -456,7 +467,6 @@ export const dict = {
|
||||
"status.popover.tab.lsp": "LSP",
|
||||
"status.popover.tab.plugins": "Wtyczki",
|
||||
"status.popover.action.manageServers": "Zarządzaj serwerami",
|
||||
|
||||
"session.share.popover.title": "Opublikuj w sieci",
|
||||
"session.share.popover.description.shared":
|
||||
"Ta sesja jest publiczna w sieci. Jest dostępna dla każdego, kto posiada link.",
|
||||
@@ -470,10 +480,8 @@ export const dict = {
|
||||
"session.share.action.view": "Widok",
|
||||
"session.share.copy.copied": "Skopiowano",
|
||||
"session.share.copy.copyLink": "Kopiuj link",
|
||||
|
||||
"lsp.tooltip.none": "Brak serwerów LSP",
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "Ładowanie promptu...",
|
||||
"terminal.loading": "Ładowanie terminala...",
|
||||
"terminal.title": "Terminal",
|
||||
@@ -482,7 +490,6 @@ export const dict = {
|
||||
"terminal.connectionLost.title": "Utracono połączenie",
|
||||
"terminal.connectionLost.description":
|
||||
"Połączenie z terminalem zostało przerwane. Może się to zdarzyć przy restarcie serwera.",
|
||||
|
||||
"common.closeTab": "Zamknij kartę",
|
||||
"common.dismiss": "Odrzuć",
|
||||
"common.requestFailed": "Żądanie nie powiodło się",
|
||||
@@ -496,7 +503,6 @@ export const dict = {
|
||||
"common.edit": "Edytuj",
|
||||
"common.loadMore": "Załaduj więcej",
|
||||
"common.key.esc": "ESC",
|
||||
|
||||
"sidebar.menu.toggle": "Przełącz menu",
|
||||
"sidebar.nav.projectsAndSessions": "Projekty i sesje",
|
||||
"sidebar.settings": "Ustawienia",
|
||||
@@ -508,18 +514,19 @@ export const dict = {
|
||||
"sidebar.gettingStarted.line2": "Połącz dowolnego dostawcę, aby używać modeli, w tym Claude, GPT, Gemini itp.",
|
||||
"sidebar.project.recentSessions": "Ostatnie sesje",
|
||||
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
"settings.section.desktop": "Pulpit",
|
||||
"settings.section.server": "Serwer",
|
||||
"settings.tab.general": "Ogólne",
|
||||
"settings.tab.shortcuts": "Skróty",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
|
||||
"settings.general.section.appearance": "Wygląd",
|
||||
"settings.general.section.notifications": "Powiadomienia systemowe",
|
||||
"settings.general.section.updates": "Aktualizacje",
|
||||
"settings.general.section.sounds": "Efekty dźwiękowe",
|
||||
|
||||
"settings.general.section.display": "Ekran",
|
||||
"settings.general.row.language.title": "Język",
|
||||
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
||||
"settings.general.row.appearance.title": "Wygląd",
|
||||
@@ -528,10 +535,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
||||
"settings.general.row.font.title": "Czcionka",
|
||||
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
|
||||
|
||||
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
||||
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"Na Linuxie z monitorami o różnym odświeżaniu, natywny Wayland może być bardziej stabilny.",
|
||||
"settings.general.row.releaseNotes.title": "Informacje o wydaniu",
|
||||
"settings.general.row.releaseNotes.description": 'Pokazuj wyskakujące okna "Co nowego" po aktualizacjach',
|
||||
|
||||
"settings.updates.row.startup.title": "Sprawdzaj aktualizacje przy uruchomieniu",
|
||||
"settings.updates.row.startup.description": "Automatycznie sprawdzaj aktualizacje podczas uruchamiania OpenCode",
|
||||
"settings.updates.row.check.title": "Sprawdź aktualizacje",
|
||||
@@ -597,7 +606,6 @@ export const dict = {
|
||||
"sound.option.yup04": "Yup 04",
|
||||
"sound.option.yup05": "Yup 05",
|
||||
"sound.option.yup06": "Yup 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "Agent",
|
||||
"settings.general.notifications.agent.description":
|
||||
"Pokaż powiadomienie systemowe, gdy agent zakończy pracę lub wymaga uwagi",
|
||||
@@ -606,14 +614,12 @@ export const dict = {
|
||||
"Pokaż powiadomienie systemowe, gdy wymagane jest uprawnienie",
|
||||
"settings.general.notifications.errors.title": "Błędy",
|
||||
"settings.general.notifications.errors.description": "Pokaż powiadomienie systemowe, gdy wystąpi błąd",
|
||||
|
||||
"settings.general.sounds.agent.title": "Agent",
|
||||
"settings.general.sounds.agent.description": "Odtwórz dźwięk, gdy agent zakończy pracę lub wymaga uwagi",
|
||||
"settings.general.sounds.permissions.title": "Uprawnienia",
|
||||
"settings.general.sounds.permissions.description": "Odtwórz dźwięk, gdy wymagane jest uprawnienie",
|
||||
"settings.general.sounds.errors.title": "Błędy",
|
||||
"settings.general.sounds.errors.description": "Odtwórz dźwięk, gdy wystąpi błąd",
|
||||
|
||||
"settings.shortcuts.title": "Skróty klawiszowe",
|
||||
"settings.shortcuts.reset.button": "Przywróć domyślne",
|
||||
"settings.shortcuts.reset.toast.title": "Zresetowano skróty",
|
||||
@@ -624,14 +630,12 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "Naciśnij klawisze",
|
||||
"settings.shortcuts.search.placeholder": "Szukaj skrótów",
|
||||
"settings.shortcuts.search.empty": "Nie znaleziono skrótów",
|
||||
|
||||
"settings.shortcuts.group.general": "Ogólne",
|
||||
"settings.shortcuts.group.session": "Sesja",
|
||||
"settings.shortcuts.group.navigation": "Nawigacja",
|
||||
"settings.shortcuts.group.modelAndAgent": "Model i agent",
|
||||
"settings.shortcuts.group.terminal": "Terminal",
|
||||
"settings.shortcuts.group.prompt": "Prompt",
|
||||
|
||||
"settings.providers.title": "Dostawcy",
|
||||
"settings.providers.description": "Ustawienia dostawców będą tutaj konfigurowalne.",
|
||||
"settings.providers.section.connected": "Połączeni dostawcy",
|
||||
@@ -649,16 +653,13 @@ export const dict = {
|
||||
"settings.commands.description": "Ustawienia poleceń będą tutaj konfigurowalne.",
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "Ustawienia MCP będą tutaj konfigurowalne.",
|
||||
|
||||
"settings.permissions.title": "Uprawnienia",
|
||||
"settings.permissions.description": "Kontroluj, jakich narzędzi serwer może używać domyślnie.",
|
||||
"settings.permissions.section.tools": "Narzędzia",
|
||||
"settings.permissions.toast.updateFailed.title": "Nie udało się zaktualizować uprawnień",
|
||||
|
||||
"settings.permissions.action.allow": "Zezwól",
|
||||
"settings.permissions.action.ask": "Pytaj",
|
||||
"settings.permissions.action.deny": "Odmów",
|
||||
|
||||
"settings.permissions.tool.read.title": "Odczyt",
|
||||
"settings.permissions.tool.read.description": "Odczyt pliku (pasuje do ścieżki pliku)",
|
||||
"settings.permissions.tool.edit.title": "Edycja",
|
||||
@@ -691,12 +692,10 @@ export const dict = {
|
||||
"settings.permissions.tool.external_directory.description": "Dostęp do plików poza katalogiem projektu",
|
||||
"settings.permissions.tool.doom_loop.title": "Zapętlenie",
|
||||
"settings.permissions.tool.doom_loop.description": "Wykrywanie powtarzających się wywołań narzędzi (doom loop)",
|
||||
|
||||
"session.delete.failed.title": "Nie udało się usunąć sesji",
|
||||
"session.delete.title": "Usuń sesję",
|
||||
"session.delete.confirm": 'Usunąć sesję "{{name}}"?',
|
||||
"session.delete.button": "Usuń sesję",
|
||||
|
||||
"workspace.new": "Nowa przestrzeń robocza",
|
||||
"workspace.type.local": "lokalna",
|
||||
"workspace.type.sandbox": "piaskownica",
|
||||
|
||||
@@ -72,6 +72,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "Авто-принятие изменений",
|
||||
"command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений",
|
||||
"command.workspace.toggle": "Переключить рабочие пространства",
|
||||
"command.workspace.toggle.description": "Включить или отключить несколько рабочих пространств в боковой панели",
|
||||
"command.session.undo": "Отменить",
|
||||
"command.session.undo.description": "Отменить последнее сообщение",
|
||||
"command.session.redo": "Повторить",
|
||||
@@ -95,9 +96,13 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "Популярные",
|
||||
"dialog.provider.group.other": "Другие",
|
||||
"dialog.provider.tag.recommended": "Рекомендуемые",
|
||||
"dialog.provider.anthropic.note": "Подключитесь с помощью Claude Pro/Max или API ключа",
|
||||
"dialog.provider.openai.note": "Подключитесь с помощью ChatGPT Pro/Plus или API ключа",
|
||||
"dialog.provider.copilot.note": "Подключитесь с помощью Copilot или API ключа",
|
||||
"dialog.provider.opencode.note": "Отобранные модели, включая Claude, GPT, Gemini и другие",
|
||||
"dialog.provider.anthropic.note": "Прямой доступ к моделям Claude, включая Pro и Max",
|
||||
"dialog.provider.copilot.note": "Модели Claude для помощи в кодировании",
|
||||
"dialog.provider.openai.note": "Модели GPT для быстрых и мощных задач общего ИИ",
|
||||
"dialog.provider.google.note": "Модели Gemini для быстрых и структурированных ответов",
|
||||
"dialog.provider.openrouter.note": "Доступ ко всем поддерживаемым моделям через одного провайдера",
|
||||
"dialog.provider.vercel.note": "Единый доступ к ИИ-моделям с умной маршрутизацией",
|
||||
|
||||
"dialog.model.select.title": "Выбрать модель",
|
||||
"dialog.model.search.placeholder": "Поиск моделей",
|
||||
@@ -145,6 +150,44 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} подключён",
|
||||
"provider.connect.toast.connected.description": "Модели {{provider}} теперь доступны.",
|
||||
|
||||
"provider.custom.title": "Пользовательский провайдер",
|
||||
"provider.custom.description.prefix": "Настройте OpenAI-совместимого провайдера. См. ",
|
||||
"provider.custom.description.link": "документацию по настройке провайдера",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "ID провайдера",
|
||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "Строчные буквы, цифры, дефисы или подчёркивания",
|
||||
"provider.custom.field.name.label": "Отображаемое имя",
|
||||
"provider.custom.field.name.placeholder": "Мой AI провайдер",
|
||||
"provider.custom.field.baseURL.label": "Базовый URL",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "API ключ",
|
||||
"provider.custom.field.apiKey.placeholder": "API ключ",
|
||||
"provider.custom.field.apiKey.description":
|
||||
"Необязательно. Оставьте пустым, если управляете авторизацией через заголовки.",
|
||||
"provider.custom.models.label": "Модели",
|
||||
"provider.custom.models.id.label": "ID",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "Имя",
|
||||
"provider.custom.models.name.placeholder": "Отображаемое имя",
|
||||
"provider.custom.models.remove": "Удалить модель",
|
||||
"provider.custom.models.add": "Добавить модель",
|
||||
"provider.custom.headers.label": "Заголовки (необязательно)",
|
||||
"provider.custom.headers.key.label": "Заголовок",
|
||||
"provider.custom.headers.key.placeholder": "Header-Name",
|
||||
"provider.custom.headers.value.label": "Значение",
|
||||
"provider.custom.headers.value.placeholder": "значение",
|
||||
"provider.custom.headers.remove": "Удалить заголовок",
|
||||
"provider.custom.headers.add": "Добавить заголовок",
|
||||
"provider.custom.error.providerID.required": "Требуется ID провайдера",
|
||||
"provider.custom.error.providerID.format": "Используйте строчные буквы, цифры, дефисы или подчёркивания",
|
||||
"provider.custom.error.providerID.exists": "Такой ID провайдера уже существует",
|
||||
"provider.custom.error.name.required": "Требуется отображаемое имя",
|
||||
"provider.custom.error.baseURL.required": "Требуется базовый URL",
|
||||
"provider.custom.error.baseURL.format": "Должен начинаться с http:// или https://",
|
||||
"provider.custom.error.required": "Обязательно",
|
||||
"provider.custom.error.duplicate": "Дубликат",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} отключён",
|
||||
"provider.disconnect.toast.disconnected.description": "Модели {{provider}} больше недоступны.",
|
||||
"model.tag.free": "Бесплатно",
|
||||
@@ -166,6 +209,7 @@ export const dict = {
|
||||
|
||||
"common.search.placeholder": "Поиск",
|
||||
"common.goBack": "Назад",
|
||||
"common.goForward": "Вперёд",
|
||||
"common.loading": "Загрузка",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "Отмена",
|
||||
@@ -327,6 +371,23 @@ export const dict = {
|
||||
"context.usage.clickToView": "Нажмите для просмотра контекста",
|
||||
"context.usage.view": "Показать использование контекста",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "Язык",
|
||||
"toast.language.description": "Переключено на {{language}}",
|
||||
|
||||
@@ -384,6 +445,7 @@ export const dict = {
|
||||
"Корневой элемент не найден. Вы забыли добавить его в index.html? Или, может быть, атрибут id был написан неправильно?",
|
||||
|
||||
"error.globalSync.connectFailed": "Не удалось подключиться к серверу. Запущен ли сервер по адресу `{{url}}`?",
|
||||
"directory.error.invalidUrl": "Недопустимая директория в URL.",
|
||||
|
||||
"error.chain.unknown": "Неизвестная ошибка",
|
||||
"error.chain.causedBy": "Причина:",
|
||||
@@ -450,6 +512,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "Поиск {{project}}",
|
||||
"session.header.searchFiles": "Поиск файлов",
|
||||
"session.header.openIn": "Открыть в",
|
||||
"session.header.open.action": "Открыть {{app}}",
|
||||
"session.header.open.ariaLabel": "Открыть в {{app}}",
|
||||
"session.header.open.menu": "Варианты открытия",
|
||||
"session.header.open.copyPath": "Копировать путь",
|
||||
|
||||
"status.popover.trigger": "Статус",
|
||||
"status.popover.ariaLabel": "Настройки серверов",
|
||||
@@ -517,11 +584,15 @@ export const dict = {
|
||||
"settings.section.server": "Сервер",
|
||||
"settings.tab.general": "Основные",
|
||||
"settings.tab.shortcuts": "Горячие клавиши",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "Интеграция с WSL",
|
||||
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
|
||||
|
||||
"settings.general.section.appearance": "Внешний вид",
|
||||
"settings.general.section.notifications": "Системные уведомления",
|
||||
"settings.general.section.updates": "Обновления",
|
||||
"settings.general.section.sounds": "Звуковые эффекты",
|
||||
"settings.general.section.display": "Дисплей",
|
||||
|
||||
"settings.general.row.language.title": "Язык",
|
||||
"settings.general.row.language.description": "Изменить язык отображения OpenCode",
|
||||
@@ -532,6 +603,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Шрифт",
|
||||
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
|
||||
|
||||
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
||||
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
"На Linux с мониторами разной частоты обновления нативный Wayland может быть стабильнее.",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "Примечания к выпуску",
|
||||
"settings.general.row.releaseNotes.description": 'Показывать всплывающие окна "Что нового" после обновлений',
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"command.workspace.toggle": "สลับพื้นที่ทำงาน",
|
||||
"command.workspace.toggle.description": "เปิดหรือปิดใช้งานพื้นที่ทำงานหลายรายการในแถบด้านข้าง",
|
||||
"command.session.undo": "ยกเลิก",
|
||||
"command.session.undo.description": "ยกเลิกข้อความล่าสุด",
|
||||
"command.session.redo": "ทำซ้ำ",
|
||||
@@ -149,6 +150,43 @@ export const dict = {
|
||||
"provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว",
|
||||
"provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว",
|
||||
|
||||
"provider.custom.title": "ผู้ให้บริการที่กำหนดเอง",
|
||||
"provider.custom.description.prefix": "กำหนดค่าผู้ให้บริการที่เข้ากันได้กับ OpenAI ดู ",
|
||||
"provider.custom.description.link": "เอกสารการกำหนดค่าผู้ให้บริการ",
|
||||
"provider.custom.description.suffix": ".",
|
||||
"provider.custom.field.providerID.label": "รหัสผู้ให้บริการ",
|
||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
|
||||
"provider.custom.field.name.label": "ชื่อที่แสดง",
|
||||
"provider.custom.field.name.placeholder": "My AI Provider",
|
||||
"provider.custom.field.baseURL.label": "URL พื้นฐาน",
|
||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||
"provider.custom.field.apiKey.label": "คีย์ API",
|
||||
"provider.custom.field.apiKey.placeholder": "คีย์ API",
|
||||
"provider.custom.field.apiKey.description": "ไม่บังคับ เว้นว่างไว้หากคุณจัดการการยืนยันตัวตนผ่านส่วนหัว",
|
||||
"provider.custom.models.label": "โมเดล",
|
||||
"provider.custom.models.id.label": "รหัส",
|
||||
"provider.custom.models.id.placeholder": "model-id",
|
||||
"provider.custom.models.name.label": "ชื่อ",
|
||||
"provider.custom.models.name.placeholder": "ชื่อที่แสดง",
|
||||
"provider.custom.models.remove": "ลบโมเดล",
|
||||
"provider.custom.models.add": "เพิ่มโมเดล",
|
||||
"provider.custom.headers.label": "ส่วนหัว (ไม่บังคับ)",
|
||||
"provider.custom.headers.key.label": "ส่วนหัว",
|
||||
"provider.custom.headers.key.placeholder": "Header-Name",
|
||||
"provider.custom.headers.value.label": "ค่า",
|
||||
"provider.custom.headers.value.placeholder": "ค่า",
|
||||
"provider.custom.headers.remove": "ลบส่วนหัว",
|
||||
"provider.custom.headers.add": "เพิ่มส่วนหัว",
|
||||
"provider.custom.error.providerID.required": "ต้องระบุรหัสผู้ให้บริการ",
|
||||
"provider.custom.error.providerID.format": "ใช้ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
|
||||
"provider.custom.error.providerID.exists": "รหัสผู้ให้บริการนั้นมีอยู่แล้ว",
|
||||
"provider.custom.error.name.required": "ต้องระบุชื่อที่แสดง",
|
||||
"provider.custom.error.baseURL.required": "ต้องระบุ URL พื้นฐาน",
|
||||
"provider.custom.error.baseURL.format": "ต้องขึ้นต้นด้วย http:// หรือ https://",
|
||||
"provider.custom.error.required": "จำเป็น",
|
||||
"provider.custom.error.duplicate": "ซ้ำ",
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} ที่ยกเลิกการเชื่อมต่อแล้ว",
|
||||
"provider.disconnect.toast.disconnected.description": "โมเดล {{provider}} ไม่พร้อมใช้งานอีกต่อไป",
|
||||
|
||||
@@ -163,7 +201,7 @@ export const dict = {
|
||||
"model.input.image": "รูปภาพ",
|
||||
"model.input.audio": "เสียง",
|
||||
"model.input.video": "วิดีโอ",
|
||||
"model.input.pdf": "pdf",
|
||||
"model.input.pdf": "PDF",
|
||||
"model.tooltip.allows": "อนุญาต: {{inputs}}",
|
||||
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
|
||||
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
|
||||
@@ -171,6 +209,7 @@ export const dict = {
|
||||
|
||||
"common.search.placeholder": "ค้นหา",
|
||||
"common.goBack": "ย้อนกลับ",
|
||||
"common.goForward": "นำทางไปข้างหน้า",
|
||||
"common.loading": "กำลังโหลด",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "ยกเลิก",
|
||||
@@ -220,8 +259,8 @@ export const dict = {
|
||||
"prompt.dropzone.label": "วางรูปภาพหรือ PDF ที่นี่",
|
||||
"prompt.dropzone.file.label": "วางเพื่อ @กล่าวถึงไฟล์",
|
||||
"prompt.slash.badge.custom": "กำหนดเอง",
|
||||
"prompt.slash.badge.skill": "skill",
|
||||
"prompt.slash.badge.mcp": "mcp",
|
||||
"prompt.slash.badge.skill": "ทักษะ",
|
||||
"prompt.slash.badge.mcp": "MCP",
|
||||
"prompt.context.active": "ใช้งานอยู่",
|
||||
"prompt.context.includeActiveFile": "รวมไฟล์ที่ใช้งานอยู่",
|
||||
"prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
|
||||
@@ -330,22 +369,39 @@ export const dict = {
|
||||
"context.usage.clickToView": "คลิกเพื่อดูบริบท",
|
||||
"context.usage.view": "ดูการใช้บริบท",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "ภาษา",
|
||||
"toast.language.description": "สลับไปที่ {{language}}",
|
||||
|
||||
"toast.theme.title": "สลับธีมแล้ว",
|
||||
"toast.scheme.title": "โทนสี",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "กำลังยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.on.description": "สิทธิ์การแก้ไขและจะได้รับเขียนการอนุมัติโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.off.title": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.off.description": "สิทธิ์การแก้ไขและเขียนจะต้องได้รับการอนุมัติ",
|
||||
|
||||
"toast.workspace.enabled.title": "เปิดใช้งานพื้นที่ทำงานแล้ว",
|
||||
"toast.workspace.enabled.description": "ตอนนี้จะแสดง worktree หลายรายการในแถบด้านข้าง",
|
||||
"toast.workspace.disabled.title": "ปิดใช้งานพื้นที่ทำงานแล้ว",
|
||||
"toast.workspace.disabled.description": "จะแสดงเฉพาะ worktree หลักในแถบด้านข้าง",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "กำลังยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.on.description": "สิทธิ์การแก้ไขและจะได้รับเขียนการอนุมัติโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.off.title": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
|
||||
"toast.permissions.autoaccept.off.description": "สิทธิ์การแก้ไขและเขียนจะต้องได้รับการอนุมัติ",
|
||||
|
||||
"toast.model.none.title": "ไม่ได้เลือกโมเดล",
|
||||
"toast.model.none.description": "เชื่อมต่อผู้ให้บริการเพื่อสรุปเซสชันนี้",
|
||||
|
||||
@@ -387,6 +443,7 @@ export const dict = {
|
||||
"error.dev.rootNotFound": "ไม่พบองค์ประกอบรูท คุณลืมเพิ่มใน index.html หรือบางทีแอตทริบิวต์ id อาจสะกดผิด?",
|
||||
|
||||
"error.globalSync.connectFailed": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ มีเซิร์ฟเวอร์ทำงานอยู่ที่ `{{url}}` หรือไม่?",
|
||||
"directory.error.invalidUrl": "ไดเรกทอรีใน URL ไม่ถูกต้อง",
|
||||
|
||||
"error.chain.unknown": "ข้อผิดพลาดที่ไม่รู้จัก",
|
||||
"error.chain.causedBy": "สาเหตุ:",
|
||||
@@ -452,6 +509,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "ค้นหา {{project}}",
|
||||
"session.header.searchFiles": "ค้นหาไฟล์",
|
||||
"session.header.openIn": "เปิดใน",
|
||||
"session.header.open.action": "เปิด {{app}}",
|
||||
"session.header.open.ariaLabel": "เปิดใน {{app}}",
|
||||
"session.header.open.menu": "ตัวเลือกการเปิด",
|
||||
"session.header.open.copyPath": "คัดลอกเส้นทาง",
|
||||
|
||||
"status.popover.trigger": "สถานะ",
|
||||
"status.popover.ariaLabel": "การกำหนดค่าเซิร์ฟเวอร์",
|
||||
@@ -516,11 +578,15 @@ export const dict = {
|
||||
"settings.section.server": "เซิร์ฟเวอร์",
|
||||
"settings.tab.general": "ทั่วไป",
|
||||
"settings.tab.shortcuts": "ทางลัด",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "การรวม WSL",
|
||||
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
|
||||
|
||||
"settings.general.section.appearance": "รูปลักษณ์",
|
||||
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||
"settings.general.section.updates": "การอัปเดต",
|
||||
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||
"settings.general.section.display": "การแสดงผล",
|
||||
|
||||
"settings.general.row.language.title": "ภาษา",
|
||||
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
|
||||
@@ -531,8 +597,22 @@ export const dict = {
|
||||
"settings.general.row.font.title": "ฟอนต์",
|
||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
||||
|
||||
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
||||
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "บันทึกการอัปเดต",
|
||||
"settings.general.row.releaseNotes.description": "แสดงป๊อปอัพ What's New หลังจากอัปเดต",
|
||||
|
||||
"settings.updates.row.startup.title": "ตรวจสอบการอัปเดตเมื่อเริ่มต้น",
|
||||
"settings.updates.row.startup.description": "ตรวจสอบการอัปเดตโดยอัตโนมัติเมื่อ OpenCode เปิดใช้งาน",
|
||||
"settings.updates.row.check.title": "ตรวจสอบการอัปเดต",
|
||||
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
|
||||
"settings.updates.action.checkNow": "ตรวจสอบทันที",
|
||||
"settings.updates.action.checking": "กำลังตรวจสอบ...",
|
||||
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
||||
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
||||
|
||||
"font.option.ibmPlexMono": "IBM Plex Mono",
|
||||
"font.option.cascadiaCode": "Cascadia Code",
|
||||
"font.option.firaCode": "Fira Code",
|
||||
|
||||
@@ -19,17 +19,22 @@ export const dict = {
|
||||
"command.category.agent": "智能体",
|
||||
"command.category.permissions": "权限",
|
||||
"command.category.workspace": "工作区",
|
||||
|
||||
"command.category.settings": "设置",
|
||||
|
||||
"theme.scheme.system": "系统",
|
||||
"theme.scheme.light": "浅色",
|
||||
"theme.scheme.dark": "深色",
|
||||
|
||||
"command.sidebar.toggle": "切换侧边栏",
|
||||
|
||||
"command.project.open": "打开项目",
|
||||
|
||||
"command.provider.connect": "连接提供商",
|
||||
|
||||
"command.server.switch": "切换服务器",
|
||||
|
||||
"command.settings.open": "打开设置",
|
||||
|
||||
"command.session.previous": "上一个会话",
|
||||
"command.session.next": "下一个会话",
|
||||
"command.session.previous.unseen": "上一个未读会话",
|
||||
@@ -47,35 +52,53 @@ export const dict = {
|
||||
"command.language.set": "使用语言:{{language}}",
|
||||
|
||||
"command.session.new": "新建会话",
|
||||
|
||||
"command.file.open": "打开文件",
|
||||
|
||||
"command.tab.close": "关闭标签页",
|
||||
|
||||
"command.context.addSelection": "将所选内容添加到上下文",
|
||||
"command.context.addSelection.description": "添加当前文件中选中的行",
|
||||
|
||||
"command.input.focus": "聚焦输入框",
|
||||
|
||||
"command.terminal.toggle": "切换终端",
|
||||
|
||||
"command.fileTree.toggle": "切换文件树",
|
||||
|
||||
"command.review.toggle": "切换审查",
|
||||
|
||||
"command.terminal.new": "新建终端",
|
||||
"command.terminal.new.description": "创建新的终端标签页",
|
||||
|
||||
"command.steps.toggle": "切换步骤",
|
||||
"command.steps.toggle.description": "显示或隐藏当前消息的步骤",
|
||||
|
||||
"command.message.previous": "上一条消息",
|
||||
"command.message.previous.description": "跳转到上一条用户消息",
|
||||
"command.message.next": "下一条消息",
|
||||
"command.message.next.description": "跳转到下一条用户消息",
|
||||
|
||||
"command.model.choose": "选择模型",
|
||||
"command.model.choose.description": "选择不同的模型",
|
||||
|
||||
"command.mcp.toggle": "切换 MCPs",
|
||||
"command.mcp.toggle.description": "切换 MCPs",
|
||||
|
||||
"command.agent.cycle": "切换智能体",
|
||||
"command.agent.cycle.description": "切换到下一个智能体",
|
||||
"command.agent.cycle.reverse": "反向切换智能体",
|
||||
"command.agent.cycle.reverse.description": "切换到上一个智能体",
|
||||
|
||||
"command.model.variant.cycle": "切换思考强度",
|
||||
"command.model.variant.cycle.description": "切换到下一个强度等级",
|
||||
|
||||
"command.permissions.autoaccept.enable": "自动接受编辑",
|
||||
"command.permissions.autoaccept.disable": "停止自动接受编辑",
|
||||
|
||||
"command.workspace.toggle": "切换工作区",
|
||||
"command.workspace.toggle.description": "在侧边栏启用或禁用多个工作区",
|
||||
|
||||
"command.session.undo": "撤销",
|
||||
"command.session.undo.description": "撤销上一条消息",
|
||||
"command.session.redo": "重做",
|
||||
@@ -99,10 +122,10 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "热门",
|
||||
"dialog.provider.group.other": "其他",
|
||||
"dialog.provider.tag.recommended": "推荐",
|
||||
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接",
|
||||
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接",
|
||||
"dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接",
|
||||
"dialog.provider.opencode.note": "使用 OpenCode Zen 或 API 密钥连接",
|
||||
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 密钥连接",
|
||||
"dialog.provider.copilot.note": "使用 Copilot 或 API 密钥连接",
|
||||
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 密钥连接",
|
||||
"dialog.provider.google.note": "使用 Google 账号或 API 密钥连接",
|
||||
"dialog.provider.openrouter.note": "使用 OpenRouter 账号或 API 密钥连接",
|
||||
"dialog.provider.vercel.note": "使用 Vercel 账号或 API 密钥连接",
|
||||
@@ -112,7 +135,6 @@ export const dict = {
|
||||
"dialog.model.empty": "未找到模型",
|
||||
"dialog.model.manage": "管理模型",
|
||||
"dialog.model.manage.description": "自定义模型选择器中显示的模型。",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "OpenCode 提供的免费模型",
|
||||
"dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型",
|
||||
|
||||
@@ -188,9 +210,9 @@ export const dict = {
|
||||
|
||||
"provider.disconnect.toast.disconnected.title": "{{provider}} 已断开连接",
|
||||
"provider.disconnect.toast.disconnected.description": "{{provider}} 模型已不再可用。",
|
||||
|
||||
"model.tag.free": "免费",
|
||||
"model.tag.latest": "最新",
|
||||
|
||||
"model.provider.anthropic": "Anthropic",
|
||||
"model.provider.openai": "OpenAI",
|
||||
"model.provider.google": "Google",
|
||||
@@ -205,8 +227,10 @@ export const dict = {
|
||||
"model.tooltip.reasoning.allowed": "支持推理",
|
||||
"model.tooltip.reasoning.none": "不支持推理",
|
||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||
|
||||
"common.search.placeholder": "搜索",
|
||||
"common.goBack": "返回",
|
||||
"common.goForward": "前进",
|
||||
"common.loading": "加载中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "取消",
|
||||
@@ -224,7 +248,6 @@ export const dict = {
|
||||
"prompt.placeholder.summarizeComment": "总结该评论…",
|
||||
"prompt.mode.shell": "Shell",
|
||||
"prompt.mode.shell.exit": "按 esc 退出",
|
||||
|
||||
"prompt.example.1": "修复代码库中的一个 TODO",
|
||||
"prompt.example.2": "这个项目的技术栈是什么?",
|
||||
"prompt.example.3": "修复失败的测试",
|
||||
@@ -250,7 +273,6 @@ export const dict = {
|
||||
"prompt.example.23": "给这个列表添加分页",
|
||||
"prompt.example.24": "创建一个 CLI 命令用于...",
|
||||
"prompt.example.25": "这里的环境变量是怎么工作的?",
|
||||
|
||||
"prompt.popover.emptyResults": "没有匹配的结果",
|
||||
"prompt.popover.emptyCommands": "没有匹配的命令",
|
||||
"prompt.dropzone.label": "将图片或 PDF 拖到这里",
|
||||
@@ -266,7 +288,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "移除附件",
|
||||
"prompt.action.send": "发送",
|
||||
"prompt.action.stop": "停止",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "不支持的粘贴",
|
||||
"prompt.toast.pasteUnsupported.description": "这里只能粘贴图片或 PDF 文件。",
|
||||
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
|
||||
@@ -283,6 +304,7 @@ export const dict = {
|
||||
"dialog.mcp.empty": "未配置 MCPs",
|
||||
|
||||
"dialog.lsp.empty": "已从文件类型自动检测到 LSPs",
|
||||
|
||||
"dialog.plugins.empty": "在 opencode.json 中配置的插件",
|
||||
|
||||
"mcp.status.connected": "已连接",
|
||||
@@ -311,7 +333,6 @@ export const dict = {
|
||||
"dialog.server.default.set": "将当前服务器设为默认",
|
||||
"dialog.server.default.clear": "清除",
|
||||
"dialog.server.action.remove": "移除服务器",
|
||||
|
||||
"dialog.server.menu.edit": "编辑",
|
||||
"dialog.server.menu.default": "设为默认",
|
||||
"dialog.server.menu.defaultRemove": "取消默认",
|
||||
@@ -327,10 +348,10 @@ export const dict = {
|
||||
"dialog.project.edit.icon.recommended": "建议:128x128px",
|
||||
"dialog.project.edit.color": "颜色",
|
||||
"dialog.project.edit.color.select": "选择{{color}}颜色",
|
||||
|
||||
"dialog.project.edit.worktree.startup": "工作区启动脚本",
|
||||
"dialog.project.edit.worktree.startup.description": "在创建新的工作区 (worktree) 后运行。",
|
||||
"dialog.project.edit.worktree.startup.placeholder": "例如 bun install",
|
||||
|
||||
"context.breakdown.title": "上下文拆分",
|
||||
"context.breakdown.note": "输入 token 的大致拆分。“其他”包含工具定义和开销。",
|
||||
"context.breakdown.system": "系统",
|
||||
@@ -338,10 +359,8 @@ export const dict = {
|
||||
"context.breakdown.assistant": "助手",
|
||||
"context.breakdown.tool": "工具调用",
|
||||
"context.breakdown.other": "其他",
|
||||
|
||||
"context.systemPrompt.title": "系统提示词",
|
||||
"context.rawMessages.title": "原始消息",
|
||||
|
||||
"context.stats.session": "会话",
|
||||
"context.stats.messages": "消息数",
|
||||
"context.stats.provider": "提供商",
|
||||
@@ -358,34 +377,44 @@ export const dict = {
|
||||
"context.stats.totalCost": "总成本",
|
||||
"context.stats.sessionCreated": "创建时间",
|
||||
"context.stats.lastActivity": "最后活动",
|
||||
|
||||
"context.usage.tokens": "Token",
|
||||
"context.usage.usage": "使用率",
|
||||
"context.usage.cost": "成本",
|
||||
"context.usage.clickToView": "点击查看上下文",
|
||||
"context.usage.view": "查看上下文用量",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "语言",
|
||||
"toast.language.description": "已切换到{{language}}",
|
||||
|
||||
"toast.theme.title": "主题已切换",
|
||||
"toast.scheme.title": "颜色方案",
|
||||
|
||||
"toast.workspace.enabled.title": "工作区已启用",
|
||||
"toast.workspace.enabled.description": "侧边栏现在显示多个工作树",
|
||||
"toast.workspace.disabled.title": "工作区已禁用",
|
||||
"toast.workspace.disabled.description": "侧边栏只显示主工作树",
|
||||
|
||||
"toast.permissions.autoaccept.on.title": "自动接受编辑",
|
||||
"toast.permissions.autoaccept.on.description": "编辑和写入权限将自动获批",
|
||||
"toast.permissions.autoaccept.off.title": "已停止自动接受编辑",
|
||||
"toast.permissions.autoaccept.off.description": "编辑和写入权限将需要手动批准",
|
||||
|
||||
"toast.model.none.title": "未选择模型",
|
||||
"toast.model.none.description": "请先连接提供商以总结此会话",
|
||||
|
||||
"toast.file.loadFailed.title": "加载文件失败",
|
||||
|
||||
"toast.file.listFailed.title": "列出文件失败",
|
||||
"toast.context.noLineSelection.title": "未选择行",
|
||||
"toast.context.noLineSelection.description": "请先在文件标签中选择行范围。",
|
||||
@@ -394,14 +423,11 @@ export const dict = {
|
||||
"toast.session.share.success.description": "分享链接已复制到剪贴板",
|
||||
"toast.session.share.failed.title": "分享会话失败",
|
||||
"toast.session.share.failed.description": "分享会话时发生错误",
|
||||
|
||||
"toast.session.unshare.success.title": "已取消分享会话",
|
||||
"toast.session.unshare.success.description": "会话已成功取消分享",
|
||||
"toast.session.unshare.failed.title": "取消分享失败",
|
||||
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
|
||||
|
||||
"toast.session.listFailed.title": "无法加载 {{project}} 的会话",
|
||||
|
||||
"toast.update.title": "有可用更新",
|
||||
"toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。",
|
||||
"toast.update.action.installRestart": "安装并重启",
|
||||
@@ -417,10 +443,9 @@ export const dict = {
|
||||
"error.page.report.prefix": "请将此错误报告给 OpenCode 团队",
|
||||
"error.page.report.discord": "在 Discord 上",
|
||||
"error.page.version": "版本:{{version}}",
|
||||
|
||||
"error.dev.rootNotFound": "未找到根元素。你是不是忘了把它添加到 index.html?或者 id 属性拼写错了?",
|
||||
|
||||
"error.globalSync.connectFailed": "无法连接到服务器。是否有服务器正在 `{{url}}` 运行?",
|
||||
|
||||
"directory.error.invalidUrl": "URL 中的目录无效。",
|
||||
|
||||
"error.chain.unknown": "未知错误",
|
||||
@@ -448,7 +473,6 @@ export const dict = {
|
||||
"notification.question.title": "问题",
|
||||
"notification.question.description": "{{sessionTitle}}({{projectName}})有一个问题",
|
||||
"notification.action.goToSession": "前往会话",
|
||||
|
||||
"notification.session.responseReady.title": "回复已就绪",
|
||||
"notification.session.error.title": "会话错误",
|
||||
"notification.session.error.fallbackDescription": "发生错误",
|
||||
@@ -474,17 +498,19 @@ export const dict = {
|
||||
"session.messages.loadingEarlier": "正在加载更早的消息...",
|
||||
"session.messages.loadEarlier": "加载更早的消息",
|
||||
"session.messages.loading": "正在加载消息...",
|
||||
|
||||
"session.messages.jumpToLatest": "跳转到最新",
|
||||
"session.context.addToContext": "将 {{selection}} 添加到上下文",
|
||||
|
||||
"session.new.worktree.main": "主分支",
|
||||
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
||||
"session.new.worktree.create": "创建新的 worktree",
|
||||
"session.new.lastModified": "最后修改",
|
||||
|
||||
"session.header.search.placeholder": "搜索 {{project}}",
|
||||
"session.header.searchFiles": "搜索文件",
|
||||
"session.header.openIn": "打开方式",
|
||||
"session.header.open.action": "打开 {{app}}",
|
||||
"session.header.open.ariaLabel": "在 {{app}} 中打开",
|
||||
"session.header.open.menu": "打开选项",
|
||||
"session.header.open.copyPath": "复制路径",
|
||||
|
||||
"status.popover.trigger": "状态",
|
||||
"status.popover.ariaLabel": "服务器配置",
|
||||
@@ -510,13 +536,14 @@ export const dict = {
|
||||
"lsp.label.connected": "{{count}} LSP",
|
||||
|
||||
"prompt.loading": "正在加载提示...",
|
||||
|
||||
"terminal.loading": "正在加载终端...",
|
||||
"terminal.title": "终端",
|
||||
"terminal.title.numbered": "终端 {{number}}",
|
||||
"terminal.close": "关闭终端",
|
||||
|
||||
"terminal.connectionLost.title": "连接已丢失",
|
||||
"terminal.connectionLost.description": "终端连接已中断。这可能发生在服务器重启时。",
|
||||
|
||||
"common.closeTab": "关闭标签页",
|
||||
"common.dismiss": "忽略",
|
||||
"common.requestFailed": "请求失败",
|
||||
@@ -529,8 +556,8 @@ export const dict = {
|
||||
"common.close": "关闭",
|
||||
"common.edit": "编辑",
|
||||
"common.loadMore": "加载更多",
|
||||
|
||||
"common.key.esc": "ESC",
|
||||
|
||||
"sidebar.menu.toggle": "切换菜单",
|
||||
"sidebar.nav.projectsAndSessions": "项目和会话",
|
||||
"sidebar.settings": "设置",
|
||||
@@ -544,16 +571,22 @@ export const dict = {
|
||||
"sidebar.project.viewAllSessions": "查看全部会话",
|
||||
|
||||
"app.name.desktop": "OpenCode Desktop",
|
||||
|
||||
"settings.section.desktop": "桌面",
|
||||
"settings.section.server": "服务器",
|
||||
|
||||
"settings.tab.general": "通用",
|
||||
"settings.tab.shortcuts": "快捷键",
|
||||
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL 集成",
|
||||
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
|
||||
|
||||
"settings.general.section.appearance": "外观",
|
||||
"settings.general.section.notifications": "系统通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
|
||||
"settings.general.section.display": "显示",
|
||||
"settings.general.row.language.title": "语言",
|
||||
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
||||
"settings.general.row.appearance.title": "外观",
|
||||
@@ -562,6 +595,9 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||
"settings.general.row.font.title": "字体",
|
||||
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||
"settings.general.row.releaseNotes.title": "发行说明",
|
||||
"settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗",
|
||||
|
||||
@@ -586,6 +622,7 @@ export const dict = {
|
||||
"font.option.robotoMono": "Roboto Mono",
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
|
||||
"sound.option.alert01": "警报 01",
|
||||
"sound.option.alert02": "警报 02",
|
||||
"sound.option.alert03": "警报 03",
|
||||
@@ -631,13 +668,13 @@ export const dict = {
|
||||
"sound.option.yup04": "是 04",
|
||||
"sound.option.yup05": "是 05",
|
||||
"sound.option.yup06": "是 06",
|
||||
|
||||
"settings.general.notifications.agent.title": "智能体",
|
||||
"settings.general.notifications.agent.description": "当智能体完成或需要注意时显示系统通知",
|
||||
"settings.general.notifications.permissions.title": "权限",
|
||||
"settings.general.notifications.permissions.description": "当需要权限时显示系统通知",
|
||||
"settings.general.notifications.errors.title": "错误",
|
||||
"settings.general.notifications.errors.description": "发生错误时显示系统通知",
|
||||
|
||||
"settings.general.sounds.agent.title": "智能体",
|
||||
"settings.general.sounds.agent.description": "当智能体完成或需要注意时播放声音",
|
||||
"settings.general.sounds.permissions.title": "权限",
|
||||
@@ -655,7 +692,6 @@ export const dict = {
|
||||
"settings.shortcuts.pressKeys": "按下按键",
|
||||
"settings.shortcuts.search.placeholder": "搜索快捷键",
|
||||
"settings.shortcuts.search.empty": "未找到快捷键",
|
||||
|
||||
"settings.shortcuts.group.general": "通用",
|
||||
"settings.shortcuts.group.session": "会话",
|
||||
"settings.shortcuts.group.navigation": "导航",
|
||||
@@ -672,12 +708,16 @@ export const dict = {
|
||||
"settings.providers.tag.config": "配置",
|
||||
"settings.providers.tag.custom": "自定义",
|
||||
"settings.providers.tag.other": "其他",
|
||||
|
||||
"settings.models.title": "模型",
|
||||
"settings.models.description": "模型设置将在此处可配置。",
|
||||
|
||||
"settings.agents.title": "智能体",
|
||||
"settings.agents.description": "智能体设置将在此处可配置。",
|
||||
|
||||
"settings.commands.title": "命令",
|
||||
"settings.commands.description": "命令设置将在此处可配置。",
|
||||
|
||||
"settings.mcp.title": "MCP",
|
||||
"settings.mcp.description": "MCP 设置将在此处可配置。",
|
||||
|
||||
@@ -685,11 +725,9 @@ export const dict = {
|
||||
"settings.permissions.description": "控制服务器默认可以使用哪些工具。",
|
||||
"settings.permissions.section.tools": "工具",
|
||||
"settings.permissions.toast.updateFailed.title": "更新权限失败",
|
||||
|
||||
"settings.permissions.action.allow": "允许",
|
||||
"settings.permissions.action.ask": "询问",
|
||||
"settings.permissions.action.deny": "拒绝",
|
||||
|
||||
"settings.permissions.tool.read.title": "读取",
|
||||
"settings.permissions.tool.read.description": "读取文件(匹配文件路径)",
|
||||
"settings.permissions.tool.edit.title": "编辑",
|
||||
@@ -702,9 +740,9 @@ export const dict = {
|
||||
"settings.permissions.tool.list.description": "列出目录中的文件",
|
||||
"settings.permissions.tool.bash.title": "Bash",
|
||||
"settings.permissions.tool.bash.description": "运行 shell 命令",
|
||||
"settings.permissions.tool.task.title": "Task",
|
||||
"settings.permissions.tool.task.title": "任务",
|
||||
"settings.permissions.tool.task.description": "启动子智能体",
|
||||
"settings.permissions.tool.skill.title": "Skill",
|
||||
"settings.permissions.tool.skill.title": "技能",
|
||||
"settings.permissions.tool.skill.description": "按名称加载技能",
|
||||
"settings.permissions.tool.lsp.title": "LSP",
|
||||
"settings.permissions.tool.lsp.description": "运行语言服务器查询",
|
||||
@@ -712,15 +750,15 @@ export const dict = {
|
||||
"settings.permissions.tool.todoread.description": "读取待办列表",
|
||||
"settings.permissions.tool.todowrite.title": "更新待办",
|
||||
"settings.permissions.tool.todowrite.description": "更新待办列表",
|
||||
"settings.permissions.tool.webfetch.title": "Web Fetch",
|
||||
"settings.permissions.tool.webfetch.title": "网页获取",
|
||||
"settings.permissions.tool.webfetch.description": "从 URL 获取内容",
|
||||
"settings.permissions.tool.websearch.title": "Web Search",
|
||||
"settings.permissions.tool.websearch.title": "网页搜索",
|
||||
"settings.permissions.tool.websearch.description": "搜索网页",
|
||||
"settings.permissions.tool.codesearch.title": "Code Search",
|
||||
"settings.permissions.tool.codesearch.title": "代码搜索",
|
||||
"settings.permissions.tool.codesearch.description": "在网上搜索代码",
|
||||
"settings.permissions.tool.external_directory.title": "外部目录",
|
||||
"settings.permissions.tool.external_directory.description": "访问项目目录之外的文件",
|
||||
"settings.permissions.tool.doom_loop.title": "Doom Loop",
|
||||
"settings.permissions.tool.doom_loop.title": "死循环",
|
||||
"settings.permissions.tool.doom_loop.description": "检测具有相同输入的重复工具调用",
|
||||
|
||||
"session.delete.failed.title": "删除会话失败",
|
||||
|
||||
@@ -76,6 +76,7 @@ export const dict = {
|
||||
"command.permissions.autoaccept.enable": "自動接受編輯",
|
||||
"command.permissions.autoaccept.disable": "停止自動接受編輯",
|
||||
"command.workspace.toggle": "切換工作區",
|
||||
"command.workspace.toggle.description": "在側邊欄啟用或停用多個工作區",
|
||||
"command.session.undo": "復原",
|
||||
"command.session.undo.description": "復原上一則訊息",
|
||||
"command.session.redo": "重做",
|
||||
@@ -99,9 +100,13 @@ export const dict = {
|
||||
"dialog.provider.group.popular": "熱門",
|
||||
"dialog.provider.group.other": "其他",
|
||||
"dialog.provider.tag.recommended": "推薦",
|
||||
"dialog.provider.opencode.note": "精選模型,包含 Claude、GPT、Gemini 等等",
|
||||
"dialog.provider.anthropic.note": "使用 Claude Pro/Max 或 API 金鑰連線",
|
||||
"dialog.provider.openai.note": "使用 ChatGPT Pro/Plus 或 API 金鑰連線",
|
||||
"dialog.provider.copilot.note": "使用 Copilot 或 API 金鑰連線",
|
||||
"dialog.provider.google.note": "Gemini 模型,提供快速且結構化的回應",
|
||||
"dialog.provider.openrouter.note": "從單一提供者存取所有支援的模型",
|
||||
"dialog.provider.vercel.note": "透過智慧路由統一存取 AI 模型",
|
||||
|
||||
"dialog.model.select.title": "選擇模型",
|
||||
"dialog.model.search.placeholder": "搜尋模型",
|
||||
@@ -204,6 +209,7 @@ export const dict = {
|
||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||
"common.search.placeholder": "搜尋",
|
||||
"common.goBack": "返回",
|
||||
"common.goForward": "前進",
|
||||
"common.loading": "載入中",
|
||||
"common.loading.ellipsis": "...",
|
||||
"common.cancel": "取消",
|
||||
@@ -362,6 +368,23 @@ export const dict = {
|
||||
"context.usage.clickToView": "點擊查看上下文",
|
||||
"context.usage.view": "檢視上下文用量",
|
||||
|
||||
"language.en": "English",
|
||||
"language.zh": "简体中文",
|
||||
"language.zht": "繁體中文",
|
||||
"language.ko": "한국어",
|
||||
"language.de": "Deutsch",
|
||||
"language.es": "Español",
|
||||
"language.fr": "Français",
|
||||
"language.da": "Dansk",
|
||||
"language.ja": "日本語",
|
||||
"language.pl": "Polski",
|
||||
"language.ru": "Русский",
|
||||
"language.ar": "العربية",
|
||||
"language.no": "Norsk",
|
||||
"language.br": "Português (Brasil)",
|
||||
"language.bs": "Bosanski",
|
||||
"language.th": "ไทย",
|
||||
|
||||
"toast.language.title": "語言",
|
||||
"toast.language.description": "已切換到 {{language}}",
|
||||
|
||||
@@ -482,6 +505,11 @@ export const dict = {
|
||||
|
||||
"session.header.search.placeholder": "搜尋 {{project}}",
|
||||
"session.header.searchFiles": "搜尋檔案",
|
||||
"session.header.openIn": "開啟於",
|
||||
"session.header.open.action": "開啟 {{app}}",
|
||||
"session.header.open.ariaLabel": "在 {{app}} 中開啟",
|
||||
"session.header.open.menu": "開啟選項",
|
||||
"session.header.open.copyPath": "複製路徑",
|
||||
|
||||
"status.popover.trigger": "狀態",
|
||||
"status.popover.ariaLabel": "伺服器設定",
|
||||
@@ -545,11 +573,15 @@ export const dict = {
|
||||
"settings.section.server": "伺服器",
|
||||
"settings.tab.general": "一般",
|
||||
"settings.tab.shortcuts": "快速鍵",
|
||||
"settings.desktop.section.wsl": "WSL",
|
||||
"settings.desktop.wsl.title": "WSL integration",
|
||||
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
|
||||
|
||||
"settings.general.section.appearance": "外觀",
|
||||
"settings.general.section.notifications": "系統通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
"settings.general.section.display": "顯示",
|
||||
|
||||
"settings.general.row.language.title": "語言",
|
||||
"settings.general.row.language.description": "變更 OpenCode 的顯示語言",
|
||||
@@ -560,6 +592,10 @@ export const dict = {
|
||||
"settings.general.row.font.title": "字型",
|
||||
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
|
||||
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||
|
||||
"settings.general.row.releaseNotes.title": "發行說明",
|
||||
"settings.general.row.releaseNotes.description": "更新後顯示「新功能」彈出視窗",
|
||||
|
||||
|
||||
@@ -1203,6 +1203,16 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
if (!result) return
|
||||
|
||||
globalSync.set(
|
||||
"project",
|
||||
produce((draft) => {
|
||||
const project = draft.find((item) => item.worktree === root)
|
||||
if (!project) return
|
||||
project.sandboxes = (project.sandboxes ?? []).filter((sandbox) => sandbox !== directory)
|
||||
}),
|
||||
)
|
||||
setStore("workspaceOrder", root, (order) => (order ?? []).filter((workspace) => workspace !== directory))
|
||||
|
||||
layout.projects.close(directory)
|
||||
layout.projects.open(root)
|
||||
|
||||
@@ -1230,6 +1240,7 @@ export default function Layout(props: ParentProps) {
|
||||
clearWorkspaceTerminals(
|
||||
directory,
|
||||
sessions.map((s) => s.id),
|
||||
platform,
|
||||
)
|
||||
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
|
||||
|
||||
@@ -1917,10 +1928,10 @@ export default function Layout(props: ParentProps) {
|
||||
renderPanel={() => <SidebarPanel project={currentProject()} />}
|
||||
/>
|
||||
</div>
|
||||
<Show when={!layout.sidebar.opened() ? hoverProjectData() : undefined} keyed>
|
||||
{(project) => (
|
||||
<Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
|
||||
{(worktree) => (
|
||||
<div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
|
||||
<SidebarPanel project={project} />
|
||||
<SidebarPanel project={hoverProjectData()} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { uuid } from "@/utils/uuid"
|
||||
|
||||
type Nav = {
|
||||
id: string
|
||||
dir?: string
|
||||
@@ -16,8 +18,6 @@ const key = (dir: string | undefined, to: string) => `${dir ?? ""}:${to}`
|
||||
|
||||
const now = () => performance.now()
|
||||
|
||||
const uid = () => crypto.randomUUID?.() ?? Math.random().toString(16).slice(2)
|
||||
|
||||
const navs = new Map<string, Nav>()
|
||||
const pending = new Map<string, string>()
|
||||
const active = new Map<string, string>()
|
||||
@@ -94,7 +94,7 @@ function ensure(id: string, data: Omit<Nav, "marks" | "logged" | "timer">) {
|
||||
export function navStart(input: { dir?: string; from?: string; to: string; trigger?: string }) {
|
||||
if (!dev) return
|
||||
|
||||
const id = uid()
|
||||
const id = uuid()
|
||||
const start = now()
|
||||
const nav = ensure(id, { ...input, id, start })
|
||||
nav.marks["navigate:start"] = start
|
||||
@@ -109,7 +109,7 @@ export function navParams(input: { dir?: string; from?: string; to: string }) {
|
||||
const k = key(input.dir, input.to)
|
||||
const pendingId = pending.get(k)
|
||||
if (pendingId) pending.delete(k)
|
||||
const id = pendingId ?? uid()
|
||||
const id = pendingId ?? uuid()
|
||||
|
||||
const start = now()
|
||||
const nav = ensure(id, { ...input, id, start, trigger: pendingId ? "key" : "route" })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { Platform, usePlatform } from "@/context/platform"
|
||||
import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
|
||||
import { checksum } from "@opencode-ai/util/encode"
|
||||
import { createResource, type Accessor } from "solid-js"
|
||||
@@ -318,9 +318,8 @@ export const Persist = {
|
||||
},
|
||||
}
|
||||
|
||||
export function removePersisted(target: { storage?: string; key: string }) {
|
||||
const platform = usePlatform()
|
||||
const isDesktop = platform.platform === "desktop" && !!platform.storage
|
||||
export function removePersisted(target: { storage?: string; key: string }, platform?: Platform) {
|
||||
const isDesktop = platform?.platform === "desktop" && !!platform.storage
|
||||
|
||||
if (isDesktop) {
|
||||
return platform.storage?.(target.storage)?.removeItem(target.key)
|
||||
|
||||
78
packages/app/src/utils/uuid.test.ts
Normal file
78
packages/app/src/utils/uuid.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { uuid } from "./uuid"
|
||||
|
||||
const cryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, "crypto")
|
||||
const secureDescriptor = Object.getOwnPropertyDescriptor(globalThis, "isSecureContext")
|
||||
const randomDescriptor = Object.getOwnPropertyDescriptor(Math, "random")
|
||||
|
||||
const setCrypto = (value: Partial<Crypto>) => {
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
configurable: true,
|
||||
value: value as Crypto,
|
||||
})
|
||||
}
|
||||
|
||||
const setSecure = (value: boolean) => {
|
||||
Object.defineProperty(globalThis, "isSecureContext", {
|
||||
configurable: true,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
const setRandom = (value: () => number) => {
|
||||
Object.defineProperty(Math, "random", {
|
||||
configurable: true,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
if (cryptoDescriptor) {
|
||||
Object.defineProperty(globalThis, "crypto", cryptoDescriptor)
|
||||
}
|
||||
|
||||
if (secureDescriptor) {
|
||||
Object.defineProperty(globalThis, "isSecureContext", secureDescriptor)
|
||||
}
|
||||
|
||||
if (!secureDescriptor) {
|
||||
delete (globalThis as { isSecureContext?: boolean }).isSecureContext
|
||||
}
|
||||
|
||||
if (randomDescriptor) {
|
||||
Object.defineProperty(Math, "random", randomDescriptor)
|
||||
}
|
||||
})
|
||||
|
||||
describe("uuid", () => {
|
||||
test("uses randomUUID in secure contexts", () => {
|
||||
setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" })
|
||||
setSecure(true)
|
||||
expect(uuid()).toBe("00000000-0000-0000-0000-000000000000")
|
||||
})
|
||||
|
||||
test("falls back in insecure contexts", () => {
|
||||
setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" })
|
||||
setSecure(false)
|
||||
setRandom(() => 0.5)
|
||||
expect(uuid()).toBe("8")
|
||||
})
|
||||
|
||||
test("falls back when randomUUID throws", () => {
|
||||
setCrypto({
|
||||
randomUUID: () => {
|
||||
throw new DOMException("Failed", "OperationError")
|
||||
},
|
||||
})
|
||||
setSecure(true)
|
||||
setRandom(() => 0.5)
|
||||
expect(uuid()).toBe("8")
|
||||
})
|
||||
|
||||
test("falls back when randomUUID is unavailable", () => {
|
||||
setCrypto({})
|
||||
setSecure(true)
|
||||
setRandom(() => 0.5)
|
||||
expect(uuid()).toBe("8")
|
||||
})
|
||||
})
|
||||
12
packages/app/src/utils/uuid.ts
Normal file
12
packages/app/src/utils/uuid.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const fallback = () => Math.random().toString(16).slice(2)
|
||||
|
||||
export function uuid() {
|
||||
const c = globalThis.crypto
|
||||
if (!c || typeof c.randomUUID !== "function") return fallback()
|
||||
if (typeof globalThis.isSecureContext === "boolean" && !globalThis.isSecureContext) return fallback()
|
||||
try {
|
||||
return c.randomUUID()
|
||||
} catch {
|
||||
return fallback()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 697 B |
@@ -0,0 +1,18 @@
|
||||
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(30, 0)">
|
||||
<g clip-path="url(#clip0_1401_86283)">
|
||||
<mask id="mask0_1401_86283" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
|
||||
<path d="M240 0H0V300H240V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1401_86283)">
|
||||
<path d="M180 240H60V120H180V240Z" fill="#4B4646"/>
|
||||
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#F1ECEC"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1401_86283">
|
||||
<rect width="240" height="300" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
Binary file not shown.
|
After Width: | Height: | Size: 697 B |
@@ -0,0 +1,18 @@
|
||||
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(30, 0)">
|
||||
<g clip-path="url(#clip0_1401_86274)">
|
||||
<mask id="mask0_1401_86274" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="240" height="300">
|
||||
<path d="M240 0H0V300H240V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1401_86274)">
|
||||
<path d="M180 240H60V120H180V240Z" fill="#CFCECD"/>
|
||||
<path d="M180 60H60V240H180V60ZM240 300H0V0H240V300Z" fill="#211E1E"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1401_86274">
|
||||
<rect width="240" height="300" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -7,18 +7,24 @@ import { useI18n } from "~/context/i18n"
|
||||
import { LocaleLinks } from "~/component/locale-links"
|
||||
import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
|
||||
import previewLogoDark from "../../asset/brand/preview-opencode-logo-dark.png"
|
||||
import previewLogoLightSquare from "../../asset/brand/preview-opencode-logo-light-square.png"
|
||||
import previewLogoDarkSquare from "../../asset/brand/preview-opencode-logo-dark-square.png"
|
||||
import previewWordmarkLight from "../../asset/brand/preview-opencode-wordmark-light.png"
|
||||
import previewWordmarkDark from "../../asset/brand/preview-opencode-wordmark-dark.png"
|
||||
import previewWordmarkSimpleLight from "../../asset/brand/preview-opencode-wordmark-simple-light.png"
|
||||
import previewWordmarkSimpleDark from "../../asset/brand/preview-opencode-wordmark-simple-dark.png"
|
||||
import logoLightPng from "../../asset/brand/opencode-logo-light.png"
|
||||
import logoDarkPng from "../../asset/brand/opencode-logo-dark.png"
|
||||
import logoLightSquarePng from "../../asset/brand/opencode-logo-light-square.png"
|
||||
import logoDarkSquarePng from "../../asset/brand/opencode-logo-dark-square.png"
|
||||
import wordmarkLightPng from "../../asset/brand/opencode-wordmark-light.png"
|
||||
import wordmarkDarkPng from "../../asset/brand/opencode-wordmark-dark.png"
|
||||
import wordmarkSimpleLightPng from "../../asset/brand/opencode-wordmark-simple-light.png"
|
||||
import wordmarkSimpleDarkPng from "../../asset/brand/opencode-wordmark-simple-dark.png"
|
||||
import logoLightSvg from "../../asset/brand/opencode-logo-light.svg"
|
||||
import logoDarkSvg from "../../asset/brand/opencode-logo-dark.svg"
|
||||
import logoLightSquareSvg from "../../asset/brand/opencode-logo-light-square.svg"
|
||||
import logoDarkSquareSvg from "../../asset/brand/opencode-logo-dark-square.svg"
|
||||
import wordmarkLightSvg from "../../asset/brand/opencode-wordmark-light.svg"
|
||||
import wordmarkDarkSvg from "../../asset/brand/opencode-wordmark-dark.svg"
|
||||
import wordmarkSimpleLightSvg from "../../asset/brand/opencode-wordmark-simple-light.svg"
|
||||
@@ -135,6 +141,60 @@ export default function Brand() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src={previewLogoLightSquare} alt="OpenCode brand guidelines" />
|
||||
<div data-component="actions">
|
||||
<button onClick={() => downloadFile(logoLightSquarePng, "opencode-logo-light-square.png")}>
|
||||
PNG
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button onClick={() => downloadFile(logoLightSquareSvg, "opencode-logo-light-square.svg")}>
|
||||
SVG
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src={previewLogoDarkSquare} alt="OpenCode brand guidelines" />
|
||||
<div data-component="actions">
|
||||
<button onClick={() => downloadFile(logoDarkSquarePng, "opencode-logo-dark-square.png")}>
|
||||
PNG
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button onClick={() => downloadFile(logoDarkSquareSvg, "opencode-logo-dark-square.svg")}>
|
||||
SVG
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
|
||||
<div data-component="actions">
|
||||
|
||||
@@ -38,6 +38,7 @@ type RetryOptions = {
|
||||
excludeProviders: string[]
|
||||
retryCount: number
|
||||
}
|
||||
type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "balance"
|
||||
|
||||
export async function handler(
|
||||
input: APIEvent,
|
||||
@@ -51,6 +52,7 @@ export async function handler(
|
||||
type AuthInfo = Awaited<ReturnType<typeof authenticate>>
|
||||
type ModelInfo = Awaited<ReturnType<typeof validateModel>>
|
||||
type ProviderInfo = Awaited<ReturnType<typeof selectProvider>>
|
||||
type CostInfo = ReturnType<typeof calculateCost>
|
||||
|
||||
const MAX_FAILOVER_RETRIES = 3
|
||||
const MAX_429_RETRIES = 3
|
||||
@@ -139,21 +141,22 @@ export async function handler(
|
||||
"llm.error.code": res.status,
|
||||
"llm.error.message": res.statusText,
|
||||
})
|
||||
}
|
||||
|
||||
// Try another provider => stop retrying if using fallback provider
|
||||
if (
|
||||
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
|
||||
res.status !== 404 &&
|
||||
// ie. cannot change codex model providers mid-session
|
||||
modelInfo.stickyProvider !== "strict" &&
|
||||
modelInfo.fallbackProvider &&
|
||||
providerInfo.id !== modelInfo.fallbackProvider
|
||||
) {
|
||||
return retriableRequest({
|
||||
excludeProviders: [...retry.excludeProviders, providerInfo.id],
|
||||
retryCount: retry.retryCount + 1,
|
||||
})
|
||||
}
|
||||
// Try another provider => stop retrying if using fallback provider
|
||||
if (
|
||||
res.status !== 200 &&
|
||||
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
|
||||
res.status !== 404 &&
|
||||
// ie. cannot change codex model providers mid-session
|
||||
modelInfo.stickyProvider !== "strict" &&
|
||||
modelInfo.fallbackProvider &&
|
||||
providerInfo.id !== modelInfo.fallbackProvider
|
||||
) {
|
||||
return retriableRequest({
|
||||
excludeProviders: [...retry.excludeProviders, providerInfo.id],
|
||||
retryCount: retry.retryCount + 1,
|
||||
})
|
||||
}
|
||||
|
||||
return { providerInfo, reqBody, res, startTimestamp }
|
||||
@@ -183,18 +186,25 @@ export async function handler(
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!isStream) {
|
||||
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
||||
const json = await res.json()
|
||||
const body = JSON.stringify(responseConverter(json))
|
||||
const usageInfo = providerInfo.normalizeUsage(json.usage)
|
||||
const costInfo = calculateCost(modelInfo, usageInfo)
|
||||
await trialLimiter?.track(usageInfo)
|
||||
await rateLimiter?.track()
|
||||
await trackUsage(billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
|
||||
await reload(billingSource, authInfo, costInfo)
|
||||
|
||||
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
||||
const body = JSON.stringify(
|
||||
responseConverter({
|
||||
...json,
|
||||
cost: calculateOccuredCost(billingSource, costInfo),
|
||||
}),
|
||||
)
|
||||
logger.metric({ response_length: body.length })
|
||||
logger.debug("RESPONSE: " + body)
|
||||
dataDumper?.provideResponse(body)
|
||||
dataDumper?.flush()
|
||||
const tokensInfo = providerInfo.normalizeUsage(json.usage)
|
||||
await trialLimiter?.track(tokensInfo)
|
||||
await rateLimiter?.track()
|
||||
const costInfo = await trackUsage(authInfo, modelInfo, providerInfo, billingSource, tokensInfo)
|
||||
await reload(authInfo, costInfo)
|
||||
return new Response(body, {
|
||||
status: resStatus,
|
||||
statusText: res.statusText,
|
||||
@@ -226,12 +236,16 @@ export async function handler(
|
||||
dataDumper?.flush()
|
||||
await rateLimiter?.track()
|
||||
const usage = usageParser.retrieve()
|
||||
let cost = "0"
|
||||
if (usage) {
|
||||
const tokensInfo = providerInfo.normalizeUsage(usage)
|
||||
await trialLimiter?.track(tokensInfo)
|
||||
const costInfo = await trackUsage(authInfo, modelInfo, providerInfo, billingSource, tokensInfo)
|
||||
await reload(authInfo, costInfo)
|
||||
const usageInfo = providerInfo.normalizeUsage(usage)
|
||||
const costInfo = calculateCost(modelInfo, usageInfo)
|
||||
await trialLimiter?.track(usageInfo)
|
||||
await trackUsage(billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
|
||||
await reload(billingSource, authInfo, costInfo)
|
||||
cost = calculateOccuredCost(billingSource, costInfo)
|
||||
}
|
||||
c.enqueue(encoder.encode(usageParser.buidlCostChunk(cost)))
|
||||
c.close()
|
||||
return
|
||||
}
|
||||
@@ -283,7 +297,6 @@ export async function handler(
|
||||
return pump()
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
status: resStatus,
|
||||
statusText: res.statusText,
|
||||
@@ -498,9 +511,9 @@ export async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo) {
|
||||
function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo): BillingSource {
|
||||
if (!authInfo) return "anonymous"
|
||||
if (authInfo.provider?.credentials) return "free"
|
||||
if (authInfo.provider?.credentials) return "byok"
|
||||
if (authInfo.isFree) return "free"
|
||||
if (modelInfo.allowAnonymous) return "free"
|
||||
|
||||
@@ -613,13 +626,7 @@ export async function handler(
|
||||
return res
|
||||
}
|
||||
|
||||
async function trackUsage(
|
||||
authInfo: AuthInfo,
|
||||
modelInfo: ModelInfo,
|
||||
providerInfo: ProviderInfo,
|
||||
billingSource: ReturnType<typeof validateBilling>,
|
||||
usageInfo: UsageInfo,
|
||||
) {
|
||||
function calculateCost(modelInfo: ModelInfo, usageInfo: UsageInfo) {
|
||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||
usageInfo
|
||||
|
||||
@@ -657,6 +664,33 @@ export async function handler(
|
||||
(cacheReadCost ?? 0) +
|
||||
(cacheWrite5mCost ?? 0) +
|
||||
(cacheWrite1hCost ?? 0)
|
||||
return {
|
||||
totalCostInCent,
|
||||
inputCost,
|
||||
outputCost,
|
||||
reasoningCost,
|
||||
cacheReadCost,
|
||||
cacheWrite5mCost,
|
||||
cacheWrite1hCost,
|
||||
}
|
||||
}
|
||||
|
||||
function calculateOccuredCost(billingSource: BillingSource, costInfo: CostInfo) {
|
||||
return billingSource === "balance" ? (costInfo.totalCostInCent / 100).toFixed(8) : "0"
|
||||
}
|
||||
|
||||
async function trackUsage(
|
||||
billingSource: BillingSource,
|
||||
authInfo: AuthInfo,
|
||||
modelInfo: ModelInfo,
|
||||
providerInfo: ProviderInfo,
|
||||
usageInfo: UsageInfo,
|
||||
costInfo: CostInfo,
|
||||
) {
|
||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
||||
usageInfo
|
||||
const { totalCostInCent, inputCost, outputCost, reasoningCost, cacheReadCost, cacheWrite5mCost, cacheWrite1hCost } =
|
||||
costInfo
|
||||
|
||||
logger.metric({
|
||||
"tokens.input": inputTokens,
|
||||
@@ -677,7 +711,7 @@ export async function handler(
|
||||
if (billingSource === "anonymous") return
|
||||
authInfo = authInfo!
|
||||
|
||||
const cost = authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent)
|
||||
const cost = centsToMicroCents(totalCostInCent)
|
||||
await Database.use((db) =>
|
||||
Promise.all([
|
||||
db.insert(UsageTable).values({
|
||||
@@ -772,16 +806,12 @@ export async function handler(
|
||||
return { costInMicroCents: cost }
|
||||
}
|
||||
|
||||
async function reload(authInfo: AuthInfo, costInfo: Awaited<ReturnType<typeof trackUsage>>) {
|
||||
if (!authInfo) return
|
||||
if (authInfo.isFree) return
|
||||
if (authInfo.provider?.credentials) return
|
||||
if (authInfo.subscription) return
|
||||
|
||||
if (!costInfo) return
|
||||
async function reload(billingSource: BillingSource, authInfo: AuthInfo, costInfo: CostInfo) {
|
||||
if (billingSource !== "balance") return
|
||||
authInfo = authInfo!
|
||||
|
||||
const reloadTrigger = centsToMicroCents((authInfo.billing.reloadTrigger ?? Billing.RELOAD_TRIGGER) * 100)
|
||||
if (authInfo.billing.balance - costInfo.costInMicroCents >= reloadTrigger) return
|
||||
if (authInfo.billing.balance - costInfo.totalCostInCent >= reloadTrigger) return
|
||||
if (authInfo.billing.timeReloadLockedTill && authInfo.billing.timeReloadLockedTill > new Date()) return
|
||||
|
||||
const lock = await Database.use((tx) =>
|
||||
|
||||
@@ -167,6 +167,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) =>
|
||||
}
|
||||
},
|
||||
retrieve: () => usage,
|
||||
buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => ({
|
||||
|
||||
@@ -56,6 +56,7 @@ export const googleHelper: ProviderHelper = ({ providerModel }) => ({
|
||||
usage = json.usageMetadata
|
||||
},
|
||||
retrieve: () => usage,
|
||||
buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ type: "ping", cost })}\n\n`,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
|
||||
@@ -54,6 +54,7 @@ export const oaCompatHelper: ProviderHelper = () => ({
|
||||
usage = json.usage
|
||||
},
|
||||
retrieve: () => usage,
|
||||
buidlCostChunk: (cost: string) => `data: ${JSON.stringify({ choices: [], cost })}\n\n`,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
|
||||
@@ -43,6 +43,7 @@ export const openaiHelper: ProviderHelper = () => ({
|
||||
usage = json.response.usage
|
||||
},
|
||||
retrieve: () => usage,
|
||||
buidlCostChunk: (cost: string) => `event: ping\ndata: ${JSON.stringify({ type: "ping", cost })}\n\n`,
|
||||
}
|
||||
},
|
||||
normalizeUsage: (usage: Usage) => {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type ProviderHelper = (input: { reqModel: string; providerModel: string }
|
||||
createUsageParser: () => {
|
||||
parse: (chunk: string) => void
|
||||
retrieve: () => any
|
||||
buidlCostChunk: (cost: string) => string
|
||||
}
|
||||
normalizeUsage: (usage: any) => UsageInfo
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
4
packages/desktop/AGENTS.md
Normal file
4
packages/desktop/AGENTS.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Desktop package notes
|
||||
|
||||
- Never call `invoke` manually in this package.
|
||||
- Use the generated bindings in `packages/desktop/src/bindings.ts` for core commands/events.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
13
packages/desktop/src-tauri/Cargo.lock
generated
13
packages/desktop/src-tauri/Cargo.lock
generated
@@ -3117,6 +3117,7 @@ dependencies = [
|
||||
"tauri-plugin-window-state",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
@@ -5631,6 +5632,18 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.17"
|
||||
|
||||
@@ -51,6 +51,7 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tracing-appender = "0.2"
|
||||
chrono = "0.4"
|
||||
tokio-stream = { version = "0.1.18", features = ["sync"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gtk = "0.18.2"
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
use futures::{FutureExt, Stream, StreamExt, future};
|
||||
use tauri::{AppHandle, Manager, path::BaseDirectory};
|
||||
use tauri_plugin_shell::{
|
||||
ShellExt,
|
||||
process::{Command, CommandChild, CommandEvent, TerminatedPayload},
|
||||
process::{CommandChild, CommandEvent, TerminatedPayload},
|
||||
};
|
||||
use tauri_plugin_store::StoreExt;
|
||||
use tauri_specta::Event;
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::constants::{SETTINGS_STORE, WSL_ENABLED_KEY};
|
||||
|
||||
const CLI_INSTALL_DIR: &str = ".opencode/bin";
|
||||
const CLI_BINARY_NAME: &str = "opencode";
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct ServerConfig {
|
||||
pub hostname: Option<String>,
|
||||
pub port: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub server: Option<ServerConfig>,
|
||||
}
|
||||
|
||||
pub async fn get_config(app: &AppHandle) -> Option<Config> {
|
||||
create_command(app, "debug config")
|
||||
.output()
|
||||
let (events, _) = spawn_command(app, "debug config", &[]).ok()?;
|
||||
|
||||
events
|
||||
.fold(String::new(), async |mut config_str, event| {
|
||||
if let CommandEvent::Stdout(stdout) = event
|
||||
&& let Ok(s) = str::from_utf8(&stdout)
|
||||
{
|
||||
config_str += s
|
||||
}
|
||||
|
||||
config_str
|
||||
})
|
||||
.map(|v| serde_json::from_str::<Config>(&v))
|
||||
.await
|
||||
.inspect_err(|e| tracing::warn!("Failed to read OC config: {e}"))
|
||||
.ok()
|
||||
.and_then(|out| String::from_utf8(out.stdout.to_vec()).ok())
|
||||
.and_then(|s| serde_json::from_str::<Config>(&s).ok())
|
||||
}
|
||||
|
||||
fn get_cli_install_path() -> Option<std::path::PathBuf> {
|
||||
@@ -149,25 +163,109 @@ fn get_user_shell() -> String {
|
||||
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
|
||||
}
|
||||
|
||||
pub fn create_command(app: &tauri::AppHandle, args: &str) -> Command {
|
||||
fn is_wsl_enabled(app: &tauri::AppHandle) -> bool {
|
||||
let Ok(store) = app.store(SETTINGS_STORE) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
store
|
||||
.get(WSL_ENABLED_KEY)
|
||||
.as_ref()
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn shell_escape(input: &str) -> String {
|
||||
if input.is_empty() {
|
||||
return "''".to_string();
|
||||
}
|
||||
|
||||
let mut escaped = String::from("'");
|
||||
escaped.push_str(&input.replace("'", "'\"'\"'"));
|
||||
escaped.push('\'');
|
||||
escaped
|
||||
}
|
||||
|
||||
pub fn spawn_command(
|
||||
app: &tauri::AppHandle,
|
||||
args: &str,
|
||||
extra_env: &[(&str, String)],
|
||||
) -> Result<(impl Stream<Item = CommandEvent> + 'static, CommandChild), tauri_plugin_shell::Error> {
|
||||
let state_dir = app
|
||||
.path()
|
||||
.resolve("", BaseDirectory::AppLocalData)
|
||||
.expect("Failed to resolve app local data dir");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return app
|
||||
.shell()
|
||||
.sidecar("opencode-cli")
|
||||
.unwrap()
|
||||
.args(args.split_whitespace())
|
||||
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
||||
.env("OPENCODE_EXPERIMENTAL_FILEWATCHER", "true")
|
||||
.env("OPENCODE_CLIENT", "desktop")
|
||||
.env("XDG_STATE_HOME", &state_dir);
|
||||
let mut envs = vec![
|
||||
(
|
||||
"OPENCODE_EXPERIMENTAL_ICON_DISCOVERY".to_string(),
|
||||
"true".to_string(),
|
||||
),
|
||||
(
|
||||
"OPENCODE_EXPERIMENTAL_FILEWATCHER".to_string(),
|
||||
"true".to_string(),
|
||||
),
|
||||
("OPENCODE_CLIENT".to_string(), "desktop".to_string()),
|
||||
(
|
||||
"XDG_STATE_HOME".to_string(),
|
||||
state_dir.to_string_lossy().to_string(),
|
||||
),
|
||||
];
|
||||
envs.extend(
|
||||
extra_env
|
||||
.iter()
|
||||
.map(|(key, value)| (key.to_string(), value.clone())),
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
return {
|
||||
let cmd = if cfg!(windows) {
|
||||
if is_wsl_enabled(app) {
|
||||
tracing::info!("WSL is enabled, spawning CLI server in WSL");
|
||||
let version = app.package_info().version.to_string();
|
||||
let mut script = vec![
|
||||
"set -e".to_string(),
|
||||
"BIN=\"$HOME/.opencode/bin/opencode\"".to_string(),
|
||||
"if [ ! -x \"$BIN\" ]; then".to_string(),
|
||||
format!(
|
||||
" curl -fsSL https://opencode.ai/install | bash -s -- --version {} --no-modify-path",
|
||||
shell_escape(&version)
|
||||
),
|
||||
"fi".to_string(),
|
||||
];
|
||||
|
||||
let mut env_prefix = vec![
|
||||
"OPENCODE_EXPERIMENTAL_ICON_DISCOVERY=true".to_string(),
|
||||
"OPENCODE_EXPERIMENTAL_FILEWATCHER=true".to_string(),
|
||||
"OPENCODE_CLIENT=desktop".to_string(),
|
||||
"XDG_STATE_HOME=\"$HOME/.local/state\"".to_string(),
|
||||
];
|
||||
env_prefix.extend(
|
||||
envs.iter()
|
||||
.filter(|(key, _)| key != "OPENCODE_EXPERIMENTAL_ICON_DISCOVERY")
|
||||
.filter(|(key, _)| key != "OPENCODE_EXPERIMENTAL_FILEWATCHER")
|
||||
.filter(|(key, _)| key != "OPENCODE_CLIENT")
|
||||
.filter(|(key, _)| key != "XDG_STATE_HOME")
|
||||
.map(|(key, value)| format!("{}={}", key, shell_escape(value))),
|
||||
);
|
||||
|
||||
script.push(format!("{} exec \"$BIN\" {}", env_prefix.join(" "), args));
|
||||
|
||||
app.shell()
|
||||
.command("wsl")
|
||||
.args(["-e", "bash", "-lc", &script.join("\n")])
|
||||
} else {
|
||||
let mut cmd = app
|
||||
.shell()
|
||||
.sidecar("opencode-cli")
|
||||
.unwrap()
|
||||
.args(args.split_whitespace());
|
||||
|
||||
for (key, value) in envs {
|
||||
cmd = cmd.env(key, value);
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
} else {
|
||||
let sidecar = get_sidecar_path(app);
|
||||
let shell = get_user_shell();
|
||||
|
||||
@@ -177,14 +275,20 @@ pub fn create_command(app: &tauri::AppHandle, args: &str) -> Command {
|
||||
format!("\"{}\" {}", sidecar.display(), args)
|
||||
};
|
||||
|
||||
app.shell()
|
||||
.command(&shell)
|
||||
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
||||
.env("OPENCODE_EXPERIMENTAL_FILEWATCHER", "true")
|
||||
.env("OPENCODE_CLIENT", "desktop")
|
||||
.env("XDG_STATE_HOME", &state_dir)
|
||||
.args(["-il", "-c", &cmd])
|
||||
let mut cmd = app.shell().command(&shell).args(["-il", "-c", &cmd]);
|
||||
|
||||
for (key, value) in envs {
|
||||
cmd = cmd.env(key, value);
|
||||
}
|
||||
|
||||
cmd
|
||||
};
|
||||
|
||||
let (rx, child) = cmd.spawn()?;
|
||||
let event_stream = tokio_stream::wrappers::ReceiverStream::new(rx);
|
||||
let event_stream = sqlite_migration::logs_middleware(app.clone(), event_stream);
|
||||
|
||||
Ok((event_stream, child))
|
||||
}
|
||||
|
||||
pub fn serve(
|
||||
@@ -197,46 +301,101 @@ pub fn serve(
|
||||
|
||||
tracing::info!(port, "Spawning sidecar");
|
||||
|
||||
let (mut rx, child) = create_command(
|
||||
let envs = [
|
||||
("OPENCODE_SERVER_USERNAME", "opencode".to_string()),
|
||||
("OPENCODE_SERVER_PASSWORD", password.to_string()),
|
||||
];
|
||||
|
||||
let (events, child) = spawn_command(
|
||||
app,
|
||||
format!("--print-logs --log-level WARN serve --hostname {hostname} --port {port}").as_str(),
|
||||
&envs,
|
||||
)
|
||||
.env("OPENCODE_SERVER_USERNAME", "opencode")
|
||||
.env("OPENCODE_SERVER_PASSWORD", password)
|
||||
.spawn()
|
||||
.expect("Failed to spawn opencode");
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut exit_tx = Some(exit_tx);
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
tracing::info!(target: "sidecar", "{line}");
|
||||
}
|
||||
CommandEvent::Stderr(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
tracing::info!(target: "sidecar", "{line}");
|
||||
}
|
||||
CommandEvent::Error(err) => {
|
||||
tracing::error!(target: "sidecar", "{err}");
|
||||
}
|
||||
CommandEvent::Terminated(payload) => {
|
||||
tracing::info!(
|
||||
target: "sidecar",
|
||||
code = ?payload.code,
|
||||
signal = ?payload.signal,
|
||||
"Sidecar terminated"
|
||||
);
|
||||
|
||||
if let Some(tx) = exit_tx.take() {
|
||||
let _ = tx.send(payload);
|
||||
let mut exit_tx = Some(exit_tx);
|
||||
tokio::spawn(
|
||||
events
|
||||
.for_each(move |event| {
|
||||
match event {
|
||||
CommandEvent::Stdout(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
tracing::info!("{line}");
|
||||
}
|
||||
CommandEvent::Stderr(line_bytes) => {
|
||||
let line = String::from_utf8_lossy(&line_bytes);
|
||||
tracing::info!("{line}");
|
||||
}
|
||||
CommandEvent::Error(err) => {
|
||||
tracing::error!("{err}");
|
||||
}
|
||||
CommandEvent::Terminated(payload) => {
|
||||
tracing::info!(
|
||||
code = ?payload.code,
|
||||
signal = ?payload.signal,
|
||||
"Sidecar terminated"
|
||||
);
|
||||
|
||||
if let Some(tx) = exit_tx.take() {
|
||||
let _ = tx.send(payload);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
future::ready(())
|
||||
})
|
||||
.instrument(tracing::info_span!("sidecar")),
|
||||
);
|
||||
|
||||
(child, exit_rx)
|
||||
}
|
||||
|
||||
pub mod sqlite_migration {
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
tauri_specta::Event, serde::Serialize, serde::Deserialize, Clone, Copy, Debug, specta::Type,
|
||||
)]
|
||||
#[serde(tag = "type", content = "value")]
|
||||
pub enum SqliteMigrationProgress {
|
||||
InProgress(u8),
|
||||
Done,
|
||||
}
|
||||
|
||||
pub(super) fn logs_middleware(
|
||||
app: AppHandle,
|
||||
stream: impl Stream<Item = CommandEvent>,
|
||||
) -> impl Stream<Item = CommandEvent> {
|
||||
let app = app.clone();
|
||||
let mut done = false;
|
||||
|
||||
stream.filter_map(move |event| {
|
||||
if done {
|
||||
return future::ready(Some(event));
|
||||
}
|
||||
|
||||
future::ready(match &event {
|
||||
CommandEvent::Stdout(stdout) => {
|
||||
let Ok(s) = str::from_utf8(stdout) else {
|
||||
return future::ready(None);
|
||||
};
|
||||
|
||||
if let Some(s) = s.strip_prefix("sqlite-migration:").map(|s| s.trim()) {
|
||||
if let Ok(progress) = s.parse::<u8>() {
|
||||
let _ = SqliteMigrationProgress::InProgress(progress).emit(&app);
|
||||
} else if s == "done" {
|
||||
done = true;
|
||||
let _ = SqliteMigrationProgress::Done.emit(&app);
|
||||
}
|
||||
|
||||
None
|
||||
} else {
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
_ => Some(event),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use tauri_plugin_window_state::StateFlags;
|
||||
|
||||
pub const SETTINGS_STORE: &str = "opencode.settings.dat";
|
||||
pub const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
|
||||
pub const WSL_ENABLED_KEY: &str = "wslEnabled";
|
||||
pub const UPDATER_ENABLED: bool = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
||||
|
||||
pub fn window_state_flags() -> StateFlags {
|
||||
|
||||
@@ -24,16 +24,17 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::{AppHandle, Manager, RunEvent, State, ipc::Channel};
|
||||
use tauri::{AppHandle, Listener, Manager, RunEvent, State, ipc::Channel};
|
||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_shell::process::CommandChild;
|
||||
use tauri_specta::Event;
|
||||
use tokio::{
|
||||
sync::{oneshot, watch},
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
|
||||
use crate::cli::sync_cli;
|
||||
use crate::cli::{sqlite_migration::SqliteMigrationProgress, sync_cli};
|
||||
use crate::constants::*;
|
||||
use crate::server::get_saved_server_url;
|
||||
use crate::windows::{LoadingWindow, MainWindow};
|
||||
@@ -52,6 +53,13 @@ enum InitStep {
|
||||
Done,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, specta::Type)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum WslPathMode {
|
||||
Windows,
|
||||
Linux,
|
||||
}
|
||||
|
||||
struct InitState {
|
||||
current: watch::Receiver<InitStep>,
|
||||
}
|
||||
@@ -115,8 +123,8 @@ async fn await_initialization(
|
||||
let mut rx = init_state.current.clone();
|
||||
|
||||
let events = async {
|
||||
let e = (*rx.borrow()).clone();
|
||||
let _ = events.send(e).unwrap();
|
||||
let e = *rx.borrow();
|
||||
let _ = events.send(e);
|
||||
|
||||
while rx.changed().await.is_ok() {
|
||||
let step = *rx.borrow_and_update();
|
||||
@@ -155,211 +163,28 @@ fn check_app_exists(app_name: &str) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn check_windows_app(app_name: &str) -> bool {
|
||||
resolve_windows_app_path(app_name).is_some()
|
||||
fn check_windows_app(_app_name: &str) -> bool {
|
||||
// Check if command exists in PATH, including .exe
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn resolve_windows_app_path(app_name: &str) -> Option<String> {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn expand_env(value: &str) -> String {
|
||||
let mut out = String::with_capacity(value.len());
|
||||
let mut index = 0;
|
||||
// Try to find the command using 'where'
|
||||
let output = Command::new("where").arg(app_name).output().ok()?;
|
||||
|
||||
while let Some(start) = value[index..].find('%') {
|
||||
let start = index + start;
|
||||
out.push_str(&value[index..start]);
|
||||
|
||||
let Some(end_rel) = value[start + 1..].find('%') else {
|
||||
out.push_str(&value[start..]);
|
||||
return out;
|
||||
};
|
||||
|
||||
let end = start + 1 + end_rel;
|
||||
let key = &value[start + 1..end];
|
||||
if key.is_empty() {
|
||||
out.push('%');
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(v) = std::env::var(key) {
|
||||
out.push_str(&v);
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push_str(&value[start..=end]);
|
||||
index = end + 1;
|
||||
}
|
||||
|
||||
out.push_str(&value[index..]);
|
||||
out
|
||||
}
|
||||
|
||||
fn extract_exe(value: &str) -> Option<String> {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(rest) = value.strip_prefix('"') {
|
||||
if let Some(end) = rest.find('"') {
|
||||
let inner = rest[..end].trim();
|
||||
if inner.to_ascii_lowercase().contains(".exe") {
|
||||
return Some(inner.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lower = value.to_ascii_lowercase();
|
||||
let end = lower.find(".exe")?;
|
||||
Some(value[..end + 4].trim().trim_matches('"').to_string())
|
||||
}
|
||||
|
||||
fn candidates(app_name: &str) -> Vec<String> {
|
||||
let app_name = app_name.trim().trim_matches('"');
|
||||
if app_name.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut out = Vec::<String>::new();
|
||||
let mut push = |value: String| {
|
||||
let value = value.trim().trim_matches('"').to_string();
|
||||
if value.is_empty() {
|
||||
return;
|
||||
}
|
||||
if out.iter().any(|v| v.eq_ignore_ascii_case(&value)) {
|
||||
return;
|
||||
}
|
||||
out.push(value);
|
||||
};
|
||||
|
||||
push(app_name.to_string());
|
||||
|
||||
let lower = app_name.to_ascii_lowercase();
|
||||
if !lower.ends_with(".exe") {
|
||||
push(format!("{app_name}.exe"));
|
||||
}
|
||||
|
||||
let snake = {
|
||||
let mut s = String::new();
|
||||
let mut underscore = false;
|
||||
for c in lower.chars() {
|
||||
if c.is_ascii_alphanumeric() {
|
||||
s.push(c);
|
||||
underscore = false;
|
||||
continue;
|
||||
}
|
||||
if underscore {
|
||||
continue;
|
||||
}
|
||||
s.push('_');
|
||||
underscore = true;
|
||||
}
|
||||
s.trim_matches('_').to_string()
|
||||
};
|
||||
|
||||
if !snake.is_empty() {
|
||||
push(snake.clone());
|
||||
if !snake.ends_with(".exe") {
|
||||
push(format!("{snake}.exe"));
|
||||
}
|
||||
}
|
||||
|
||||
let alnum = lower
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.collect::<String>();
|
||||
|
||||
if !alnum.is_empty() {
|
||||
push(alnum.clone());
|
||||
push(format!("{alnum}.exe"));
|
||||
}
|
||||
|
||||
match lower.as_str() {
|
||||
"sublime text" | "sublime-text" | "sublime_text" | "sublime text.exe" => {
|
||||
push("subl".to_string());
|
||||
push("subl.exe".to_string());
|
||||
push("sublime_text".to_string());
|
||||
push("sublime_text.exe".to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn reg_app_path(exe: &str) -> Option<String> {
|
||||
let exe = exe.trim().trim_matches('"');
|
||||
if exe.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let keys = [
|
||||
format!(
|
||||
r"HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\{exe}"
|
||||
),
|
||||
format!(
|
||||
r"HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\{exe}"
|
||||
),
|
||||
format!(
|
||||
r"HKLM\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\{exe}"
|
||||
),
|
||||
];
|
||||
|
||||
for key in keys {
|
||||
let Some(output) = Command::new("reg")
|
||||
.args(["query", &key, "/ve"])
|
||||
.output()
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
let tokens = line.split_whitespace().collect::<Vec<_>>();
|
||||
let Some(index) = tokens.iter().position(|v| v.starts_with("REG_")) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let value = tokens[index + 1..].join(" ");
|
||||
let Some(exe) = extract_exe(&value) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let exe = expand_env(&exe);
|
||||
let path = Path::new(exe.trim().trim_matches('"'));
|
||||
if path.exists() {
|
||||
return Some(path.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
let app_name = app_name.trim().trim_matches('"');
|
||||
if app_name.is_empty() {
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let direct = Path::new(app_name);
|
||||
if direct.is_absolute() && direct.exists() {
|
||||
return Some(direct.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
let key = app_name
|
||||
.chars()
|
||||
.filter(|v| v.is_ascii_alphanumeric())
|
||||
.flat_map(|v| v.to_lowercase())
|
||||
.collect::<String>();
|
||||
let paths = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let has_ext = |path: &Path, ext: &str| {
|
||||
path.extension()
|
||||
@@ -368,19 +193,22 @@ fn resolve_windows_app_path(app_name: &str) -> Option<String> {
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
if let Some(path) = paths.iter().find(|path| has_ext(path, "exe")) {
|
||||
return Some(path.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
let resolve_cmd = |path: &Path| -> Option<String> {
|
||||
let bytes = std::fs::read(path).ok()?;
|
||||
let content = String::from_utf8_lossy(&bytes);
|
||||
let content = std::fs::read_to_string(path).ok()?;
|
||||
|
||||
for token in content.split('"') {
|
||||
let Some(exe) = extract_exe(token) else {
|
||||
let lower = token.to_ascii_lowercase();
|
||||
if !lower.contains(".exe") {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
let lower = exe.to_ascii_lowercase();
|
||||
if let Some(index) = lower.find("%~dp0") {
|
||||
let base = path.parent()?;
|
||||
let suffix = &exe[index + 5..];
|
||||
let suffix = &token[index + 5..];
|
||||
let mut resolved = PathBuf::from(base);
|
||||
|
||||
for part in suffix.replace('/', "\\").split('\\') {
|
||||
@@ -397,11 +225,9 @@ fn resolve_windows_app_path(app_name: &str) -> Option<String> {
|
||||
if resolved.exists() {
|
||||
return Some(resolved.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let resolved = PathBuf::from(expand_env(&exe));
|
||||
let resolved = PathBuf::from(token);
|
||||
if resolved.exists() {
|
||||
return Some(resolved.to_string_lossy().to_string());
|
||||
}
|
||||
@@ -410,130 +236,74 @@ fn resolve_windows_app_path(app_name: &str) -> Option<String> {
|
||||
None
|
||||
};
|
||||
|
||||
let resolve_where = |query: &str| -> Option<String> {
|
||||
let output = Command::new("where").arg(query).output().ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
for path in &paths {
|
||||
if has_ext(path, "cmd") || has_ext(path, "bat") {
|
||||
if let Some(resolved) = resolve_cmd(path) {
|
||||
return Some(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
let paths = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if paths.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(path) = paths.iter().find(|path| has_ext(path, "exe")) {
|
||||
return Some(path.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
for path in &paths {
|
||||
if has_ext(path, "cmd") || has_ext(path, "bat") {
|
||||
if let Some(resolved) = resolve_cmd(path) {
|
||||
if path.extension().is_none() {
|
||||
let cmd = path.with_extension("cmd");
|
||||
if cmd.exists() {
|
||||
if let Some(resolved) = resolve_cmd(&cmd) {
|
||||
return Some(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
if path.extension().is_none() {
|
||||
let cmd = path.with_extension("cmd");
|
||||
if cmd.exists() {
|
||||
if let Some(resolved) = resolve_cmd(&cmd) {
|
||||
return Some(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
let bat = path.with_extension("bat");
|
||||
if bat.exists() {
|
||||
if let Some(resolved) = resolve_cmd(&bat) {
|
||||
return Some(resolved);
|
||||
}
|
||||
let bat = path.with_extension("bat");
|
||||
if bat.exists() {
|
||||
if let Some(resolved) = resolve_cmd(&bat) {
|
||||
return Some(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !key.is_empty() {
|
||||
for path in &paths {
|
||||
let dirs = [
|
||||
path.parent(),
|
||||
path.parent().and_then(|dir| dir.parent()),
|
||||
path.parent()
|
||||
.and_then(|dir| dir.parent())
|
||||
.and_then(|dir| dir.parent()),
|
||||
];
|
||||
let key = app_name
|
||||
.chars()
|
||||
.filter(|v| v.is_ascii_alphanumeric())
|
||||
.flat_map(|v| v.to_lowercase())
|
||||
.collect::<String>();
|
||||
|
||||
for dir in dirs.into_iter().flatten() {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
let candidate = entry.path();
|
||||
if !has_ext(&candidate, "exe") {
|
||||
continue;
|
||||
}
|
||||
if !key.is_empty() {
|
||||
for path in &paths {
|
||||
let dirs = [
|
||||
path.parent(),
|
||||
path.parent().and_then(|dir| dir.parent()),
|
||||
path.parent()
|
||||
.and_then(|dir| dir.parent())
|
||||
.and_then(|dir| dir.parent()),
|
||||
];
|
||||
|
||||
let Some(stem) = candidate.file_stem().and_then(|v| v.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
for dir in dirs.into_iter().flatten() {
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
let candidate = entry.path();
|
||||
if !has_ext(&candidate, "exe") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = stem
|
||||
.chars()
|
||||
.filter(|v| v.is_ascii_alphanumeric())
|
||||
.flat_map(|v| v.to_lowercase())
|
||||
.collect::<String>();
|
||||
let Some(stem) = candidate.file_stem().and_then(|v| v.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if name.contains(&key) || key.contains(&name) {
|
||||
return Some(candidate.to_string_lossy().to_string());
|
||||
}
|
||||
let name = stem
|
||||
.chars()
|
||||
.filter(|v| v.is_ascii_alphanumeric())
|
||||
.flat_map(|v| v.to_lowercase())
|
||||
.collect::<String>();
|
||||
|
||||
if name.contains(&key) || key.contains(&name) {
|
||||
return Some(candidate.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths.first().map(|path| path.to_string_lossy().to_string())
|
||||
};
|
||||
|
||||
let list = candidates(app_name);
|
||||
for query in &list {
|
||||
if let Some(path) = resolve_where(query) {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
let mut exes = Vec::<String>::new();
|
||||
for query in &list {
|
||||
let query = query.trim().trim_matches('"');
|
||||
if query.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = Path::new(query)
|
||||
.file_name()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or(query);
|
||||
|
||||
let exe = if name.to_ascii_lowercase().ends_with(".exe") {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{name}.exe")
|
||||
};
|
||||
|
||||
if exes.iter().any(|v| v.eq_ignore_ascii_case(&exe)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
exes.push(exe);
|
||||
}
|
||||
|
||||
for exe in exes {
|
||||
if let Some(path) = reg_app_path(&exe) {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
paths.first().map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -620,32 +390,50 @@ fn check_linux_app(app_name: &str) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
fn wsl_path(path: String, mode: Option<WslPathMode>) -> Result<String, String> {
|
||||
if !cfg!(windows) {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
let flag = match mode.unwrap_or(WslPathMode::Linux) {
|
||||
WslPathMode::Windows => "-w",
|
||||
WslPathMode::Linux => "-u",
|
||||
};
|
||||
|
||||
let output = if path.starts_with('~') {
|
||||
let suffix = path.strip_prefix('~').unwrap_or("");
|
||||
let escaped = suffix.replace('"', "\\\"");
|
||||
let cmd = format!("wslpath {flag} \"$HOME{escaped}\"");
|
||||
Command::new("wsl")
|
||||
.args(["-e", "sh", "-lc", &cmd])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run wslpath: {e}"))?
|
||||
} else {
|
||||
Command::new("wsl")
|
||||
.args(["-e", "wslpath", flag, &path])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to run wslpath: {e}"))?
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
if stderr.is_empty() {
|
||||
return Err("wslpath failed".to_string());
|
||||
}
|
||||
return Err(stderr);
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let builder = tauri_specta::Builder::<tauri::Wry>::new()
|
||||
// Then register them (separated by a comma)
|
||||
.commands(tauri_specta::collect_commands![
|
||||
kill_sidecar,
|
||||
cli::install_cli,
|
||||
await_initialization,
|
||||
server::get_default_server_url,
|
||||
server::set_default_server_url,
|
||||
get_display_backend,
|
||||
set_display_backend,
|
||||
markdown::parse_markdown_command,
|
||||
check_app_exists,
|
||||
resolve_app_path
|
||||
])
|
||||
.events(tauri_specta::collect_events![LoadingWindowComplete])
|
||||
.error_handling(tauri_specta::ErrorHandlingMode::Throw);
|
||||
let builder = make_specta_builder();
|
||||
|
||||
#[cfg(debug_assertions)] // <- Only export on non-release builds
|
||||
builder
|
||||
.export(
|
||||
specta_typescript::Typescript::default(),
|
||||
"../src/bindings.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
export_types(&builder);
|
||||
|
||||
#[cfg(all(target_os = "macos", not(debug_assertions)))]
|
||||
let _ = std::process::Command::new("killall")
|
||||
@@ -712,6 +500,47 @@ pub fn run() {
|
||||
});
|
||||
}
|
||||
|
||||
fn make_specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
tauri_specta::Builder::<tauri::Wry>::new()
|
||||
// Then register them (separated by a comma)
|
||||
.commands(tauri_specta::collect_commands![
|
||||
kill_sidecar,
|
||||
cli::install_cli,
|
||||
await_initialization,
|
||||
server::get_default_server_url,
|
||||
server::set_default_server_url,
|
||||
server::get_wsl_config,
|
||||
server::set_wsl_config,
|
||||
get_display_backend,
|
||||
set_display_backend,
|
||||
markdown::parse_markdown_command,
|
||||
check_app_exists,
|
||||
wsl_path,
|
||||
resolve_app_path
|
||||
])
|
||||
.events(tauri_specta::collect_events![
|
||||
LoadingWindowComplete,
|
||||
SqliteMigrationProgress
|
||||
])
|
||||
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
|
||||
}
|
||||
|
||||
fn export_types(builder: &tauri_specta::Builder<tauri::Wry>) {
|
||||
builder
|
||||
.export(
|
||||
specta_typescript::Typescript::default(),
|
||||
"../src/bindings.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_export_types() {
|
||||
let builder = make_specta_builder();
|
||||
export_types(&builder);
|
||||
}
|
||||
|
||||
#[derive(tauri_specta::Event, serde::Deserialize, specta::Type)]
|
||||
struct LoadingWindowComplete;
|
||||
|
||||
@@ -731,17 +560,46 @@ async fn initialize(app: AppHandle) {
|
||||
|
||||
tracing::info!("Main and loading windows created");
|
||||
|
||||
// SQLite migration handling:
|
||||
// We only do this if the sqlite db doesn't exist, and we're expecting the sidecar to create it
|
||||
// First, we spawn a task that listens for SqliteMigrationProgress events that can
|
||||
// come from any invocation of the sidecar CLI. The progress is captured by a stdout stream interceptor.
|
||||
// Then in the loading task, we wait for sqlite migration to complete before
|
||||
// starting our health check against the server, otherwise long migrations could result in a timeout.
|
||||
let sqlite_enabled = option_env!("OPENCODE_SQLITE").is_some();
|
||||
let sqlite_done = (sqlite_enabled && !sqlite_file_exists()).then(|| {
|
||||
tracing::info!(
|
||||
path = %opencode_db_path().expect("failed to get db path").display(),
|
||||
"Sqlite file not found, waiting for it to be generated"
|
||||
);
|
||||
|
||||
let (done_tx, done_rx) = oneshot::channel::<()>();
|
||||
let done_tx = Arc::new(Mutex::new(Some(done_tx)));
|
||||
|
||||
let init_tx = init_tx.clone();
|
||||
let id = SqliteMigrationProgress::listen(&app, move |e| {
|
||||
let _ = init_tx.send(InitStep::SqliteWaiting);
|
||||
|
||||
if matches!(e.payload, SqliteMigrationProgress::Done)
|
||||
&& let Some(done_tx) = done_tx.lock().unwrap().take()
|
||||
{
|
||||
let _ = done_tx.send(());
|
||||
}
|
||||
});
|
||||
|
||||
let app = app.clone();
|
||||
tokio::spawn(done_rx.map(async move |_| {
|
||||
app.unlisten(id);
|
||||
}))
|
||||
});
|
||||
|
||||
let loading_task = tokio::spawn({
|
||||
let init_tx = init_tx.clone();
|
||||
let app = app.clone();
|
||||
|
||||
async move {
|
||||
let mut sqlite_exists = sqlite_file_exists();
|
||||
|
||||
tracing::info!("Setting up server connection");
|
||||
let server_connection = setup_server_connection(app.clone()).await;
|
||||
tracing::info!("Server connection setup");
|
||||
|
||||
// we delay spawning this future so that the timeout is created lazily
|
||||
let cli_health_check = match server_connection {
|
||||
@@ -797,23 +655,12 @@ async fn initialize(app: AppHandle) {
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("server connection started");
|
||||
|
||||
if let Some(cli_health_check) = cli_health_check {
|
||||
if sqlite_enabled {
|
||||
tracing::debug!(sqlite_exists, "Checking sqlite file existence");
|
||||
if !sqlite_exists {
|
||||
tracing::info!(
|
||||
path = %opencode_db_path().expect("failed to get db path").display(),
|
||||
"Sqlite file not found, waiting for it to be generated"
|
||||
);
|
||||
let _ = init_tx.send(InitStep::SqliteWaiting);
|
||||
|
||||
while !sqlite_exists {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
sqlite_exists = sqlite_file_exists();
|
||||
}
|
||||
}
|
||||
if let Some(sqlite_done_rx) = sqlite_done {
|
||||
let _ = sqlite_done_rx.await;
|
||||
}
|
||||
|
||||
tokio::spawn(cli_health_check);
|
||||
}
|
||||
|
||||
@@ -829,11 +676,11 @@ async fn initialize(app: AppHandle) {
|
||||
.is_err()
|
||||
{
|
||||
tracing::debug!("Loading task timed out, showing loading window");
|
||||
let app = app.clone();
|
||||
let loading_window = LoadingWindow::create(&app).expect("Failed to create loading window");
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
Some(loading_window)
|
||||
} else {
|
||||
tracing::debug!("Showing main window without loading window");
|
||||
MainWindow::create(&app).expect("Failed to create main window");
|
||||
|
||||
None
|
||||
@@ -842,7 +689,6 @@ async fn initialize(app: AppHandle) {
|
||||
let _ = loading_task.await;
|
||||
|
||||
tracing::info!("Loading done, completing initialisation");
|
||||
|
||||
let _ = init_tx.send(InitStep::Done);
|
||||
|
||||
if loading_window.is_some() {
|
||||
|
||||
@@ -8,9 +8,14 @@ use tokio::task::JoinHandle;
|
||||
|
||||
use crate::{
|
||||
cli,
|
||||
constants::{DEFAULT_SERVER_URL_KEY, SETTINGS_STORE},
|
||||
constants::{DEFAULT_SERVER_URL_KEY, SETTINGS_STORE, WSL_ENABLED_KEY},
|
||||
};
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, specta::Type, Debug, Default)]
|
||||
pub struct WslConfig {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
|
||||
@@ -48,6 +53,38 @@ pub async fn set_default_server_url(app: AppHandle, url: Option<String>) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_wsl_config(app: AppHandle) -> Result<WslConfig, String> {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE)
|
||||
.map_err(|e| format!("Failed to open settings store: {}", e))?;
|
||||
|
||||
let enabled = store
|
||||
.get(WSL_ENABLED_KEY)
|
||||
.as_ref()
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(WslConfig { enabled })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn set_wsl_config(app: AppHandle, config: WslConfig) -> Result<(), String> {
|
||||
let store = app
|
||||
.store(SETTINGS_STORE)
|
||||
.map_err(|e| format!("Failed to open settings store: {}", e))?;
|
||||
|
||||
store.set(WSL_ENABLED_KEY, serde_json::Value::Bool(config.enabled));
|
||||
|
||||
store
|
||||
.save()
|
||||
.map_err(|e| format!("Failed to save settings: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_saved_server_url(app: &tauri::AppHandle) -> Option<String> {
|
||||
if let Some(url) = get_default_server_url(app.clone()).ok().flatten() {
|
||||
tracing::info!(%url, "Using desktop-specific custom URL");
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::constants::{UPDATER_ENABLED, window_state_flags};
|
||||
use crate::{
|
||||
constants::{UPDATER_ENABLED, window_state_flags},
|
||||
server::get_wsl_config,
|
||||
};
|
||||
use std::{ops::Deref, time::Duration};
|
||||
use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
|
||||
use tauri_plugin_window_state::AppHandleExt;
|
||||
@@ -22,6 +25,11 @@ impl MainWindow {
|
||||
return Ok(Self(window));
|
||||
}
|
||||
|
||||
let wsl_enabled = get_wsl_config(app.clone())
|
||||
.ok()
|
||||
.map(|v| v.enabled)
|
||||
.unwrap_or(false);
|
||||
|
||||
let window_builder = base_window_config(
|
||||
WebviewWindowBuilder::new(app, Self::LABEL, WebviewUrl::App("/".into())),
|
||||
app,
|
||||
@@ -36,6 +44,7 @@ impl MainWindow {
|
||||
r#"
|
||||
window.__OPENCODE__ ??= {{}};
|
||||
window.__OPENCODE__.updaterEnabled = {UPDATER_ENABLED};
|
||||
window.__OPENCODE__.wsl = {wsl_enabled};
|
||||
"#
|
||||
));
|
||||
|
||||
|
||||
@@ -10,16 +10,20 @@ export const commands = {
|
||||
awaitInitialization: (events: Channel) => __TAURI_INVOKE<ServerReadyData>("await_initialization", { events }),
|
||||
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
|
||||
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
|
||||
getWslConfig: () => __TAURI_INVOKE<WslConfig>("get_wsl_config"),
|
||||
setWslConfig: (config: WslConfig) => __TAURI_INVOKE<null>("set_wsl_config", { config }),
|
||||
getDisplayBackend: () => __TAURI_INVOKE<"wayland" | "auto" | null>("get_display_backend"),
|
||||
setDisplayBackend: (backend: LinuxDisplayBackend) => __TAURI_INVOKE<null>("set_display_backend", { backend }),
|
||||
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
|
||||
checkAppExists: (appName: string) => __TAURI_INVOKE<boolean>("check_app_exists", { appName }),
|
||||
wslPath: (path: string, mode: "windows" | "linux" | null) => __TAURI_INVOKE<string>("wsl_path", { path, mode }),
|
||||
resolveAppPath: (appName: string) => __TAURI_INVOKE<string | null>("resolve_app_path", { appName }),
|
||||
};
|
||||
|
||||
/** Events */
|
||||
export const events = {
|
||||
loadingWindowComplete: makeEvent<LoadingWindowComplete>("loading-window-complete"),
|
||||
sqliteMigrationProgress: makeEvent<SqliteMigrationProgress>("sqlite-migration-progress"),
|
||||
};
|
||||
|
||||
/* Types */
|
||||
@@ -34,6 +38,14 @@ export type ServerReadyData = {
|
||||
password: string | null,
|
||||
};
|
||||
|
||||
export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" };
|
||||
|
||||
export type WslConfig = {
|
||||
enabled: boolean,
|
||||
};
|
||||
|
||||
export type WslPathMode = "windows" | "linux";
|
||||
|
||||
/* Tauri Specta runtime */
|
||||
function makeEvent<T>(name: string) {
|
||||
const base = {
|
||||
|
||||
@@ -16,7 +16,6 @@ import { open as shellOpen } from "@tauri-apps/plugin-shell"
|
||||
import { type as ostype } from "@tauri-apps/plugin-os"
|
||||
import { check, Update } from "@tauri-apps/plugin-updater"
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"
|
||||
import { relaunch } from "@tauri-apps/plugin-process"
|
||||
import { AsyncStorage } from "@solid-primitives/storage"
|
||||
@@ -30,7 +29,7 @@ import { UPDATER_ENABLED } from "./updater"
|
||||
import { initI18n, t } from "./i18n"
|
||||
import pkg from "../package.json"
|
||||
import "./styles.css"
|
||||
import { commands, InitStep } from "./bindings"
|
||||
import { commands, InitStep, type WslConfig } from "./bindings"
|
||||
import { Channel } from "@tauri-apps/api/core"
|
||||
import { createMenu } from "./menu"
|
||||
|
||||
@@ -59,338 +58,374 @@ const listenForDeepLinks = async () => {
|
||||
await onOpenUrl((urls) => emitDeepLinks(urls)).catch(() => undefined)
|
||||
}
|
||||
|
||||
const createPlatform = (password: Accessor<string | null>): Platform => ({
|
||||
platform: "desktop",
|
||||
os: (() => {
|
||||
const createPlatform = (password: Accessor<string | null>): Platform => {
|
||||
const os = (() => {
|
||||
const type = ostype()
|
||||
if (type === "macos" || type === "windows" || type === "linux") return type
|
||||
return undefined
|
||||
})(),
|
||||
version: pkg.version,
|
||||
})()
|
||||
|
||||
async openDirectoryPickerDialog(opts) {
|
||||
const result = await open({
|
||||
directory: true,
|
||||
multiple: opts?.multiple ?? false,
|
||||
title: opts?.title ?? t("desktop.dialog.chooseFolder"),
|
||||
})
|
||||
return result
|
||||
},
|
||||
const wslHome = async () => {
|
||||
if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined
|
||||
return commands.wslPath("~", "windows").catch(() => undefined)
|
||||
}
|
||||
|
||||
async openFilePickerDialog(opts) {
|
||||
const result = await open({
|
||||
directory: false,
|
||||
multiple: opts?.multiple ?? false,
|
||||
title: opts?.title ?? t("desktop.dialog.chooseFile"),
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
async saveFilePickerDialog(opts) {
|
||||
const result = await save({
|
||||
title: opts?.title ?? t("desktop.dialog.saveFile"),
|
||||
defaultPath: opts?.defaultPath,
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
openLink(url: string) {
|
||||
void shellOpen(url).catch(() => undefined)
|
||||
},
|
||||
|
||||
async openPath(path: string, app?: string) {
|
||||
const os = ostype()
|
||||
if (os === "windows" && app) {
|
||||
const resolvedApp = await commands.resolveAppPath(app)
|
||||
return openerOpenPath(path, resolvedApp || app)
|
||||
const handleWslPicker = async <T extends string | string[]>(result: T | null): Promise<T | null> => {
|
||||
if (!result || !window.__OPENCODE__?.wsl) return result
|
||||
if (Array.isArray(result)) {
|
||||
return Promise.all(result.map((path) => commands.wslPath(path, "linux").catch(() => path))) as any
|
||||
}
|
||||
return openerOpenPath(path, app)
|
||||
},
|
||||
return commands.wslPath(result, "linux").catch(() => result) as any
|
||||
}
|
||||
|
||||
back() {
|
||||
window.history.back()
|
||||
},
|
||||
return {
|
||||
platform: "desktop",
|
||||
os,
|
||||
version: pkg.version,
|
||||
|
||||
forward() {
|
||||
window.history.forward()
|
||||
},
|
||||
async openDirectoryPickerDialog(opts) {
|
||||
const defaultPath = await wslHome()
|
||||
const result = await open({
|
||||
directory: true,
|
||||
multiple: opts?.multiple ?? false,
|
||||
title: opts?.title ?? t("desktop.dialog.chooseFolder"),
|
||||
defaultPath,
|
||||
})
|
||||
return await handleWslPicker(result)
|
||||
},
|
||||
|
||||
storage: (() => {
|
||||
type StoreLike = {
|
||||
get(key: string): Promise<string | null | undefined>
|
||||
set(key: string, value: string): Promise<unknown>
|
||||
delete(key: string): Promise<unknown>
|
||||
clear(): Promise<unknown>
|
||||
keys(): Promise<string[]>
|
||||
length(): Promise<number>
|
||||
}
|
||||
async openFilePickerDialog(opts) {
|
||||
const result = await open({
|
||||
directory: false,
|
||||
multiple: opts?.multiple ?? false,
|
||||
title: opts?.title ?? t("desktop.dialog.chooseFile"),
|
||||
})
|
||||
return handleWslPicker(result)
|
||||
},
|
||||
|
||||
const WRITE_DEBOUNCE_MS = 250
|
||||
async saveFilePickerDialog(opts) {
|
||||
const result = await save({
|
||||
title: opts?.title ?? t("desktop.dialog.saveFile"),
|
||||
defaultPath: opts?.defaultPath,
|
||||
})
|
||||
return handleWslPicker(result)
|
||||
},
|
||||
|
||||
const storeCache = new Map<string, Promise<StoreLike>>()
|
||||
const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
|
||||
const memoryCache = new Map<string, StoreLike>()
|
||||
openLink(url: string) {
|
||||
void shellOpen(url).catch(() => undefined)
|
||||
},
|
||||
async openPath(path: string, app?: string) {
|
||||
const os = ostype()
|
||||
if (os === "windows") {
|
||||
const resolvedApp = (app && (await commands.resolveAppPath(app))) || app
|
||||
const resolvedPath = await (async () => {
|
||||
if (window.__OPENCODE__?.wsl) {
|
||||
const converted = await commands.wslPath(path, "windows").catch(() => null)
|
||||
if (converted) return converted
|
||||
}
|
||||
|
||||
const flushAll = async () => {
|
||||
const apis = Array.from(apiCache.values())
|
||||
await Promise.all(apis.map((api) => api.flush().catch(() => undefined)))
|
||||
}
|
||||
return path
|
||||
})()
|
||||
return openerOpenPath(resolvedPath, resolvedApp)
|
||||
}
|
||||
return openerOpenPath(path, app)
|
||||
},
|
||||
|
||||
if ("addEventListener" in globalThis) {
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState !== "hidden") return
|
||||
void flushAll()
|
||||
back() {
|
||||
window.history.back()
|
||||
},
|
||||
|
||||
forward() {
|
||||
window.history.forward()
|
||||
},
|
||||
|
||||
storage: (() => {
|
||||
type StoreLike = {
|
||||
get(key: string): Promise<string | null | undefined>
|
||||
set(key: string, value: string): Promise<unknown>
|
||||
delete(key: string): Promise<unknown>
|
||||
clear(): Promise<unknown>
|
||||
keys(): Promise<string[]>
|
||||
length(): Promise<number>
|
||||
}
|
||||
|
||||
window.addEventListener("pagehide", () => void flushAll())
|
||||
document.addEventListener("visibilitychange", handleVisibility)
|
||||
}
|
||||
const WRITE_DEBOUNCE_MS = 250
|
||||
|
||||
const createMemoryStore = () => {
|
||||
const data = new Map<string, string>()
|
||||
const store: StoreLike = {
|
||||
get: async (key) => data.get(key),
|
||||
set: async (key, value) => {
|
||||
data.set(key, value)
|
||||
},
|
||||
delete: async (key) => {
|
||||
data.delete(key)
|
||||
},
|
||||
clear: async () => {
|
||||
data.clear()
|
||||
},
|
||||
keys: async () => Array.from(data.keys()),
|
||||
length: async () => data.size,
|
||||
const storeCache = new Map<string, Promise<StoreLike>>()
|
||||
const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
|
||||
const memoryCache = new Map<string, StoreLike>()
|
||||
|
||||
const flushAll = async () => {
|
||||
const apis = Array.from(apiCache.values())
|
||||
await Promise.all(apis.map((api) => api.flush().catch(() => undefined)))
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
const getStore = (name: string) => {
|
||||
const cached = storeCache.get(name)
|
||||
if (cached) return cached
|
||||
if ("addEventListener" in globalThis) {
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState !== "hidden") return
|
||||
void flushAll()
|
||||
}
|
||||
|
||||
const store = Store.load(name).catch(() => {
|
||||
const cached = memoryCache.get(name)
|
||||
window.addEventListener("pagehide", () => void flushAll())
|
||||
document.addEventListener("visibilitychange", handleVisibility)
|
||||
}
|
||||
|
||||
const createMemoryStore = () => {
|
||||
const data = new Map<string, string>()
|
||||
const store: StoreLike = {
|
||||
get: async (key) => data.get(key),
|
||||
set: async (key, value) => {
|
||||
data.set(key, value)
|
||||
},
|
||||
delete: async (key) => {
|
||||
data.delete(key)
|
||||
},
|
||||
clear: async () => {
|
||||
data.clear()
|
||||
},
|
||||
keys: async () => Array.from(data.keys()),
|
||||
length: async () => data.size,
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
const getStore = (name: string) => {
|
||||
const cached = storeCache.get(name)
|
||||
if (cached) return cached
|
||||
|
||||
const memory = createMemoryStore()
|
||||
memoryCache.set(name, memory)
|
||||
return memory
|
||||
})
|
||||
const store = Store.load(name).catch(() => {
|
||||
const cached = memoryCache.get(name)
|
||||
if (cached) return cached
|
||||
|
||||
storeCache.set(name, store)
|
||||
return store
|
||||
}
|
||||
const memory = createMemoryStore()
|
||||
memoryCache.set(name, memory)
|
||||
return memory
|
||||
})
|
||||
|
||||
const createStorage = (name: string) => {
|
||||
const pending = new Map<string, string | null>()
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let flushing: Promise<void> | undefined
|
||||
storeCache.set(name, store)
|
||||
return store
|
||||
}
|
||||
|
||||
const flush = async () => {
|
||||
if (flushing) return flushing
|
||||
const createStorage = (name: string) => {
|
||||
const pending = new Map<string, string | null>()
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let flushing: Promise<void> | undefined
|
||||
|
||||
flushing = (async () => {
|
||||
const store = await getStore(name)
|
||||
while (pending.size > 0) {
|
||||
const batch = Array.from(pending.entries())
|
||||
pending.clear()
|
||||
for (const [key, value] of batch) {
|
||||
if (value === null) {
|
||||
await store.delete(key).catch(() => undefined)
|
||||
} else {
|
||||
await store.set(key, value).catch(() => undefined)
|
||||
const flush = async () => {
|
||||
if (flushing) return flushing
|
||||
|
||||
flushing = (async () => {
|
||||
const store = await getStore(name)
|
||||
while (pending.size > 0) {
|
||||
const batch = Array.from(pending.entries())
|
||||
pending.clear()
|
||||
for (const [key, value] of batch) {
|
||||
if (value === null) {
|
||||
await store.delete(key).catch(() => undefined)
|
||||
} else {
|
||||
await store.set(key, value).catch(() => undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})().finally(() => {
|
||||
flushing = undefined
|
||||
})
|
||||
})().finally(() => {
|
||||
flushing = undefined
|
||||
})
|
||||
|
||||
return flushing
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined
|
||||
void flush()
|
||||
}, WRITE_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
const api: AsyncStorage & { flush: () => Promise<void> } = {
|
||||
flush,
|
||||
getItem: async (key: string) => {
|
||||
const next = pending.get(key)
|
||||
if (next !== undefined) return next
|
||||
|
||||
const store = await getStore(name)
|
||||
const value = await store.get(key).catch(() => null)
|
||||
if (value === undefined) return null
|
||||
return value
|
||||
},
|
||||
setItem: async (key: string, value: string) => {
|
||||
pending.set(key, value)
|
||||
schedule()
|
||||
},
|
||||
removeItem: async (key: string) => {
|
||||
pending.set(key, null)
|
||||
schedule()
|
||||
},
|
||||
clear: async () => {
|
||||
pending.clear()
|
||||
const store = await getStore(name)
|
||||
await store.clear().catch(() => undefined)
|
||||
},
|
||||
key: async (index: number) => {
|
||||
const store = await getStore(name)
|
||||
return (await store.keys().catch(() => []))[index]
|
||||
},
|
||||
getLength: async () => {
|
||||
const store = await getStore(name)
|
||||
return await store.length().catch(() => 0)
|
||||
},
|
||||
get length() {
|
||||
return api.getLength()
|
||||
},
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
return (name = "default.dat") => {
|
||||
const cached = apiCache.get(name)
|
||||
if (cached) return cached
|
||||
|
||||
const api = createStorage(name)
|
||||
apiCache.set(name, api)
|
||||
return api
|
||||
}
|
||||
})(),
|
||||
|
||||
checkUpdate: async () => {
|
||||
if (!UPDATER_ENABLED) return { updateAvailable: false }
|
||||
const next = await check().catch(() => null)
|
||||
if (!next) return { updateAvailable: false }
|
||||
const ok = await next
|
||||
.download()
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (!ok) return { updateAvailable: false }
|
||||
update = next
|
||||
return { updateAvailable: true, version: next.version }
|
||||
},
|
||||
|
||||
update: async () => {
|
||||
if (!UPDATER_ENABLED || !update) return
|
||||
if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
|
||||
await update.install().catch(() => undefined)
|
||||
},
|
||||
|
||||
restart: async () => {
|
||||
await commands.killSidecar().catch(() => undefined)
|
||||
await relaunch()
|
||||
},
|
||||
|
||||
notify: async (title, description, href) => {
|
||||
const granted = await isPermissionGranted().catch(() => false)
|
||||
const permission = granted ? "granted" : await requestPermission().catch(() => "denied")
|
||||
if (permission !== "granted") return
|
||||
|
||||
const win = getCurrentWindow()
|
||||
const focused = await win.isFocused().catch(() => document.hasFocus())
|
||||
if (focused) return
|
||||
|
||||
await Promise.resolve()
|
||||
.then(() => {
|
||||
const notification = new Notification(title, {
|
||||
body: description ?? "",
|
||||
icon: "https://opencode.ai/favicon-96x96-v3.png",
|
||||
})
|
||||
notification.onclick = () => {
|
||||
const win = getCurrentWindow()
|
||||
void win.show().catch(() => undefined)
|
||||
void win.unminimize().catch(() => undefined)
|
||||
void win.setFocus().catch(() => undefined)
|
||||
if (href) {
|
||||
window.history.pushState(null, "", href)
|
||||
window.dispatchEvent(new PopStateEvent("popstate"))
|
||||
}
|
||||
notification.close()
|
||||
return flushing
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined
|
||||
void flush()
|
||||
}, WRITE_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
const api: AsyncStorage & { flush: () => Promise<void> } = {
|
||||
flush,
|
||||
getItem: async (key: string) => {
|
||||
const next = pending.get(key)
|
||||
if (next !== undefined) return next
|
||||
|
||||
const store = await getStore(name)
|
||||
const value = await store.get(key).catch(() => null)
|
||||
if (value === undefined) return null
|
||||
return value
|
||||
},
|
||||
setItem: async (key: string, value: string) => {
|
||||
pending.set(key, value)
|
||||
schedule()
|
||||
},
|
||||
removeItem: async (key: string) => {
|
||||
pending.set(key, null)
|
||||
schedule()
|
||||
},
|
||||
clear: async () => {
|
||||
pending.clear()
|
||||
const store = await getStore(name)
|
||||
await store.clear().catch(() => undefined)
|
||||
},
|
||||
key: async (index: number) => {
|
||||
const store = await getStore(name)
|
||||
return (await store.keys().catch(() => []))[index]
|
||||
},
|
||||
getLength: async () => {
|
||||
const store = await getStore(name)
|
||||
return await store.length().catch(() => 0)
|
||||
},
|
||||
get length() {
|
||||
return api.getLength()
|
||||
},
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
return (name = "default.dat") => {
|
||||
const cached = apiCache.get(name)
|
||||
if (cached) return cached
|
||||
|
||||
const api = createStorage(name)
|
||||
apiCache.set(name, api)
|
||||
return api
|
||||
}
|
||||
})(),
|
||||
|
||||
checkUpdate: async () => {
|
||||
if (!UPDATER_ENABLED) return { updateAvailable: false }
|
||||
const next = await check().catch(() => null)
|
||||
if (!next) return { updateAvailable: false }
|
||||
const ok = await next
|
||||
.download()
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (!ok) return { updateAvailable: false }
|
||||
update = next
|
||||
return { updateAvailable: true, version: next.version }
|
||||
},
|
||||
|
||||
update: async () => {
|
||||
if (!UPDATER_ENABLED || !update) return
|
||||
if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
|
||||
await update.install().catch(() => undefined)
|
||||
},
|
||||
|
||||
restart: async () => {
|
||||
await commands.killSidecar().catch(() => undefined)
|
||||
await relaunch()
|
||||
},
|
||||
|
||||
notify: async (title, description, href) => {
|
||||
const granted = await isPermissionGranted().catch(() => false)
|
||||
const permission = granted ? "granted" : await requestPermission().catch(() => "denied")
|
||||
if (permission !== "granted") return
|
||||
|
||||
const win = getCurrentWindow()
|
||||
const focused = await win.isFocused().catch(() => document.hasFocus())
|
||||
if (focused) return
|
||||
|
||||
await Promise.resolve()
|
||||
.then(() => {
|
||||
const notification = new Notification(title, {
|
||||
body: description ?? "",
|
||||
icon: "https://opencode.ai/favicon-96x96-v3.png",
|
||||
})
|
||||
notification.onclick = () => {
|
||||
const win = getCurrentWindow()
|
||||
void win.show().catch(() => undefined)
|
||||
void win.unminimize().catch(() => undefined)
|
||||
void win.setFocus().catch(() => undefined)
|
||||
if (href) {
|
||||
window.history.pushState(null, "", href)
|
||||
window.dispatchEvent(new PopStateEvent("popstate"))
|
||||
}
|
||||
notification.close()
|
||||
}
|
||||
})
|
||||
.catch(() => undefined)
|
||||
},
|
||||
|
||||
fetch: (input, init) => {
|
||||
const pw = password()
|
||||
|
||||
const addHeader = (headers: Headers, password: string) => {
|
||||
headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
|
||||
}
|
||||
|
||||
if (input instanceof Request) {
|
||||
if (pw) addHeader(input.headers, pw)
|
||||
return tauriFetch(input)
|
||||
} else {
|
||||
const headers = new Headers(init?.headers)
|
||||
if (pw) addHeader(headers, pw)
|
||||
return tauriFetch(input, {
|
||||
...(init as any),
|
||||
headers: headers,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getWslEnabled: async () => {
|
||||
const next = await commands.getWslConfig().catch(() => null)
|
||||
if (next) return next.enabled
|
||||
return window.__OPENCODE__!.wsl ?? false
|
||||
},
|
||||
|
||||
setWslEnabled: async (enabled) => {
|
||||
await commands.setWslConfig({ enabled })
|
||||
},
|
||||
|
||||
getDefaultServerUrl: async () => {
|
||||
const result = await commands.getDefaultServerUrl().catch(() => null)
|
||||
return result
|
||||
},
|
||||
|
||||
setDefaultServerUrl: async (url: string | null) => {
|
||||
await commands.setDefaultServerUrl(url)
|
||||
},
|
||||
|
||||
getDisplayBackend: async () => {
|
||||
const result = await commands.getDisplayBackend().catch(() => null)
|
||||
return result
|
||||
},
|
||||
|
||||
setDisplayBackend: async (backend) => {
|
||||
await commands.setDisplayBackend(backend)
|
||||
},
|
||||
|
||||
parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
|
||||
|
||||
webviewZoom,
|
||||
|
||||
checkAppExists: async (appName: string) => {
|
||||
return commands.checkAppExists(appName)
|
||||
},
|
||||
|
||||
async readClipboardImage() {
|
||||
const image = await readImage().catch(() => null)
|
||||
if (!image) return null
|
||||
const bytes = await image.rgba().catch(() => null)
|
||||
if (!bytes || bytes.length === 0) return null
|
||||
const size = await image.size().catch(() => null)
|
||||
if (!size) return null
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = size.width
|
||||
canvas.height = size.height
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return null
|
||||
const imageData = ctx.createImageData(size.width, size.height)
|
||||
imageData.data.set(bytes)
|
||||
ctx.putImageData(imageData, 0, 0)
|
||||
return new Promise<File | null>((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return resolve(null)
|
||||
resolve(new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png" }))
|
||||
}, "image/png")
|
||||
})
|
||||
.catch(() => undefined)
|
||||
},
|
||||
|
||||
fetch: (input, init) => {
|
||||
const pw = password()
|
||||
|
||||
const addHeader = (headers: Headers, password: string) => {
|
||||
headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
|
||||
}
|
||||
|
||||
if (input instanceof Request) {
|
||||
if (pw) addHeader(input.headers, pw)
|
||||
return tauriFetch(input)
|
||||
} else {
|
||||
const headers = new Headers(init?.headers)
|
||||
if (pw) addHeader(headers, pw)
|
||||
return tauriFetch(input, {
|
||||
...(init as any),
|
||||
headers: headers,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultServerUrl: async () => {
|
||||
const result = await commands.getDefaultServerUrl().catch(() => null)
|
||||
return result
|
||||
},
|
||||
|
||||
setDefaultServerUrl: async (url: string | null) => {
|
||||
await commands.setDefaultServerUrl(url)
|
||||
},
|
||||
|
||||
getDisplayBackend: async () => {
|
||||
const result = await invoke<DisplayBackend | null>("get_display_backend").catch(() => null)
|
||||
return result
|
||||
},
|
||||
|
||||
setDisplayBackend: async (backend) => {
|
||||
await invoke("set_display_backend", { backend }).catch(() => undefined)
|
||||
},
|
||||
|
||||
parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
|
||||
|
||||
webviewZoom,
|
||||
|
||||
checkAppExists: async (appName: string) => {
|
||||
return commands.checkAppExists(appName)
|
||||
},
|
||||
|
||||
async readClipboardImage() {
|
||||
const image = await readImage().catch(() => null)
|
||||
if (!image) return null
|
||||
const bytes = await image.rgba().catch(() => null)
|
||||
if (!bytes || bytes.length === 0) return null
|
||||
const size = await image.size().catch(() => null)
|
||||
if (!size) return null
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = size.width
|
||||
canvas.height = size.height
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return null
|
||||
const imageData = ctx.createImageData(size.width, size.height)
|
||||
imageData.data.set(bytes)
|
||||
ctx.putImageData(imageData, 0, 0)
|
||||
return new Promise<File | null>((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return resolve(null)
|
||||
resolve(new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png" }))
|
||||
}, "image/png")
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let menuTrigger = null as null | ((id: string) => void)
|
||||
createMenu((id) => {
|
||||
@@ -400,6 +435,7 @@ void listenForDeepLinks()
|
||||
|
||||
render(() => {
|
||||
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
|
||||
|
||||
const platform = createPlatform(() => serverPassword())
|
||||
|
||||
function handleClick(e: MouseEvent) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import "@opencode-ai/app/index.css"
|
||||
import { Font } from "@opencode-ai/ui/font"
|
||||
import { Splash } from "@opencode-ai/ui/logo"
|
||||
import "./styles.css"
|
||||
import { createSignal, Match, onMount } from "solid-js"
|
||||
import { createSignal, Match, onCleanup, onMount } from "solid-js"
|
||||
import { commands, events, InitStep } from "./bindings"
|
||||
import { Channel } from "@tauri-apps/api/core"
|
||||
import { Switch } from "solid-js"
|
||||
@@ -57,15 +57,29 @@ render(() => {
|
||||
"This could take a couple of minutes",
|
||||
]
|
||||
const [textIndex, setTextIndex] = createSignal(0)
|
||||
const [progress, setProgress] = createSignal(0)
|
||||
|
||||
onMount(async () => {
|
||||
const listener = events.sqliteMigrationProgress.listen((e) => {
|
||||
if (e.payload.type === "InProgress") setProgress(e.payload.value)
|
||||
})
|
||||
onCleanup(() => listener.then((c) => c()))
|
||||
|
||||
await new Promise((res) => setTimeout(res, 3000))
|
||||
setTextIndex(1)
|
||||
await new Promise((res) => setTimeout(res, 6000))
|
||||
setTextIndex(2)
|
||||
})
|
||||
|
||||
return <>{textItems[textIndex()]}</>
|
||||
return (
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<span>{textItems[textIndex()]}</span>
|
||||
<span>Progress: {progress()}%</span>
|
||||
<div class="h-2 w-48 rounded-full border border-white relative">
|
||||
<div class="bg-[#fff] h-full absolute left-0 inset-y-0" style={{ width: `${progress()}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.1.58"
|
||||
version = "1.1.60"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.58/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.60/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.58/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.60/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.58/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.60/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.58/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.60/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.58/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.60/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.1.58",
|
||||
"version": "1.1.60",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -84,8 +84,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||
"@opentui/core": "0.1.77",
|
||||
"@opentui/solid": "0.1.77",
|
||||
"@opentui/core": "0.1.79",
|
||||
"@opentui/solid": "0.1.79",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Clipboard } from "@tui/util/clipboard"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
|
||||
import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32"
|
||||
import { Installation } from "@/installation"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
@@ -110,8 +111,17 @@ export function tui(input: {
|
||||
}) {
|
||||
// promise to prevent immediate exit
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const mode = await getTerminalBackgroundColor()
|
||||
|
||||
// Re-clear after getTerminalBackgroundColor() — setRawMode(false) restores
|
||||
// the original console mode which re-enables ENABLE_PROCESSED_INPUT.
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const onExit = async () => {
|
||||
unguard?.()
|
||||
await input.onExit?.()
|
||||
resolve()
|
||||
}
|
||||
@@ -730,7 +740,8 @@ function ErrorComponent(props: {
|
||||
const handleExit = async () => {
|
||||
renderer.setTerminalTitle("")
|
||||
renderer.destroy()
|
||||
props.onExit()
|
||||
win32FlushInputBuffer()
|
||||
await props.onExit()
|
||||
}
|
||||
|
||||
useKeyboard((evt) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { cmd } from "../cmd"
|
||||
import { tui } from "./app"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
|
||||
export const AttachCommand = cmd({
|
||||
command: "attach <url>",
|
||||
@@ -26,27 +27,34 @@ export const AttachCommand = cmd({
|
||||
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
const directory = (() => {
|
||||
if (!args.dir) return undefined
|
||||
try {
|
||||
process.chdir(args.dir)
|
||||
return process.cwd()
|
||||
} catch {
|
||||
// If the directory doesn't exist locally (remote attach), pass it through.
|
||||
return args.dir
|
||||
}
|
||||
})()
|
||||
const headers = (() => {
|
||||
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
|
||||
return { Authorization: auth }
|
||||
})()
|
||||
await tui({
|
||||
url: args.url,
|
||||
args: { sessionID: args.session },
|
||||
directory,
|
||||
headers,
|
||||
})
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
try {
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const directory = (() => {
|
||||
if (!args.dir) return undefined
|
||||
try {
|
||||
process.chdir(args.dir)
|
||||
return process.cwd()
|
||||
} catch {
|
||||
// If the directory doesn't exist locally (remote attach), pass it through.
|
||||
return args.dir
|
||||
}
|
||||
})()
|
||||
const headers = (() => {
|
||||
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
|
||||
if (!password) return undefined
|
||||
const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
|
||||
return { Authorization: auth }
|
||||
})()
|
||||
await tui({
|
||||
url: args.url,
|
||||
args: { sessionID: args.session },
|
||||
directory,
|
||||
headers,
|
||||
})
|
||||
} finally {
|
||||
unguard?.()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { FormatError, FormatUnknownError } from "@/cli/error"
|
||||
import { win32FlushInputBuffer } from "../win32"
|
||||
type Exit = ((reason?: unknown) => Promise<void>) & {
|
||||
message: {
|
||||
set: (value?: string) => () => void
|
||||
@@ -32,6 +33,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
|
||||
// Reset window title before destroying renderer
|
||||
renderer.setTerminalTitle("")
|
||||
renderer.destroy()
|
||||
win32FlushInputBuffer()
|
||||
await input.onExit?.()
|
||||
if (reason) {
|
||||
const formatted = FormatError(reason) ?? FormatUnknownError(reason)
|
||||
|
||||
@@ -149,6 +149,7 @@ export function Session() {
|
||||
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
|
||||
const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
|
||||
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
|
||||
const [showHeader, setShowHeader] = kv.signal("header_visible", true)
|
||||
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
|
||||
const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true)
|
||||
|
||||
@@ -582,6 +583,15 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: showHeader() ? "Hide header" : "Show header",
|
||||
value: "session.toggle.header",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setShowHeader((prev) => !prev)
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Page up",
|
||||
value: "session.page.up",
|
||||
@@ -963,7 +973,7 @@ export function Session() {
|
||||
<box flexDirection="row">
|
||||
<box flexGrow={1} paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<Show when={session()}>
|
||||
<Show when={!sidebarVisible() || !wide()}>
|
||||
<Show when={showHeader() && (!sidebarVisible() || !wide())}>
|
||||
<Header />
|
||||
</Show>
|
||||
<scrollbox
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Log } from "@/util/log"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||
import type { Event } from "@opencode-ai/sdk/v2"
|
||||
import type { EventSource } from "./context/sdk"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_WORKER_PATH: string
|
||||
@@ -77,99 +78,111 @@ export const TuiThreadCommand = cmd({
|
||||
describe: "agent to use",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
if (args.fork && !args.continue && !args.session) {
|
||||
UI.error("--fork requires --continue or --session")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
||||
const baseCwd = process.env.PWD ?? process.cwd()
|
||||
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
|
||||
const localWorker = new URL("./worker.ts", import.meta.url)
|
||||
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
|
||||
const workerPath = await iife(async () => {
|
||||
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
|
||||
if (await Bun.file(distWorker).exists()) return distWorker
|
||||
return localWorker
|
||||
})
|
||||
// Keep ENABLE_PROCESSED_INPUT cleared even if other code flips it.
|
||||
// (Important when running under `bun run` wrappers on Windows.)
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
try {
|
||||
process.chdir(cwd)
|
||||
} catch (e) {
|
||||
UI.error("Failed to change directory to " + cwd)
|
||||
return
|
||||
// Must be the very first thing — disables CTRL_C_EVENT before any Worker
|
||||
// spawn or async work so the OS cannot kill the process group.
|
||||
win32DisableProcessedInput()
|
||||
|
||||
if (args.fork && !args.continue && !args.session) {
|
||||
UI.error("--fork requires --continue or --session")
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
||||
const baseCwd = process.env.PWD ?? process.cwd()
|
||||
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
|
||||
const localWorker = new URL("./worker.ts", import.meta.url)
|
||||
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
|
||||
const workerPath = await iife(async () => {
|
||||
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
|
||||
if (await Bun.file(distWorker).exists()) return distWorker
|
||||
return localWorker
|
||||
})
|
||||
try {
|
||||
process.chdir(cwd)
|
||||
} catch (e) {
|
||||
UI.error("Failed to change directory to " + cwd)
|
||||
return
|
||||
}
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
env: Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
),
|
||||
})
|
||||
worker.onerror = (e) => {
|
||||
Log.Default.error(e)
|
||||
}
|
||||
const client = Rpc.client<typeof rpc>(worker)
|
||||
process.on("uncaughtException", (e) => {
|
||||
Log.Default.error(e)
|
||||
})
|
||||
process.on("unhandledRejection", (e) => {
|
||||
Log.Default.error(e)
|
||||
})
|
||||
process.on("SIGUSR2", async () => {
|
||||
await client.call("reload", undefined)
|
||||
})
|
||||
|
||||
const prompt = await iife(async () => {
|
||||
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
||||
if (!args.prompt) return piped
|
||||
return piped ? piped + "\n" + args.prompt : args.prompt
|
||||
})
|
||||
|
||||
// Check if server should be started (port or hostname explicitly set in CLI or config)
|
||||
const networkOpts = await resolveNetworkOptions(args)
|
||||
const shouldStartServer =
|
||||
process.argv.includes("--port") ||
|
||||
process.argv.includes("--hostname") ||
|
||||
process.argv.includes("--mdns") ||
|
||||
networkOpts.mdns ||
|
||||
networkOpts.port !== 0 ||
|
||||
networkOpts.hostname !== "127.0.0.1"
|
||||
|
||||
let url: string
|
||||
let customFetch: typeof fetch | undefined
|
||||
let events: EventSource | undefined
|
||||
|
||||
if (shouldStartServer) {
|
||||
// Start HTTP server for external access
|
||||
const server = await client.call("server", networkOpts)
|
||||
url = server.url
|
||||
} else {
|
||||
// Use direct RPC communication (no HTTP)
|
||||
url = "http://opencode.internal"
|
||||
customFetch = createWorkerFetch(client)
|
||||
events = createEventSource(client)
|
||||
}
|
||||
|
||||
const tuiPromise = tui({
|
||||
url,
|
||||
fetch: customFetch,
|
||||
events,
|
||||
args: {
|
||||
continue: args.continue,
|
||||
sessionID: args.session,
|
||||
agent: args.agent,
|
||||
model: args.model,
|
||||
prompt,
|
||||
fork: args.fork,
|
||||
},
|
||||
onExit: async () => {
|
||||
await client.call("shutdown", undefined)
|
||||
},
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
|
||||
}, 1000)
|
||||
|
||||
await tuiPromise
|
||||
} finally {
|
||||
unguard?.()
|
||||
}
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
env: Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
),
|
||||
})
|
||||
worker.onerror = (e) => {
|
||||
Log.Default.error(e)
|
||||
}
|
||||
const client = Rpc.client<typeof rpc>(worker)
|
||||
process.on("uncaughtException", (e) => {
|
||||
Log.Default.error(e)
|
||||
})
|
||||
process.on("unhandledRejection", (e) => {
|
||||
Log.Default.error(e)
|
||||
})
|
||||
process.on("SIGUSR2", async () => {
|
||||
await client.call("reload", undefined)
|
||||
})
|
||||
|
||||
const prompt = await iife(async () => {
|
||||
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
||||
if (!args.prompt) return piped
|
||||
return piped ? piped + "\n" + args.prompt : args.prompt
|
||||
})
|
||||
|
||||
// Check if server should be started (port or hostname explicitly set in CLI or config)
|
||||
const networkOpts = await resolveNetworkOptions(args)
|
||||
const shouldStartServer =
|
||||
process.argv.includes("--port") ||
|
||||
process.argv.includes("--hostname") ||
|
||||
process.argv.includes("--mdns") ||
|
||||
networkOpts.mdns ||
|
||||
networkOpts.port !== 0 ||
|
||||
networkOpts.hostname !== "127.0.0.1"
|
||||
|
||||
let url: string
|
||||
let customFetch: typeof fetch | undefined
|
||||
let events: EventSource | undefined
|
||||
|
||||
if (shouldStartServer) {
|
||||
// Start HTTP server for external access
|
||||
const server = await client.call("server", networkOpts)
|
||||
url = server.url
|
||||
} else {
|
||||
// Use direct RPC communication (no HTTP)
|
||||
url = "http://opencode.internal"
|
||||
customFetch = createWorkerFetch(client)
|
||||
events = createEventSource(client)
|
||||
}
|
||||
|
||||
const tuiPromise = tui({
|
||||
url,
|
||||
fetch: customFetch,
|
||||
events,
|
||||
args: {
|
||||
continue: args.continue,
|
||||
sessionID: args.session,
|
||||
agent: args.agent,
|
||||
model: args.model,
|
||||
prompt,
|
||||
fork: args.fork,
|
||||
},
|
||||
onExit: async () => {
|
||||
await client.call("shutdown", undefined)
|
||||
},
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
|
||||
}, 1000)
|
||||
|
||||
await tuiPromise
|
||||
},
|
||||
})
|
||||
|
||||
129
packages/opencode/src/cli/cmd/tui/win32.ts
Normal file
129
packages/opencode/src/cli/cmd/tui/win32.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { dlopen, ptr } from "bun:ffi"
|
||||
|
||||
const STD_INPUT_HANDLE = -10
|
||||
const ENABLE_PROCESSED_INPUT = 0x0001
|
||||
|
||||
const kernel = () =>
|
||||
dlopen("kernel32.dll", {
|
||||
GetStdHandle: { args: ["i32"], returns: "ptr" },
|
||||
GetConsoleMode: { args: ["ptr", "ptr"], returns: "i32" },
|
||||
SetConsoleMode: { args: ["ptr", "u32"], returns: "i32" },
|
||||
FlushConsoleInputBuffer: { args: ["ptr"], returns: "i32" },
|
||||
})
|
||||
|
||||
let k32: ReturnType<typeof kernel> | undefined
|
||||
|
||||
function load() {
|
||||
if (process.platform !== "win32") return false
|
||||
try {
|
||||
k32 ??= kernel()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear ENABLE_PROCESSED_INPUT on the console stdin handle.
|
||||
*/
|
||||
export function win32DisableProcessedInput() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buf = new Uint32Array(1)
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
|
||||
const mode = buf[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard any queued console input (mouse events, key presses, etc.).
|
||||
*/
|
||||
export function win32FlushInputBuffer() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
k32!.symbols.FlushConsoleInputBuffer(handle)
|
||||
}
|
||||
|
||||
let unhook: (() => void) | undefined
|
||||
|
||||
/**
|
||||
* Keep ENABLE_PROCESSED_INPUT disabled.
|
||||
*
|
||||
* On Windows, Ctrl+C becomes a CTRL_C_EVENT (instead of stdin input) when
|
||||
* ENABLE_PROCESSED_INPUT is set. Various runtimes can re-apply console modes
|
||||
* (sometimes on a later tick), and the flag is console-global, not per-process.
|
||||
*
|
||||
* We combine:
|
||||
* - A `setRawMode(...)` hook to re-clear after known raw-mode toggles.
|
||||
* - A low-frequency poll as a backstop for native/external mode changes.
|
||||
*/
|
||||
export function win32InstallCtrlCGuard() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
if (unhook) return unhook
|
||||
|
||||
const stdin = process.stdin as any
|
||||
const original = stdin.setRawMode
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buf = new Uint32Array(1)
|
||||
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
const initial = buf[0]!
|
||||
|
||||
const enforce = () => {
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
const mode = buf[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
|
||||
// Some runtimes can re-apply console modes on the next tick; enforce twice.
|
||||
const later = () => {
|
||||
enforce()
|
||||
setImmediate(enforce)
|
||||
}
|
||||
|
||||
let wrapped: ((mode: boolean) => unknown) | undefined
|
||||
|
||||
if (typeof original === "function") {
|
||||
wrapped = (mode: boolean) => {
|
||||
const result = original.call(stdin, mode)
|
||||
later()
|
||||
return result
|
||||
}
|
||||
|
||||
stdin.setRawMode = wrapped
|
||||
}
|
||||
|
||||
// Ensure it's cleared immediately too (covers any earlier mode changes).
|
||||
later()
|
||||
|
||||
const interval = setInterval(enforce, 100)
|
||||
interval.unref()
|
||||
|
||||
let done = false
|
||||
unhook = () => {
|
||||
if (done) return
|
||||
done = true
|
||||
|
||||
clearInterval(interval)
|
||||
if (wrapped && stdin.setRawMode === wrapped) {
|
||||
stdin.setRawMode = original
|
||||
}
|
||||
|
||||
k32!.symbols.SetConsoleMode(handle, initial)
|
||||
unhook = undefined
|
||||
}
|
||||
|
||||
return unhook
|
||||
}
|
||||
@@ -571,25 +571,28 @@ export namespace MCP {
|
||||
const clientsSnapshot = await clients()
|
||||
const defaultTimeout = cfg.experimental?.mcp_timeout
|
||||
|
||||
for (const [clientName, client] of Object.entries(clientsSnapshot)) {
|
||||
// Only include tools from connected MCPs (skip disabled ones)
|
||||
if (s.status[clientName]?.status !== "connected") {
|
||||
continue
|
||||
}
|
||||
const connectedClients = Object.entries(clientsSnapshot).filter(
|
||||
([clientName]) => s.status[clientName]?.status === "connected",
|
||||
)
|
||||
|
||||
const toolsResult = await client.listTools().catch((e) => {
|
||||
log.error("failed to get tools", { clientName, error: e.message })
|
||||
const failedStatus = {
|
||||
status: "failed" as const,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
}
|
||||
s.status[clientName] = failedStatus
|
||||
delete s.clients[clientName]
|
||||
return undefined
|
||||
})
|
||||
if (!toolsResult) {
|
||||
continue
|
||||
}
|
||||
const toolsResults = await Promise.all(
|
||||
connectedClients.map(async ([clientName, client]) => {
|
||||
const toolsResult = await client.listTools().catch((e) => {
|
||||
log.error("failed to get tools", { clientName, error: e.message })
|
||||
const failedStatus = {
|
||||
status: "failed" as const,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
}
|
||||
s.status[clientName] = failedStatus
|
||||
delete s.clients[clientName]
|
||||
return undefined
|
||||
})
|
||||
return { clientName, client, toolsResult }
|
||||
}),
|
||||
)
|
||||
|
||||
for (const { clientName, client, toolsResult } of toolsResults) {
|
||||
if (!toolsResult) continue
|
||||
const mcpConfig = config[clientName]
|
||||
const entry = isMcpConfigured(mcpConfig) ? mcpConfig : undefined
|
||||
const timeout = entry?.timeout ?? defaultTimeout
|
||||
|
||||
@@ -64,7 +64,7 @@ export namespace ModelsDev {
|
||||
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
|
||||
options: z.record(z.string(), z.any()),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
provider: z.object({ npm: z.string() }).optional(),
|
||||
provider: z.object({ npm: z.string(), api: z.string() }).optional(),
|
||||
variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
||||
})
|
||||
export type Model = z.infer<typeof Model>
|
||||
|
||||
@@ -629,7 +629,7 @@ export namespace Provider {
|
||||
family: model.family,
|
||||
api: {
|
||||
id: model.id,
|
||||
url: provider.api!,
|
||||
url: model.provider?.api ?? provider.api!,
|
||||
npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
|
||||
},
|
||||
status: model.status ?? "active",
|
||||
@@ -781,7 +781,7 @@ export namespace Provider {
|
||||
existingModel?.api.npm ??
|
||||
modelsDev[providerID]?.npm ??
|
||||
"@ai-sdk/openai-compatible",
|
||||
url: provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
|
||||
url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
|
||||
},
|
||||
status: model.status ?? existingModel?.status ?? "active",
|
||||
name,
|
||||
|
||||
@@ -401,6 +401,8 @@ export namespace ProviderTransform {
|
||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/xai
|
||||
case "@ai-sdk/deepinfra":
|
||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra
|
||||
case "venice-ai-sdk-provider":
|
||||
// https://docs.venice.ai/overview/guides/reasoning-models#reasoning-effort
|
||||
case "@ai-sdk/openai-compatible":
|
||||
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ export namespace SessionCompaction {
|
||||
|
||||
const reserved =
|
||||
config.compaction?.reserved ?? Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model))
|
||||
const usable = input.model.limit.input ? input.model.limit.input - reserved : context - reserved
|
||||
const usable = input.model.limit.input
|
||||
? input.model.limit.input - reserved
|
||||
: context - ProviderTransform.maxOutputTokens(input.model)
|
||||
return count >= usable
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export namespace LLM {
|
||||
small?: boolean
|
||||
tools: Record<string, Tool>
|
||||
retries?: number
|
||||
toolChoice?: "auto" | "required" | "none"
|
||||
}
|
||||
|
||||
export type StreamOutput = StreamTextResult<ToolSet, unknown>
|
||||
@@ -205,6 +206,7 @@ export namespace LLM {
|
||||
providerOptions: ProviderTransform.providerOptions(input.model, params.options),
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
tools,
|
||||
toolChoice: input.toolChoice,
|
||||
maxOutputTokens,
|
||||
abortSignal: input.abort,
|
||||
headers: {
|
||||
|
||||
@@ -15,6 +15,13 @@ import type { Provider } from "@/provider/provider"
|
||||
export namespace MessageV2 {
|
||||
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
|
||||
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
|
||||
export const StructuredOutputError = NamedError.create(
|
||||
"StructuredOutputError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
retries: z.number(),
|
||||
}),
|
||||
)
|
||||
export const AuthError = NamedError.create(
|
||||
"ProviderAuthError",
|
||||
z.object({
|
||||
@@ -39,6 +46,29 @@ export namespace MessageV2 {
|
||||
z.object({ message: z.string(), responseBody: z.string().optional() }),
|
||||
)
|
||||
|
||||
export const OutputFormatText = z
|
||||
.object({
|
||||
type: z.literal("text"),
|
||||
})
|
||||
.meta({
|
||||
ref: "OutputFormatText",
|
||||
})
|
||||
|
||||
export const OutputFormatJsonSchema = z
|
||||
.object({
|
||||
type: z.literal("json_schema"),
|
||||
schema: z.record(z.string(), z.any()).meta({ ref: "JSONSchema" }),
|
||||
retryCount: z.number().int().min(0).default(2),
|
||||
})
|
||||
.meta({
|
||||
ref: "OutputFormatJsonSchema",
|
||||
})
|
||||
|
||||
export const Format = z.discriminatedUnion("type", [OutputFormatText, OutputFormatJsonSchema]).meta({
|
||||
ref: "OutputFormat",
|
||||
})
|
||||
export type OutputFormat = z.infer<typeof Format>
|
||||
|
||||
const PartBase = z.object({
|
||||
id: z.string(),
|
||||
sessionID: z.string(),
|
||||
@@ -313,6 +343,7 @@ export namespace MessageV2 {
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
}),
|
||||
format: Format.optional(),
|
||||
summary: z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
@@ -365,6 +396,7 @@ export namespace MessageV2 {
|
||||
NamedError.Unknown.Schema,
|
||||
OutputLengthError.Schema,
|
||||
AbortedError.Schema,
|
||||
StructuredOutputError.Schema,
|
||||
ContextOverflowError.Schema,
|
||||
APIError.Schema,
|
||||
])
|
||||
@@ -393,6 +425,7 @@ export namespace MessageV2 {
|
||||
write: z.number(),
|
||||
}),
|
||||
}),
|
||||
structured: z.any().optional(),
|
||||
variant: z.string().optional(),
|
||||
finish: z.string().optional(),
|
||||
}).meta({
|
||||
|
||||
@@ -50,6 +50,16 @@ import { Truncate } from "@/tool/truncation"
|
||||
// @ts-ignore
|
||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
|
||||
const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
|
||||
|
||||
IMPORTANT:
|
||||
- You MUST call this tool exactly once at the end of your response
|
||||
- The input must be valid JSON matching the required schema
|
||||
- Complete all necessary research and tool calls BEFORE calling this tool
|
||||
- This tool provides your final answer - no further actions are taken after calling it`
|
||||
|
||||
const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
|
||||
|
||||
export namespace SessionPrompt {
|
||||
const log = Log.create({ service: "session.prompt" })
|
||||
|
||||
@@ -96,6 +106,7 @@ export namespace SessionPrompt {
|
||||
.describe(
|
||||
"@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
|
||||
),
|
||||
format: MessageV2.Format.optional(),
|
||||
system: z.string().optional(),
|
||||
variant: z.string().optional(),
|
||||
parts: z.array(
|
||||
@@ -276,6 +287,11 @@ export namespace SessionPrompt {
|
||||
|
||||
using _ = defer(() => cancel(sessionID))
|
||||
|
||||
// Structured output state
|
||||
// Note: On session resumption, state is reset but outputFormat is preserved
|
||||
// on the user message and will be retrieved from lastUser below
|
||||
let structuredOutput: unknown | undefined
|
||||
|
||||
let step = 0
|
||||
const session = await Session.get(sessionID)
|
||||
while (true) {
|
||||
@@ -589,6 +605,16 @@ export namespace SessionPrompt {
|
||||
messages: msgs,
|
||||
})
|
||||
|
||||
// Inject StructuredOutput tool if JSON schema mode enabled
|
||||
if (lastUser.format?.type === "json_schema") {
|
||||
tools["StructuredOutput"] = createStructuredOutputTool({
|
||||
schema: lastUser.format.schema,
|
||||
onSuccess(output) {
|
||||
structuredOutput = output
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (step === 1) {
|
||||
SessionSummary.summarize({
|
||||
sessionID: sessionID,
|
||||
@@ -619,12 +645,19 @@ export namespace SessionPrompt {
|
||||
|
||||
await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: sessionMessages })
|
||||
|
||||
// Build system prompt, adding structured output instruction if needed
|
||||
const system = [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())]
|
||||
const format = lastUser.format ?? { type: "text" }
|
||||
if (format.type === "json_schema") {
|
||||
system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
|
||||
}
|
||||
|
||||
const result = await processor.process({
|
||||
user: lastUser,
|
||||
agent,
|
||||
abort,
|
||||
sessionID,
|
||||
system: [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())],
|
||||
system,
|
||||
messages: [
|
||||
...MessageV2.toModelMessages(sessionMessages, model),
|
||||
...(isLastStep
|
||||
@@ -638,7 +671,33 @@ export namespace SessionPrompt {
|
||||
],
|
||||
tools,
|
||||
model,
|
||||
toolChoice: format.type === "json_schema" ? "required" : undefined,
|
||||
})
|
||||
|
||||
// If structured output was captured, save it and exit immediately
|
||||
// This takes priority because the StructuredOutput tool was called successfully
|
||||
if (structuredOutput !== undefined) {
|
||||
processor.message.structured = structuredOutput
|
||||
processor.message.finish = processor.message.finish ?? "stop"
|
||||
await Session.updateMessage(processor.message)
|
||||
break
|
||||
}
|
||||
|
||||
// Check if model finished (finish reason is not "tool-calls" or "unknown")
|
||||
const modelFinished = processor.message.finish && !["tool-calls", "unknown"].includes(processor.message.finish)
|
||||
|
||||
if (modelFinished && !processor.message.error) {
|
||||
if (format.type === "json_schema") {
|
||||
// Model stopped without calling StructuredOutput tool
|
||||
processor.message.error = new MessageV2.StructuredOutputError({
|
||||
message: "Model did not produce structured output",
|
||||
retries: 0,
|
||||
}).toObject()
|
||||
await Session.updateMessage(processor.message)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (result === "stop") break
|
||||
if (result === "compact") {
|
||||
await SessionCompaction.create({
|
||||
@@ -669,7 +728,8 @@ export namespace SessionPrompt {
|
||||
return Provider.defaultModel()
|
||||
}
|
||||
|
||||
async function resolveTools(input: {
|
||||
/** @internal Exported for testing */
|
||||
export async function resolveTools(input: {
|
||||
agent: Agent.Info
|
||||
model: Provider.Model
|
||||
session: Session.Info
|
||||
@@ -849,6 +909,36 @@ export namespace SessionPrompt {
|
||||
return tools
|
||||
}
|
||||
|
||||
/** @internal Exported for testing */
|
||||
export function createStructuredOutputTool(input: {
|
||||
schema: Record<string, any>
|
||||
onSuccess: (output: unknown) => void
|
||||
}): AITool {
|
||||
// Remove $schema property if present (not needed for tool input)
|
||||
const { $schema, ...toolSchema } = input.schema
|
||||
|
||||
return tool({
|
||||
id: "StructuredOutput" as any,
|
||||
description: STRUCTURED_OUTPUT_DESCRIPTION,
|
||||
inputSchema: jsonSchema(toolSchema as any),
|
||||
async execute(args) {
|
||||
// AI SDK validates args against inputSchema before calling execute()
|
||||
input.onSuccess(args)
|
||||
return {
|
||||
output: "Structured output captured successfully.",
|
||||
title: "Structured Output",
|
||||
metadata: { valid: true },
|
||||
}
|
||||
},
|
||||
toModelOutput(result) {
|
||||
return {
|
||||
type: "text",
|
||||
value: result.output,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function createUserMessage(input: PromptInput) {
|
||||
const agent = await Agent.get(input.agent ?? (await Agent.defaultAgent()))
|
||||
|
||||
@@ -870,6 +960,7 @@ export namespace SessionPrompt {
|
||||
agent: agent.name,
|
||||
model,
|
||||
system: input.system,
|
||||
format: input.format,
|
||||
variant,
|
||||
}
|
||||
using _ = defer(() => InstructionPrompt.clear(info.id))
|
||||
@@ -1022,9 +1113,9 @@ export namespace SessionPrompt {
|
||||
}
|
||||
}
|
||||
}
|
||||
offset = Math.max(start - 1, 0)
|
||||
offset = Math.max(start, 1)
|
||||
if (end) {
|
||||
limit = end - offset
|
||||
limit = end - (offset - 1)
|
||||
}
|
||||
}
|
||||
const args = { filePath: filepath, offset, limit }
|
||||
|
||||
@@ -38,7 +38,7 @@ export const EditTool = Tool.define("edit", {
|
||||
}
|
||||
|
||||
if (params.oldString === params.newString) {
|
||||
throw new Error("oldString and newString must be different")
|
||||
throw new Error("No changes to apply: oldString and newString are identical.")
|
||||
}
|
||||
|
||||
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
||||
@@ -617,7 +617,7 @@ export function trimDiff(diff: string): string {
|
||||
|
||||
export function replace(content: string, oldString: string, newString: string, replaceAll = false): string {
|
||||
if (oldString === newString) {
|
||||
throw new Error("oldString and newString must be different")
|
||||
throw new Error("No changes to apply: oldString and newString are identical.")
|
||||
}
|
||||
|
||||
let notFound = true
|
||||
@@ -647,9 +647,9 @@ export function replace(content: string, oldString: string, newString: string, r
|
||||
}
|
||||
|
||||
if (notFound) {
|
||||
throw new Error("oldString not found in content")
|
||||
throw new Error(
|
||||
"Could not find oldString in the file. It must match exactly, including whitespace, indentation, and line endings.",
|
||||
)
|
||||
}
|
||||
throw new Error(
|
||||
"Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match.",
|
||||
)
|
||||
throw new Error("Found multiple matches for oldString. Provide more surrounding context to make the match unique.")
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ Performs exact string replacements in files.
|
||||
|
||||
Usage:
|
||||
- You must use your `Read` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
|
||||
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the oldString or newString.
|
||||
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + colon + space (e.g., `1: `). Everything after that space is the actual file content to match. Never include any part of the line number prefix in the oldString or newString.
|
||||
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
||||
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
||||
- The edit will FAIL if `oldString` is not found in the file with an error "oldString not found in content".
|
||||
- The edit will FAIL if `oldString` is found multiple times in the file with an error "oldString found multiple times and requires more code context to uniquely identify the intended match". Either provide a larger string with more surrounding context to make it unique or use `replaceAll` to change every instance of `oldString`.
|
||||
- The edit will FAIL if `oldString` is found multiple times in the file with an error "Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match." Either provide a larger string with more surrounding context to make it unique or use `replaceAll` to change every instance of `oldString`.
|
||||
- Use `replaceAll` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
|
||||
|
||||
@@ -62,7 +62,9 @@ export const GlobTool = Tool.define("glob", {
|
||||
output.push(...files.map((f) => f.path))
|
||||
if (truncated) {
|
||||
output.push("")
|
||||
output.push("(Results are truncated. Consider using a more specific path or pattern.)")
|
||||
output.push(
|
||||
`(Results are truncated: showing first ${limit} results. Consider using a more specific path or pattern.)`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ export const GrepTool = Tool.define("grep", {
|
||||
}
|
||||
}
|
||||
|
||||
const outputLines = [`Found ${finalMatches.length} matches`]
|
||||
const totalMatches = matches.length
|
||||
const outputLines = [`Found ${totalMatches} matches${truncated ? ` (showing first ${limit})` : ""}`]
|
||||
|
||||
let currentFile = ""
|
||||
for (const match of finalMatches) {
|
||||
@@ -127,7 +128,9 @@ export const GrepTool = Tool.define("grep", {
|
||||
|
||||
if (truncated) {
|
||||
outputLines.push("")
|
||||
outputLines.push("(Results are truncated. Consider using a more specific path or pattern.)")
|
||||
outputLines.push(
|
||||
`(Results truncated: showing ${limit} of ${totalMatches} matches (${totalMatches - limit} hidden). Consider using a more specific path or pattern.)`,
|
||||
)
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
@@ -138,7 +141,7 @@ export const GrepTool = Tool.define("grep", {
|
||||
return {
|
||||
title: params.pattern,
|
||||
metadata: {
|
||||
matches: finalMatches.length,
|
||||
matches: totalMatches,
|
||||
truncated,
|
||||
},
|
||||
output: outputLines.join("\n"),
|
||||
|
||||
@@ -17,19 +17,26 @@ const MAX_BYTES = 50 * 1024
|
||||
export const ReadTool = Tool.define("read", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The path to the file to read"),
|
||||
offset: z.coerce.number().describe("The line number to start reading from (0-based)").optional(),
|
||||
limit: z.coerce.number().describe("The number of lines to read (defaults to 2000)").optional(),
|
||||
filePath: z.string().describe("The absolute path to the file or directory to read"),
|
||||
offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(),
|
||||
limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
if (params.offset !== undefined && params.offset < 1) {
|
||||
throw new Error("offset must be greater than or equal to 1")
|
||||
}
|
||||
let filepath = params.filePath
|
||||
if (!path.isAbsolute(filepath)) {
|
||||
filepath = path.resolve(Instance.directory, filepath)
|
||||
}
|
||||
const title = path.relative(Instance.worktree, filepath)
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
const stat = await file.stat().catch(() => undefined)
|
||||
|
||||
await assertExternalDirectory(ctx, filepath, {
|
||||
bypass: Boolean(ctx.extra?.["bypassCwdCheck"]),
|
||||
kind: stat?.isDirectory() ? "directory" : "file",
|
||||
})
|
||||
|
||||
await ctx.ask({
|
||||
@@ -39,8 +46,7 @@ export const ReadTool = Tool.define("read", {
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
if (!(await file.exists())) {
|
||||
if (!stat) {
|
||||
const dir = path.dirname(filepath)
|
||||
const base = path.basename(filepath)
|
||||
|
||||
@@ -60,6 +66,48 @@ export const ReadTool = Tool.define("read", {
|
||||
throw new Error(`File not found: ${filepath}`)
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const dirents = await fs.promises.readdir(filepath, { withFileTypes: true })
|
||||
const entries = await Promise.all(
|
||||
dirents.map(async (dirent) => {
|
||||
if (dirent.isDirectory()) return dirent.name + "/"
|
||||
if (dirent.isSymbolicLink()) {
|
||||
const target = await fs.promises.stat(path.join(filepath, dirent.name)).catch(() => undefined)
|
||||
if (target?.isDirectory()) return dirent.name + "/"
|
||||
}
|
||||
return dirent.name
|
||||
}),
|
||||
)
|
||||
entries.sort((a, b) => a.localeCompare(b))
|
||||
|
||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||
const offset = params.offset ?? 1
|
||||
const start = offset - 1
|
||||
const sliced = entries.slice(start, start + limit)
|
||||
const truncated = start + sliced.length < entries.length
|
||||
|
||||
const output = [
|
||||
`<path>${filepath}</path>`,
|
||||
`<type>directory</type>`,
|
||||
`<entries>`,
|
||||
sliced.join("\n"),
|
||||
truncated
|
||||
? `\n(Showing ${sliced.length} of ${entries.length} entries. Use 'offset' parameter to read beyond entry ${offset + sliced.length})`
|
||||
: `\n(${entries.length} entries)`,
|
||||
`</entries>`,
|
||||
].join("\n")
|
||||
|
||||
return {
|
||||
title,
|
||||
output,
|
||||
metadata: {
|
||||
preview: sliced.slice(0, 20).join("\n"),
|
||||
truncated,
|
||||
loaded: [] as string[],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const instructions = await InstructionPrompt.resolve(ctx.messages, filepath, ctx.messageID)
|
||||
|
||||
// Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files)
|
||||
@@ -75,7 +123,7 @@ export const ReadTool = Tool.define("read", {
|
||||
metadata: {
|
||||
preview: msg,
|
||||
truncated: false,
|
||||
...(instructions.length > 0 && { loaded: instructions.map((i) => i.filepath) }),
|
||||
loaded: instructions.map((i) => i.filepath),
|
||||
},
|
||||
attachments: [
|
||||
{
|
||||
@@ -94,13 +142,15 @@ export const ReadTool = Tool.define("read", {
|
||||
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
|
||||
|
||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||
const offset = params.offset || 0
|
||||
const offset = params.offset ?? 1
|
||||
const start = offset - 1
|
||||
const lines = await file.text().then((text) => text.split("\n"))
|
||||
if (start >= lines.length) throw new Error(`Offset ${offset} is out of range for this file (${lines.length} lines)`)
|
||||
|
||||
const raw: string[] = []
|
||||
let bytes = 0
|
||||
let truncatedByBytes = false
|
||||
for (let i = offset; i < Math.min(lines.length, offset + limit); i++) {
|
||||
for (let i = start; i < Math.min(lines.length, start + limit); i++) {
|
||||
const line = lines[i].length > MAX_LINE_LENGTH ? lines[i].substring(0, MAX_LINE_LENGTH) + "..." : lines[i]
|
||||
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
|
||||
if (bytes + size > MAX_BYTES) {
|
||||
@@ -112,15 +162,15 @@ export const ReadTool = Tool.define("read", {
|
||||
}
|
||||
|
||||
const content = raw.map((line, index) => {
|
||||
return `${(index + offset + 1).toString().padStart(5, "0")}| ${line}`
|
||||
return `${index + offset}: ${line}`
|
||||
})
|
||||
const preview = raw.slice(0, 20).join("\n")
|
||||
|
||||
let output = "<file>\n"
|
||||
let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>"].join("\n")
|
||||
output += content.join("\n")
|
||||
|
||||
const totalLines = lines.length
|
||||
const lastReadLine = offset + raw.length
|
||||
const lastReadLine = offset + raw.length - 1
|
||||
const hasMoreLines = totalLines > lastReadLine
|
||||
const truncated = hasMoreLines || truncatedByBytes
|
||||
|
||||
@@ -131,7 +181,7 @@ export const ReadTool = Tool.define("read", {
|
||||
} else {
|
||||
output += `\n\n(End of file - total ${totalLines} lines)`
|
||||
}
|
||||
output += "\n</file>"
|
||||
output += "\n</content>"
|
||||
|
||||
// just warms the lsp client
|
||||
LSP.touchFile(filepath, false)
|
||||
@@ -147,7 +197,7 @@ export const ReadTool = Tool.define("read", {
|
||||
metadata: {
|
||||
preview,
|
||||
truncated,
|
||||
...(instructions.length > 0 && { loaded: instructions.map((i) => i.filepath) }),
|
||||
loaded: instructions.map((i) => i.filepath),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user