Compare commits

..

3 Commits

Author SHA1 Message Date
David Hill
5d419a0211 tweak(ui): expand question dock toggle area 2026-02-27 21:30:49 +00:00
David Hill
8b168981aa tweak(ui): active state on type your own answer 2026-02-27 18:50:50 +00:00
David Hill
724dd665ec tweak(ui): collapse questions 2026-02-27 18:47:53 +00:00
174 changed files with 1999 additions and 8368 deletions

View File

@@ -27,7 +27,6 @@
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |

View File

@@ -304,8 +304,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@opentui/core": "0.1.84",
"@opentui/solid": "0.1.84",
"@opentui/core": "0.1.81",
"@opentui/solid": "0.1.81",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -342,7 +342,6 @@
"ulid": "catalog:",
"vscode-jsonrpc": "8.2.1",
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
@@ -365,7 +364,6 @@
"@types/bun": "catalog:",
"@types/mime-types": "3.0.1",
"@types/turndown": "5.0.5",
"@types/which": "3.0.4",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "1.0.0-beta.12-a5629fb",
@@ -425,17 +423,17 @@
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
"@storybook/addon-a11y": "^10.2.13",
"@storybook/addon-docs": "^10.2.13",
"@storybook/addon-links": "^10.2.13",
"@storybook/addon-onboarding": "^10.2.13",
"@storybook/addon-vitest": "^10.2.13",
"@storybook/addon-a11y": "^10.2.10",
"@storybook/addon-docs": "^10.2.10",
"@storybook/addon-links": "^10.2.10",
"@storybook/addon-onboarding": "^10.2.10",
"@storybook/addon-vitest": "^10.2.10",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@types/react": "18.0.25",
"react": "18.2.0",
"solid-js": "catalog:",
"storybook": "^10.2.13",
"storybook": "^10.2.10",
"storybook-solidjs-vite": "^10.0.9",
"typescript": "catalog:",
"vite": "catalog:",
@@ -547,7 +545,7 @@
"@kobalte/core": "0.13.11",
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.1.0-beta.18",
"@pierre/diffs": "1.1.0-beta.13",
"@playwright/test": "1.51.0",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "0.29.4",
@@ -1343,21 +1341,21 @@
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.84", "", { "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.84", "@opentui/core-darwin-x64": "0.1.84", "@opentui/core-linux-arm64": "0.1.84", "@opentui/core-linux-x64": "0.1.84", "@opentui/core-win32-arm64": "0.1.84", "@opentui/core-win32-x64": "0.1.84", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-UdPD/sldUSiIu588l45lQq6q09zLW8L4GOgkA4E3fyExIlIgOrpIhFSKqZwbVCbe53dQOybHVUdKob64Xxhm4w=="],
"@opentui/core": ["@opentui/core@0.1.81", "", { "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.81", "@opentui/core-darwin-x64": "0.1.81", "@opentui/core-linux-arm64": "0.1.81", "@opentui/core-linux-x64": "0.1.81", "@opentui/core-win32-arm64": "0.1.81", "@opentui/core-win32-x64": "0.1.81", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ooFjkkQ80DDC4X5eLvH8dBcLAtWwGp9RTaWsaeWet3GOv4N0SDcN8mi1XGhYnUlTuxmofby5eQrPegjtWHODlA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.84", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpLdRYd2zLJP7IcWHu1SYXFet6ZfOzTUW12iaexlUL4DkAU+P5mO1v/OdLlZrmXVj/FD3BqDM5SUIXNE0+A46Q=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.81", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I3Ry5JbkSQXs2g1me8yYr0v3CUcIIfLHzbWz9WMFla8kQDSa+HOr8IpZbqZDeIFgOVzolAXBmZhg0VJI3bZ7MA=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.84", "", { "os": "darwin", "cpu": "x64" }, "sha512-eEFzEdo/WVVjbpwY2pnQBxkpqsWyGLHNv3kFc0RHvZ3ARIW2qb/Jqo9O9uUbR6th7H77V1NRJ0a6gvby48Oofg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.81", "", { "os": "darwin", "cpu": "x64" }, "sha512-CrtNKu41D6+bOQdUOmDX4Q3hTL6p+sT55wugPzbDq7cdqFZabCeguBAyOlvRl2g2aJ93kmOWW6MXG0bPPklEFg=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.84", "", { "os": "linux", "cpu": "arm64" }, "sha512-AYfG9iLWavfVYZYbNfEZkCimqk8Uq8EzvHKwRkbP24XVZO7eALztZ6PWl2nJqDrw+rYBVSJg0uS6ON+M7NxyBA=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.81", "", { "os": "linux", "cpu": "arm64" }, "sha512-FJw9zmJop9WiMvtT07nSrfBLPLqskxL6xfV3GNft0mSYV+C3hdJ0qkiczGSHUX/6V7fmouM84RWwmY53Rb6hYQ=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.84", "", { "os": "linux", "cpu": "x64" }, "sha512-pLi3a3rqs1BMeCqj1GZm/Qi3zdwilQxaPEI/IeWc5qdzWInpGPHs3uadPJBAllEGRzvTmStMWI6iY6bdc98bng=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.81", "", { "os": "linux", "cpu": "x64" }, "sha512-Rj2AFIiuWI0BEMIvh/Jeuxty9Gp5ZhLuQU7ZHJJhojKo/mpBpMs9X+5kwZPZya/tyR8uVDAVyB6AOLkhdRW5lw=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.84", "", { "os": "win32", "cpu": "arm64" }, "sha512-dcQruw9VyYACxukoB+iv8SskQxl5MMeY73uqvQ17dsnM2sfukT+c2MSgbgYImCKKixtlGXLcb5Weo36lQqI5AQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.81", "", { "os": "win32", "cpu": "arm64" }, "sha512-AiZB+mZ1cVr8plAPrPT98e3kw6D0OdOSe2CQYLgJRbfRlPqq3jl26lHPzDb3ZO2OR0oVGRPJvXraus939mvoiQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.84", "", { "os": "win32", "cpu": "x64" }, "sha512-KiCaIVtVnZYQza+vAW8TDbUMxBgd2thvjNOIJ03NmjMiiRnfmO55wA3Zh9vNuQ5PJEYI0awzXYFAZ7kaMsDQxA=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.81", "", { "os": "win32", "cpu": "x64" }, "sha512-l8R2Ni1CR4eHi3DTmSkEL/EjHAtOZ/sndYs3VVw+Ej2esL3Mf0W7qSO5S0YNBanz2VXZhbkmM6ERm9keH8RD3w=="],
"@opentui/solid": ["@opentui/solid@0.1.84", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.84", "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-5koIQ9Nk5CU6/uVxjjWJ7jtFeSNkczbZoEzHLdUK/T2bqHSyuPSFBeliQ1hfOwrN2G5SKFqIB3U5+1EcHxDCCA=="],
"@opentui/solid": ["@opentui/solid@0.1.81", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.81", "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-QRjS0wPuIhBRdY8tpG3yprCM4ZnOxWWHTuaZ4hhia2wFZygf7Ome6EuZnLXmtuOQjkjCwu0if8Yik6toc6QylA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1471,9 +1469,7 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.18", "", { "dependencies": { "@pierre/theme": "0.0.22", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-7ZF3YD9fxdbYsPnltz5cUqHacN7ztp8RX/fJLxwv8wIEORpP4+7dHz1h/qx3o4EW2xUrIhmbM8ImywLasB787Q=="],
"@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="],
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
@@ -1805,25 +1801,25 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-zuR1n1xgWoieEnr6E5xdTR40BI61IBQahgmsRpTvqRffL3mxAs5aFoORDmA5pZWI2LE9URdMkY85h218ijuLiw=="],
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="],
"@storybook/addon-docs": ["@storybook/addon-docs@10.2.13", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.13", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.13", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-puMxpJbt/CuodLIbKDxWrW1ZgADYomfNHWEKp2d2l2eJjp17rADx0h3PABuNbX+YHbJwYcDdqluSnQwMysFEOA=="],
"@storybook/addon-docs": ["@storybook/addon-docs@10.2.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="],
"@storybook/addon-links": ["@storybook/addon-links@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" }, "optionalPeers": ["react"] }, "sha512-8wnAomGiHaUpNIc+lOzmazTrebxa64z9rihIbM/Q59vkOImHQNkGp7KP/qNgJA4GPTFtu8+fLjX2qCoAQPM0jQ=="],
"@storybook/addon-links": ["@storybook/addon-links@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" }, "optionalPeers": ["react"] }, "sha512-oo9Xx4/2OVJtptXKpqH4ySri7ZuBdiSOXlZVGejEfLa0Jeajlh/KIlREpGvzPPOqUVT7dSddWzBjJmJUyQC3ew=="],
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.13", "", { "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-kw2GgIY67UR8YXKfuVS0k+mfWL1joNQHeSe5DlDL4+7qbgp9zfV6cRJ199BMdfRAQNMzQoxHgRUcAMAqs3Rkpw=="],
"@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.10", "", { "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-DkzZQTXHp99SpHMIQ5plbbHcs4EWVzWhLXlW+icA8sBlKo5Bwj540YcOApKbqB0m/OzWprsznwN7Kv4vfvHu4w=="],
"@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.13", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-qQD3xzxc31cQHS0loF9enGWi5sgA6zBTbaJ0HuSUNGO81iwfLSALh8L/1vrD5NfN2vlBeUMTsgv3EkCuLfe9EQ=="],
"@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.10", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-U2oHw+Ar+Xd06wDTB74VlujhIIW89OHThpJjwgqgM6NWrOC/XLllJ53ILFDyREBkMwpBD7gJQIoQpLEcKBIEhw=="],
"@storybook/builder-vite": ["@storybook/builder-vite@10.2.10", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="],
"@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.13", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.13", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-gUCR7PmyrWYj3dIJJgxOm25dcXFolPIUPmug3z90Aaon7YPXw3pUN+dNDx8KqDJqRK1WDIB4HaefgYZIm5V7iA=="],
"@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="],
"@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="],
"@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="],
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.13", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" } }, "sha512-ZSduoB10qTI0V9z22qeULmQLsvTs8d/rtJi03qbVxpPiMRor86AmyAaBrfhGGmWBxWQZpOGQQm6yIT2YLoPs7w=="],
"@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="],
"@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="],
@@ -2037,8 +2033,6 @@
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
"@types/which": ["@types/which@3.0.4", "", {}, "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
@@ -3017,7 +3011,7 @@
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
@@ -3901,7 +3895,7 @@
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
"storybook": ["storybook@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-heMfJjOfbHvL+wlCAwFZlSxcakyJ5yQDam6e9k2RRArB1veJhRnsjO6lO1hOXjJYrqxfHA/ldIugbBVlCDqfvQ=="],
"storybook": ["storybook@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg=="],
"storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="],
@@ -4219,7 +4213,7 @@
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -4725,8 +4719,6 @@
"@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
"@storybook/builder-vite/@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="],
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
@@ -4821,8 +4813,6 @@
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
@@ -5395,8 +5385,6 @@
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"editorconfig/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],

View File

@@ -8,7 +8,6 @@ import type { Context as GitHubContext } from "@actions/github/lib/context"
import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { spawn } from "node:child_process"
import { setTimeout as sleep } from "node:timers/promises"
type GitHubAuthor = {
login: string
@@ -282,7 +281,7 @@ async function assertOpencodeConnected() {
connected = true
break
} catch (e) {}
await sleep(300)
await Bun.sleep(300)
} while (retry++ < 30)
if (!connected) {

View File

@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-zBRw0PJxQPL2e+u23BkG5MYHeki0nF6Ti+kzPFXYsrI=",
"aarch64-linux": "sha256-95mgnJwauU62Ofe4mah+BqRNgDWd0AEMjFNikRS+SMY=",
"aarch64-darwin": "sha256-1OP2clDYn8os5E3po9mdIvZbUIAM2DmRzOs3S7D6Um8=",
"x86_64-darwin": "sha256-VJjlFZ+EUTu1gSfdX9J4mCtTU3qTsZkKSqoAZA4RmwE="
"x86_64-linux": "sha256-dZoLhWe4smBsOF7WczMySLXSAB1YRO1vfhiOCL1rBf0=",
"aarch64-linux": "sha256-J7nIz1xuVZEHun5WRZkYRySz29B0A8g5g0RRxnIWTYU=",
"aarch64-darwin": "sha256-R2PuhX+EjUBuLE8MF0G0fcUwNaU+5n6V6uVeK89ulzw=",
"x86_64-darwin": "sha256-Bvzfz9TsTpYriZNLSLgpNcNb+BgtkgpjoWqdOtF2IBg="
}
}

View File

@@ -9,7 +9,6 @@
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop tauri dev",
"dev:web": "bun --cwd packages/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"typecheck": "bun turbo typecheck",
"prepare": "husky",
"random": "echo 'Random script'",
@@ -36,7 +35,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/diffs": "1.1.0-beta.18",
"@pierre/diffs": "1.1.0-beta.13",
"@solid-primitives/storage": "4.3.3",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",

View File

@@ -1,51 +0,0 @@
import type { Platform } from "@/context/platform"
import { getRelativeTime } from "@/utils/time"
const REPO = "anomalyco/opencode"
const GITHUB_API_URL = `https://api.github.com/repos/${REPO}/releases`
const PER_PAGE = 30
const CACHE_TTL = 1000 * 60 * 30
const CACHE_KEY = "opencode.releases"
type Release = {
tag: string
body: string
date: string
}
function loadCache() {
const raw = localStorage.getItem(CACHE_KEY)
return raw ? JSON.parse(raw) : null
}
function saveCache(data: { releases: Release[]; timestamp: number }) {
localStorage.setItem(CACHE_KEY, JSON.stringify(data))
}
export async function fetchReleases(platform: Platform): Promise<{ releases: Release[] }> {
const now = Date.now()
const cached = loadCache()
if (cached && now - cached.timestamp < CACHE_TTL) {
return { releases: cached.releases }
}
const fetcher = platform.fetch ?? fetch
const res = await fetcher(`${GITHUB_API_URL}?per_page=${PER_PAGE}`, {
headers: { Accept: "application/vnd.github.v3+json" },
}).then((r) => (r.ok ? r.json() : Promise.reject(new Error("Failed to load"))))
const releases = (Array.isArray(res) ? res : []).map((r) => ({
tag: r.tag_name ?? "Unknown",
body: (r.body ?? "")
.replace(/#(\d+)/g, (_: string, id: string) => `[#${id}](https://github.com/anomalyco/opencode/pull/${id})`)
.replace(/@([a-zA-Z0-9_-]+)/g, (_: string, u: string) => `[@${u}](https://github.com/${u})`),
date: r.published_at ? getRelativeTime(r.published_at) : "",
}))
saveCache({ releases, timestamp: now })
return { releases }
}
export type { Release }

View File

@@ -1,150 +0,0 @@
.dialog-changelog {
min-height: 500px;
display: flex;
flex-direction: column;
}
.dialog-changelog [data-slot="dialog-body"] {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
}
.dialog-changelog-list {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
}
.dialog-changelog-list [data-slot="list-scroll"] {
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--border-weak-base) transparent;
}
.dialog-changelog-list [data-slot="list-scroll"]::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.dialog-changelog-list [data-slot="list-scroll"]::-webkit-scrollbar-track {
background: transparent;
border-radius: 5px;
}
.dialog-changelog-list [data-slot="list-scroll"]::-webkit-scrollbar-thumb {
background: var(--border-weak-base);
border-radius: 5px;
border: 3px solid transparent;
background-clip: padding-box;
}
.dialog-changelog-list [data-slot="list-scroll"]::-webkit-scrollbar-thumb:hover {
background: var(--border-weak-base);
}
.dialog-changelog-header {
padding: 8px 12px 8px 8px;
display: flex;
align-items: baseline;
gap: 8px;
position: sticky;
top: 0;
z-index: 10;
background: var(--surface-raised-stronger-non-alpha);
}
.dialog-changelog-header::after {
content: "";
position: absolute;
top: 100%;
left: 0;
right: 0;
height: 16px;
background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent);
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.dialog-changelog-header[data-stuck="true"]::after {
opacity: 1;
}
.dialog-changelog-version {
font-size: 20px;
font-weight: 600;
}
.dialog-changelog-date {
font-size: 12px;
font-weight: 400;
color: var(--text-weak);
}
.dialog-changelog-list [data-slot="list-item"] {
margin-bottom: 32px;
padding: 0;
border: none;
background: transparent;
cursor: default;
display: block;
text-align: left;
}
.dialog-changelog-list [data-slot="list-item"]:hover {
background: transparent;
}
.dialog-changelog-list [data-slot="list-item"]:focus {
outline: none;
}
.dialog-changelog-list [data-slot="list-item"]:focus-visible {
outline: 2px solid var(--focus-base);
outline-offset: 2px;
}
.dialog-changelog-content {
padding: 0 8px 24px;
}
.dialog-changelog-markdown h2 {
border-bottom: 1px solid var(--border-weak-base);
padding-bottom: 4px;
margin: 32px 0 12px 0;
font-size: 14px;
font-weight: 500;
text-transform: capitalize;
}
.dialog-changelog-markdown h2:first-child {
margin-top: 16px;
}
.dialog-changelog-markdown a.external-link {
color: var(--text-interactive-base);
font-weight: 500;
}
.dialog-changelog-markdown a.external-link[href^="https://github.com/anomalyco/opencode/pull/"],
.dialog-changelog-markdown a.external-link[href^="https://github.com/anomalyco/opencode/issues/"],
.dialog-changelog-markdown a.external-link[href^="https://github.com/"]
{
border-radius: 3px;
padding: 0 2px;
}
.dialog-changelog-markdown a.external-link[href^="https://github.com/anomalyco/opencode/pull/"]:hover,
.dialog-changelog-markdown a.external-link[href^="https://github.com/anomalyco/opencode/issues/"]:hover,
.dialog-changelog-markdown a.external-link[href^="https://github.com/"]:hover
{
background: var(--surface-weak-base);
}

View File

@@ -1,47 +0,0 @@
import { createSignal, onMount, Show } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { fetchReleases, type Release } from "@/api/releases"
import { ReleaseList } from "@/components/release-list"
export function DialogChangelog() {
const dialog = useDialog()
const language = useLanguage()
const platform = usePlatform()
const [releases, setReleases] = createSignal<Release[]>([])
const [loading, setLoading] = createSignal(true)
const [error, setError] = createSignal<string | undefined>(undefined)
async function loadReleases() {
const result = await fetchReleases(platform)
.then((r) => ({ releases: r.releases, error: undefined }))
.catch((e) => ({ releases: [], error: e instanceof Error ? e.message : "Failed to load changelog" }))
setReleases(result.releases)
setError(result.error)
setLoading(false)
}
onMount(() => loadReleases())
return (
<Dialog size="x-large" transition title="Changelog">
<div class="flex-1 min-h-0 flex flex-col">
<Show when={loading()}>
<p class="text-text-weak p-6">{language.t("common.loading")}...</p>
</Show>
<Show when={error()}>
<p class="text-text-weak p-6">{error()}</p>
</Show>
<Show when={!loading() && !error() && releases().length === 0}>
<p class="text-text-weak p-6">{language.t("common.noReleasesFound")}</p>
</Show>
<Show when={!loading() && !error() && releases().length > 0}>
<ReleaseList releases={releases()} hasMore={false} loadingMore={false} onLoadMore={() => {}} />
</Show>
</div>
</Dialog>
)
}

View File

@@ -210,52 +210,17 @@ function useDirectorySearch(args: {
const cap = 12
const branch = 4
let paths = [scopedInput.directory]
// Optimization: Try to construct the full exact path first
// This avoids triggering Instance initialization for each segment
const potentialExactPaths: string[] = []
for (const p of paths) {
let currentPath = p
for (const part of head) {
if (part === "..") {
currentPath = parentOf(currentPath)
} else {
currentPath = joinPath(currentPath, part)
}
for (const part of head) {
if (!active()) return []
if (part === "..") {
paths = paths.map(parentOf)
continue
}
potentialExactPaths.push(currentPath)
}
// Verify if any of the exact paths exist (only ONE API call per potential path)
const exactPathResults = await Promise.all(
potentialExactPaths.map(async (exactPath) => {
try {
await dirs(exactPath)
return exactPath
} catch {
return null
}
})
)
const validExactPaths = exactPathResults.filter((p): p is string => p !== null)
if (validExactPaths.length > 0) {
// Exact path exists, use it directly
paths = validExactPaths
} else {
// Exact path doesn't exist, fall back to segment-by-segment fuzzy matching
paths = [scopedInput.directory]
for (const part of head) {
if (!active()) return []
if (part === "..") {
paths = paths.map(parentOf)
continue
}
const next = (await Promise.all(paths.map((p) => match(p, part, branch)))).flat()
if (!active()) return []
paths = Array.from(new Set(next)).slice(0, cap)
if (paths.length === 0) return [] as string[]
}
const next = (await Promise.all(paths.map((p) => match(p, part, branch)))).flat()
if (!active()) return []
paths = Array.from(new Set(next)).slice(0, cap)
if (paths.length === 0) return [] as string[]
}
const out = (await Promise.all(paths.map((p) => match(p, tail, 50)))).flat()

View File

@@ -2,25 +2,16 @@ import { Component } from "solid-js"
import { Dialog } from "@opencode-ai/ui/dialog"
import { Tabs } from "@opencode-ai/ui/tabs"
import { Icon } from "@opencode-ai/ui/icon"
import { Button } from "@opencode-ai/ui/button"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { SettingsGeneral } from "./settings-general"
import { SettingsKeybinds } from "./settings-keybinds"
import { SettingsProviders } from "./settings-providers"
import { SettingsModels } from "./settings-models"
import { SettingsArchive } from "./settings-archive"
import { DialogChangelog } from "@/components/dialog-changelog"
export const DialogSettings: Component = () => {
const language = useLanguage()
const platform = usePlatform()
const dialog = useDialog()
function handleShowChangelog() {
dialog.show(() => <DialogChangelog />)
}
return (
<Dialog size="x-large" transition>
@@ -56,27 +47,11 @@ export const DialogSettings: Component = () => {
</Tabs.Trigger>
</div>
</div>
<div class="flex flex-col gap-1.5">
<Tabs.SectionTitle>{language.t("settings.section.data")}</Tabs.SectionTitle>
<div class="flex flex-col gap-1.5 w-full">
<Tabs.Trigger value="archive">
<Icon name="archive" />
{language.t("settings.archive.title")}
</Tabs.Trigger>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-1 pl-1 py-1 text-12-medium text-text-weak">
<span>{language.t("app.name.desktop")}</span>
<span class="text-11-regular">v{platform.version}</span>
<button
class="text-11-regular text-text-weak hover:text-text-base self-start"
onClick={handleShowChangelog}
>
Changelog
</button>
</div>
</div>
</Tabs.List>
@@ -92,9 +67,6 @@ export const DialogSettings: Component = () => {
<Tabs.Content value="models" class="no-scrollbar">
<SettingsModels />
</Tabs.Content>
<Tabs.Content value="archive" class="no-scrollbar">
<SettingsArchive />
</Tabs.Content>
</Tabs>
</Dialog>
)

View File

@@ -1,60 +0,0 @@
import { Component } from "solid-js"
import { List } from "@opencode-ai/ui/list"
import { Markdown } from "@opencode-ai/ui/markdown"
import { Button } from "@opencode-ai/ui/button"
import { Tag } from "@opencode-ai/ui/tag"
import { useLanguage } from "@/context/language"
type Release = {
tag: string
body: string
date: string
}
interface ReleaseListProps {
releases: Release[]
hasMore: boolean
loadingMore: boolean
onLoadMore: () => void
}
export const ReleaseList: Component<ReleaseListProps> = (props) => {
const language = useLanguage()
return (
<List
items={props.releases}
key={(x) => x.tag}
search={false}
emptyMessage="No releases found"
loadingMessage={language.t("common.loading")}
class="flex-1 min-h-0 overflow-hidden flex flex-col [&_[data-slot=list-scroll]]:session-scroller [&_[data-slot=list-item]]:block [&_[data-slot=list-item]]:p-0 [&_[data-slot=list-item]]:border-0 [&_[data-slot=list-item]]:bg-transparent [&_[data-slot=list-item]]:text-left [&_[data-slot=list-item]]:cursor-default [&_[data-slot=list-item]]:hover:bg-transparent [&_[data-slot=list-item]]:focus:outline-none"
add={{
render: () =>
props.hasMore ? (
<div class="p-4 flex justify-center">
<Button variant="secondary" size="small" onClick={props.onLoadMore} loading={props.loadingMore}>
{language.t("common.loadMore")}
</Button>
</div>
) : null,
}}
>
{(item) => (
<div class="mb-8">
<div class="py-2 pr-3 pl-2 flex items-baseline gap-2 sticky top-0 z-10 bg-surface-raised-stronger-non-alpha">
<span class="text-[20px] font-semibold">{item.tag}</span>
<span class="text-xs text-text-weak">{item.date}</span>
{item.tag === props.releases[0]?.tag && <Tag>{language.t("changelog.tag.latest")}</Tag>}
</div>
<div class="px-2 pb-2">
<Markdown
text={item.body}
class="prose prose-sm max-w-none text-text-base [&_h2]:border-b [&_h2]:border-border-weak-base [&_h2]:pb-1 [&_h2]:mt-8 [&_h2]:mb-3 [&_h2]:text-sm [&_h2]:font-medium [&_h2]:capitalize [&_h2:first-child]:mt-4 [&_a.external-link]:text-text-interactive-base [&_a.external-link]:font-medium"
/>
</div>
</div>
)}
</List>
)
}

View File

@@ -1,81 +0,0 @@
import { describe, expect, test } from "bun:test"
import type { Message } from "@opencode-ai/sdk/v2/client"
import { findAssistantMessages } from "@opencode-ai/ui/find-assistant-messages"
function user(id: string): Message {
return {
id,
role: "user",
sessionID: "session-1",
time: { created: 1 },
} as unknown as Message
}
function assistant(id: string, parentID: string): Message {
return {
id,
role: "assistant",
sessionID: "session-1",
parentID,
time: { created: 1 },
} as unknown as Message
}
describe("findAssistantMessages", () => {
test("normal ordering: assistant after user in array → found via forward scan", () => {
const messages = [user("u1"), assistant("a1", "u1")]
const result = findAssistantMessages(messages, 0, "u1")
expect(result).toHaveLength(1)
expect(result[0].id).toBe("a1")
})
test("clock skew: assistant before user in array → found via backward scan", () => {
// When client clock is ahead, user ID sorts after assistant ID,
// so assistant appears earlier in the ID-sorted message array
const messages = [assistant("a1", "u1"), user("u1")]
const result = findAssistantMessages(messages, 1, "u1")
expect(result).toHaveLength(1)
expect(result[0].id).toBe("a1")
})
test("no assistant messages → returns empty array", () => {
const messages = [user("u1"), user("u2")]
const result = findAssistantMessages(messages, 0, "u1")
expect(result).toHaveLength(0)
})
test("multiple assistant messages with matching parentID → all found", () => {
const messages = [user("u1"), assistant("a1", "u1"), assistant("a2", "u1")]
const result = findAssistantMessages(messages, 0, "u1")
expect(result).toHaveLength(2)
expect(result[0].id).toBe("a1")
expect(result[1].id).toBe("a2")
})
test("does not return assistant messages with different parentID", () => {
const messages = [user("u1"), assistant("a1", "u1"), assistant("a2", "other")]
const result = findAssistantMessages(messages, 0, "u1")
expect(result).toHaveLength(1)
expect(result[0].id).toBe("a1")
})
test("stops forward scan at next user message", () => {
const messages = [user("u1"), assistant("a1", "u1"), user("u2"), assistant("a2", "u1")]
const result = findAssistantMessages(messages, 0, "u1")
expect(result).toHaveLength(1)
expect(result[0].id).toBe("a1")
})
test("stops backward scan at previous user message", () => {
const messages = [assistant("a0", "u1"), user("u0"), assistant("a1", "u1"), user("u1")]
const result = findAssistantMessages(messages, 3, "u1")
expect(result).toHaveLength(1)
expect(result[0].id).toBe("a1")
})
test("invalid index returns empty array", () => {
const messages = [user("u1")]
expect(findAssistantMessages(messages, -1, "u1")).toHaveLength(0)
expect(findAssistantMessages(messages, 5, "u1")).toHaveLength(0)
})
})

View File

@@ -1,188 +0,0 @@
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { RadioGroup } from "@opencode-ai/ui/radio-group"
import { getFilename } from "@opencode-ai/util/path"
import { Component, For, Show, createMemo, createResource, createSignal } from "solid-js"
import { useParams } from "@solidjs/router"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { getRelativeTime } from "@/utils/time"
import { decode64 } from "@/utils/base64"
import type { Session } from "@opencode-ai/sdk/v2/client"
import { SessionSkeleton } from "@/pages/layout/sidebar-items"
type FilterScope = "all" | "current"
type ScopeOption = { value: FilterScope; label: "settings.archive.scope.all" | "settings.archive.scope.current" }
const scopeOptions: ScopeOption[] = [
{ value: "all", label: "settings.archive.scope.all" },
{ value: "current", label: "settings.archive.scope.current" },
]
export const SettingsArchive: Component = () => {
const language = useLanguage()
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
const layout = useLayout()
const params = useParams()
const [removedIds, setRemovedIds] = createSignal<Set<string>>(new Set())
const projects = createMemo(() => globalSync.data.project)
const layoutProjects = createMemo(() => layout.projects.list())
const hasMultipleProjects = createMemo(() => projects().length > 1)
const homedir = createMemo(() => globalSync.data.path.home)
const defaultScope = () => (hasMultipleProjects() ? "current" : "all")
const [filterScope, setFilterScope] = createSignal<FilterScope>(defaultScope())
const currentDirectory = createMemo(() => decode64(params.dir) ?? "")
const currentProject = createMemo(() => {
const dir = currentDirectory()
if (!dir) return null
return layoutProjects().find((p) => p.worktree === dir || p.sandboxes?.includes(dir)) ?? null
})
const filteredProjects = createMemo(() => {
if (filterScope() === "current" && currentProject()) {
return [currentProject()!]
}
return layoutProjects()
})
const getSessionLabel = (session: Session) => {
const directory = session.directory
const home = homedir()
const path = home ? directory.replace(home, "~") : directory
if (filterScope() === "current" && currentProject()) {
const current = currentProject()
const kind =
current && directory === current.worktree
? language.t("workspace.type.local")
: language.t("workspace.type.sandbox")
const [store] = globalSync.child(directory, { bootstrap: false })
const name = store.vcs?.branch ?? getFilename(directory)
return `${kind} : ${name || path}`
}
return path
}
const [archivedSessions] = createResource(
() => ({ scope: filterScope(), projects: filteredProjects() }),
async ({ projects }) => {
const allSessions: Session[] = []
for (const project of projects) {
const directories = [project.worktree, ...(project.sandboxes ?? [])]
for (const directory of directories) {
const result = await globalSDK.client.experimental.session.list({ directory, archived: true })
const sessions = result.data ?? []
for (const session of sessions) {
allSessions.push(session)
}
}
}
return allSessions.sort((a, b) => (b.time?.updated ?? 0) - (a.time?.updated ?? 0))
},
{ initialValue: [] },
)
const displayedSessions = () => {
const sessions = archivedSessions() ?? []
const removed = removedIds()
return sessions.filter((s) => !removed.has(s.id))
}
const currentScopeOption = () => scopeOptions.find((o) => o.value === filterScope())
const unarchiveSession = async (session: Session) => {
setRemovedIds((prev) => new Set(prev).add(session.id))
await globalSDK.client.session.update({
directory: session.directory,
sessionID: session.id,
time: { archived: null as any },
})
}
const handleScopeChange = (option: ScopeOption | undefined) => {
if (!option) return
setRemovedIds(new Set<string>())
setFilterScope(option.value)
}
return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.archive.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.archive.description")}</p>
</div>
</div>
<div class="flex flex-col gap-4 max-w-[720px]">
<Show when={hasMultipleProjects()}>
<RadioGroup
options={scopeOptions}
current={currentScopeOption() ?? undefined}
value={(o) => o.value}
size="small"
label={(o) => language.t(o.label)}
onSelect={handleScopeChange}
/>
</Show>
<Show
when={!archivedSessions.loading}
fallback={
<div class="min-h-[700px]">
<SessionSkeleton count={4} />
</div>
}
>
<Show
when={displayedSessions().length}
fallback={
<div class="min-h-[700px]">
<div class="text-14-regular text-text-weak">{language.t("settings.archive.none")}</div>
</div>
}
>
<div class="min-h-[700px] flex flex-col gap-2">
<For each={displayedSessions()}>
{(session) => (
<div class="flex items-center justify-between gap-4 px-3 py-1 rounded-md hover:bg-surface-raised-base-hover">
<div class="flex items-center gap-x-3 grow min-w-0">
<div class="flex items-center gap-2 min-w-0">
<span class="text-14-regular text-text-strong truncate">{session.title}</span>
<span class="text-14-regular text-text-weak truncate">{getSessionLabel(session)}</span>
</div>
</div>
<div class="flex items-center gap-4 shrink-0">
<Show when={session.time?.updated}>
{(updated) => (
<span class="text-12-regular text-text-weak whitespace-nowrap">
{getRelativeTime(new Date(updated()).toISOString(), language.t)}
</span>
)}
</Show>
<Button
size="normal"
variant="secondary"
onClick={() => unarchiveSession(session)}
>
{language.t("common.unarchive")}
</Button>
</div>
</div>
)}
</For>
</div>
</Show>
</Show>
</div>
</div>
)
}

View File

@@ -265,9 +265,6 @@ export function Titlebar() {
</div>
</div>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
<div class="bg-icon-interactive-base text-background-base font-medium px-2 rounded-sm uppercase font-mono">
BETA
</div>
</div>
<div class="min-w-0 flex items-center justify-center pointer-events-none">

View File

@@ -506,9 +506,6 @@ export const dict = {
"common.close": "إغلاق",
"common.edit": "تحرير",
"common.loadMore": "تحميل المزيد",
"common.changelog": "التغييرات",
"common.noReleasesFound": "لم يتم العثور على إصدارات",
"changelog.tag.latest": "الأحدث",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "تبديل القائمة",
"sidebar.nav.projectsAndSessions": "المشاريع والجلسات",
@@ -737,11 +734,6 @@ export const dict = {
"workspace.reset.archived.one": "ستتم أرشفة جلسة واحدة.",
"workspace.reset.archived.many": "ستتم أرشفة {{count}} جلسات.",
"workspace.reset.note": "سيؤدي هذا إلى إعادة تعيين مساحة العمل لتتطابق مع الفرع الافتراضي.",
"settings.archive.title": "الجلسات المؤرشفة",
"settings.archive.description": "استعادة الجلسات المؤرشفة لجعلها مرئية في الشريط الجانبي.",
"settings.archive.none": "لا توجد جلسات مؤرشفة.",
"settings.archive.scope.all": "جميع المشاريع",
"settings.archive.scope.current": "المشروع الحالي",
"common.open": "فتح",
"dialog.releaseNotes.action.getStarted": "البدء",
"dialog.releaseNotes.action.next": "التالي",

View File

@@ -512,9 +512,6 @@ export const dict = {
"common.close": "Fechar",
"common.edit": "Editar",
"common.loadMore": "Carregar mais",
"common.changelog": "Novidades",
"common.noReleasesFound": "Nenhuma release encontrada",
"changelog.tag.latest": "Mais recente",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Alternar menu",
"sidebar.nav.projectsAndSessions": "Projetos e sessões",
@@ -745,11 +742,6 @@ export const dict = {
"workspace.reset.archived.one": "1 sessão será arquivada.",
"workspace.reset.archived.many": "{{count}} sessões serão arquivadas.",
"workspace.reset.note": "Isso redefinirá o espaço de trabalho para corresponder ao branch padrão.",
"settings.archive.title": "Sessões arquivadas",
"settings.archive.description": "Restaure sessões arquivadas para torná-las visíveis na barra lateral.",
"settings.archive.none": "Nenhuma sessão arquivada.",
"settings.archive.scope.all": "Todos os projetos",
"settings.archive.scope.current": "Projeto atual",
"common.open": "Abrir",
"dialog.releaseNotes.action.getStarted": "Começar",
"dialog.releaseNotes.action.next": "Próximo",

View File

@@ -572,9 +572,6 @@ export const dict = {
"common.close": "Zatvori",
"common.edit": "Uredi",
"common.loadMore": "Učitaj još",
"common.changelog": "Novosti",
"common.noReleasesFound": "Nema pronađenih verzija",
"changelog.tag.latest": "Najnovije",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Prikaži/sakrij meni",
@@ -822,11 +819,6 @@ export const dict = {
"workspace.reset.archived.one": "1 sesija će biti arhivirana.",
"workspace.reset.archived.many": "Biće arhivirano {{count}} sesija.",
"workspace.reset.note": "Ovo će resetovati radni prostor da odgovara podrazumijevanoj grani.",
"settings.archive.title": "Arhivirane sesije",
"settings.archive.description": "Vrati arhivirane sesije da bi bile vidljive u bočnoj traci.",
"settings.archive.none": "Nema arhiviranih sesija.",
"settings.archive.scope.all": "Svi projekti",
"settings.archive.scope.current": "Trenutni projekt",
"common.open": "Otvori",
"dialog.releaseNotes.action.getStarted": "Započni",
"dialog.releaseNotes.action.next": "Sljedeće",

View File

@@ -568,9 +568,6 @@ export const dict = {
"common.close": "Luk",
"common.edit": "Rediger",
"common.loadMore": "Indlæs flere",
"common.changelog": "Nyheder",
"common.noReleasesFound": "Ingen versioner fundet",
"changelog.tag.latest": "Seneste",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Skift menu",
@@ -816,11 +813,6 @@ export const dict = {
"workspace.reset.archived.one": "1 session vil blive arkiveret.",
"workspace.reset.archived.many": "{{count}} sessioner vil blive arkiveret.",
"workspace.reset.note": "Dette vil nulstille arbejdsområdet til at matche hovedgrenen.",
"settings.archive.title": "Arkiverede sessioner",
"settings.archive.description": "Gendan arkiverede sessioner for at gøre dem synlige i sidebjælken.",
"settings.archive.none": "Ingen arkiverede sessioner.",
"settings.archive.scope.all": "Alle projekter",
"settings.archive.scope.current": "Nuværende projekt",
"common.open": "Åbn",
"dialog.releaseNotes.action.getStarted": "Kom i gang",
"dialog.releaseNotes.action.next": "Næste",

View File

@@ -520,10 +520,6 @@ export const dict = {
"common.close": "Schließen",
"common.edit": "Bearbeiten",
"common.loadMore": "Mehr laden",
"common.changelog": "Neuerungen",
"common.noReleasesFound": "Keine Versionen gefunden",
"changelog.tag.latest": "Neueste",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Menü umschalten",
"sidebar.nav.projectsAndSessions": "Projekte und Sitzungen",
@@ -755,12 +751,6 @@ export const dict = {
"workspace.reset.archived.one": "1 Sitzung wird archiviert.",
"workspace.reset.archived.many": "{{count}} Sitzungen werden archiviert.",
"workspace.reset.note": "Dadurch wird der Arbeitsbereich auf den Standard-Branch zurückgesetzt.",
"settings.archive.title": "Archivierte Sitzungen",
"settings.archive.description": "Archivierte Sitzungen wiederherstellen, um sie in der Seitenleiste anzuzeigen.",
"settings.archive.none": "Keine archivierten Sitzungen.",
"settings.archive.scope.all": "Alle Projekte",
"settings.archive.scope.current": "Aktuelles Projekt",
"common.open": "Öffnen",
"dialog.releaseNotes.action.getStarted": "Loslegen",
"dialog.releaseNotes.action.next": "Weiter",

View File

@@ -585,14 +585,10 @@ export const dict = {
"common.rename": "Rename",
"common.reset": "Reset",
"common.archive": "Archive",
"common.unarchive": "Unarchive",
"common.delete": "Delete",
"common.close": "Close",
"common.edit": "Edit",
"common.loadMore": "Load more",
"common.changelog": "Changelog",
"common.noReleasesFound": "No releases found",
"changelog.tag.latest": "Latest",
"common.key.esc": "ESC",
"common.time.justNow": "Just now",
@@ -617,7 +613,6 @@ export const dict = {
"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
"settings.section.data": "Data",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Shortcuts",
"settings.desktop.section.wsl": "WSL",
@@ -849,10 +844,4 @@ export const dict = {
"workspace.reset.archived.one": "1 session will be archived.",
"workspace.reset.archived.many": "{{count}} sessions will be archived.",
"workspace.reset.note": "This will reset the workspace to match the default branch.",
"settings.archive.title": "Archived Sessions",
"settings.archive.description": "Restore archived sessions to make them visible in the sidebar.",
"settings.archive.none": "No archived sessions.",
"settings.archive.scope.all": "All projects",
"settings.archive.scope.current": "Current project",
}

View File

@@ -575,10 +575,6 @@ export const dict = {
"common.close": "Cerrar",
"common.edit": "Editar",
"common.loadMore": "Cargar más",
"common.changelog": "Novedades",
"common.noReleasesFound": "No se encontraron versiones",
"changelog.tag.latest": "Último",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Alternar menú",
@@ -829,12 +825,6 @@ export const dict = {
"workspace.reset.archived.one": "1 sesión será archivada.",
"workspace.reset.archived.many": "{{count}} sesiones serán archivadas.",
"workspace.reset.note": "Esto restablecerá el espacio de trabajo para coincidir con la rama predeterminada.",
"settings.archive.title": "Sesiones archivadas",
"settings.archive.description": "Restaura las sesiones archivadas para hacerlas visibles en la barra lateral.",
"settings.archive.none": "No hay sesiones archivadas.",
"settings.archive.scope.all": "Todos los proyectos",
"settings.archive.scope.current": "Proyecto actual",
"common.open": "Abrir",
"dialog.releaseNotes.action.getStarted": "Comenzar",
"dialog.releaseNotes.action.next": "Siguiente",

View File

@@ -516,10 +516,6 @@ export const dict = {
"common.close": "Fermer",
"common.edit": "Modifier",
"common.loadMore": "Charger plus",
"common.changelog": "Nouveautés",
"common.noReleasesFound": "Aucune version trouvée",
"changelog.tag.latest": "Dernier",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Basculer le menu",
"sidebar.nav.projectsAndSessions": "Projets et sessions",
@@ -753,11 +749,6 @@ export const dict = {
"workspace.reset.archived.one": "1 session sera archivée.",
"workspace.reset.archived.many": "{{count}} sessions seront archivées.",
"workspace.reset.note": "Cela réinitialisera l'espace de travail pour correspondre à la branche par défaut.",
"settings.archive.title": "Sessions archivées",
"settings.archive.description": "Restaurez les sessions archivées pour les rendre visibles dans la barre latérale.",
"settings.archive.none": "Aucune session archivée.",
"settings.archive.scope.all": "Tous les Projets",
"settings.archive.scope.current": "Projet actuel",
"common.open": "Ouvrir",
"dialog.releaseNotes.action.getStarted": "Commencer",
"dialog.releaseNotes.action.next": "Suivant",

View File

@@ -510,10 +510,6 @@ export const dict = {
"common.close": "閉じる",
"common.edit": "編集",
"common.loadMore": "さらに読み込む",
"common.changelog": "更新履歴",
"common.noReleasesFound": "バージョンが見つかりません",
"changelog.tag.latest": "最新",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "メニューを切り替え",
"sidebar.nav.projectsAndSessions": "プロジェクトとセッション",
@@ -742,12 +738,6 @@ export const dict = {
"workspace.reset.archived.one": "1つのセッションがアーカイブされます。",
"workspace.reset.archived.many": "{{count}}個のセッションがアーカイブされます。",
"workspace.reset.note": "これにより、ワークスペースはデフォルトブランチと一致するようにリセットされます。",
"settings.archive.title": "アーカイブされたセッション",
"settings.archive.description": "アーカイブされたセッションを復元してサイドバーに表示します。",
"settings.archive.none": "アーカイブされたセッションはありません。",
"settings.archive.scope.all": "すべてのプロジェクト",
"settings.archive.scope.current": "現在のプロジェクト",
"common.open": "開く",
"dialog.releaseNotes.action.getStarted": "始める",
"dialog.releaseNotes.action.next": "次へ",

View File

@@ -511,10 +511,6 @@ export const dict = {
"common.close": "닫기",
"common.edit": "편집",
"common.loadMore": "더 불러오기",
"common.changelog": "새로운 기능",
"common.noReleasesFound": "버전을 찾을 수 없음",
"changelog.tag.latest": "최신",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "메뉴 토글",
"sidebar.nav.projectsAndSessions": "프로젝트 및 세션",
@@ -742,12 +738,6 @@ export const dict = {
"workspace.reset.archived.one": "1개의 세션이 보관됩니다.",
"workspace.reset.archived.many": "{{count}}개의 세션이 보관됩니다.",
"workspace.reset.note": "이 작업은 작업 공간을 기본 브랜치와 일치하도록 재설정합니다.",
"settings.archive.title": "보관된 세션",
"settings.archive.description": "보관된 세션을 복원하여 사이드바에 표시합니다.",
"settings.archive.none": "보관된 세션이 없습니다.",
"settings.archive.scope.all": "모든 프로젝트",
"settings.archive.scope.current": "현재 프로젝트",
"common.open": "열기",
"dialog.releaseNotes.action.getStarted": "시작하기",
"dialog.releaseNotes.action.next": "다음",

View File

@@ -575,9 +575,6 @@ export const dict = {
"common.close": "Lukk",
"common.edit": "Rediger",
"common.loadMore": "Last flere",
"common.changelog": "Nyheter",
"common.noReleasesFound": "Ingen versjoner funnet",
"changelog.tag.latest": "Siste",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Veksle meny",
@@ -824,12 +821,6 @@ export const dict = {
"workspace.reset.archived.one": "1 sesjon vil bli arkivert.",
"workspace.reset.archived.many": "{{count}} sesjoner vil bli arkivert.",
"workspace.reset.note": "Dette vil tilbakestille arbeidsområdet til å samsvare med standardgrenen.",
"settings.archive.title": "Arkiverte økter",
"settings.archive.description": "Gjenopprett arkiverte økter for å gjøre dem synlige i sidefeltet.",
"settings.archive.none": "Ingen arkiverte økter.",
"settings.archive.scope.all": "Alle prosjekter",
"settings.archive.scope.current": "Nåværende prosjekt",
"common.open": "Åpne",
"dialog.releaseNotes.action.getStarted": "Kom i gang",
"dialog.releaseNotes.action.next": "Neste",

View File

@@ -511,9 +511,6 @@ export const dict = {
"common.close": "Zamknij",
"common.edit": "Edytuj",
"common.loadMore": "Załaduj więcej",
"common.changelog": "Nowości",
"common.noReleasesFound": "Nie znaleziono wersji",
"changelog.tag.latest": "Najnowszy",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Przełącz menu",
"sidebar.nav.projectsAndSessions": "Projekty i sesje",
@@ -743,11 +740,6 @@ export const dict = {
"workspace.reset.archived.one": "1 sesja zostanie zarchiwizowana.",
"workspace.reset.archived.many": "{{count}} sesji zostanie zarchiwizowanych.",
"workspace.reset.note": "To zresetuje przestrzeń roboczą, aby odpowiadała domyślnej gałęzi.",
"settings.archive.title": "Zarchiwizowane sesje",
"settings.archive.description": "Przywróć zarchiwizowane sesje, aby były widoczne na pasku bocznym.",
"settings.archive.none": "Brak zarchiwizowanych sesji.",
"settings.archive.scope.all": "Wszystkie projekty",
"settings.archive.scope.current": "Bieżący projekt",
"common.open": "Otwórz",
"dialog.releaseNotes.action.getStarted": "Rozpocznij",
"dialog.releaseNotes.action.next": "Dalej",

View File

@@ -573,9 +573,6 @@ export const dict = {
"common.close": "Закрыть",
"common.edit": "Редактировать",
"common.loadMore": "Загрузить ещё",
"common.changelog": "Что нового",
"common.noReleasesFound": "Версии не найдены",
"changelog.tag.latest": "Последний",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Переключить меню",
@@ -824,11 +821,6 @@ export const dict = {
"workspace.reset.archived.one": "1 сессия будет архивирована.",
"workspace.reset.archived.many": "{{count}} сессий будет архивировано.",
"workspace.reset.note": "Рабочее пространство будет сброшено в соответствие с веткой по умолчанию.",
"settings.archive.title": "Архивированные сессии",
"settings.archive.description": "Восстановите архивированные сессии, чтобы они отображались на боковой панели.",
"settings.archive.none": "Нет архивированных сессий.",
"settings.archive.scope.all": "Все проекты",
"settings.archive.scope.current": "Текущий проект",
"common.open": "Открыть",
"dialog.releaseNotes.action.getStarted": "Начать",
"dialog.releaseNotes.action.next": "Далее",

View File

@@ -567,9 +567,6 @@ export const dict = {
"common.close": "ปิด",
"common.edit": "แก้ไข",
"common.loadMore": "โหลดเพิ่มเติม",
"common.changelog": "อัปเดต",
"common.noReleasesFound": "ไม่พบเวอร์ชัน",
"changelog.tag.latest": "ล่าสุด",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "สลับเมนู",
@@ -814,12 +811,6 @@ export const dict = {
"workspace.reset.archived.one": "1 เซสชันจะถูกจัดเก็บ",
"workspace.reset.archived.many": "{{count}} เซสชันจะถูกจัดเก็บ",
"workspace.reset.note": "สิ่งนี้จะรีเซ็ตพื้นที่ทำงานให้ตรงกับสาขาเริ่มต้น",
"settings.archive.title": "เซสชันที่จัดเก็บ",
"settings.archive.description": "กู้คืนเซสชันที่จัดเก็บเพื่อให้แสดงในแถบด้านข้าง",
"settings.archive.none": "ไม่มีเซสชันที่จัดเก็บ",
"settings.archive.scope.all": "โปรเจกต์ทั้งหมด",
"settings.archive.scope.current": "โปรเจกต์ปัจจุบัน",
"common.open": "เปิด",
"dialog.releaseNotes.action.getStarted": "เริ่มต้น",
"dialog.releaseNotes.action.next": "ถัดไป",

View File

@@ -566,10 +566,6 @@ export const dict = {
"common.close": "关闭",
"common.edit": "编辑",
"common.loadMore": "加载更多",
"common.changelog": "更新日志",
"common.noReleasesFound": "未找到版本",
"changelog.tag.latest": "最新",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "切换菜单",
@@ -813,12 +809,6 @@ export const dict = {
"workspace.reset.archived.one": "将归档 1 个会话。",
"workspace.reset.archived.many": "将归档 {{count}} 个会话。",
"workspace.reset.note": "这将把工作区重置为与默认分支一致。",
"settings.archive.title": "归档会话",
"settings.archive.description": "恢复归档会话以使其在侧边栏中可见。",
"settings.archive.none": "没有归档会话。",
"settings.archive.scope.all": "所有项目",
"settings.archive.scope.current": "当前项目",
"common.open": "打开",
"dialog.releaseNotes.action.getStarted": "开始",
"dialog.releaseNotes.action.next": "下一步",

View File

@@ -563,9 +563,6 @@ export const dict = {
"common.close": "關閉",
"common.edit": "編輯",
"common.loadMore": "載入更多",
"common.changelog": "更新日誌",
"common.noReleasesFound": "未找到版本",
"changelog.tag.latest": "最新",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "切換選單",
@@ -807,12 +804,6 @@ export const dict = {
"workspace.reset.archived.one": "將封存 1 個工作階段。",
"workspace.reset.archived.many": "將封存 {{count}} 個工作階段。",
"workspace.reset.note": "這將把工作區重設為與預設分支一致。",
"settings.archive.title": "封存工作階段",
"settings.archive.description": "恢復封存的工作階段以使其在側邊欄中可見。",
"settings.archive.none": "沒有封存的工作階段。",
"settings.archive.scope.all": "所有專案",
"settings.archive.scope.current": "目前專案",
"common.open": "打開",
"dialog.releaseNotes.action.getStarted": "開始",
"dialog.releaseNotes.action.next": "下一步",

View File

@@ -43,7 +43,6 @@ import { retry } from "@opencode-ai/util/retry"
import { playSound, soundSrc } from "@/utils/sound"
import { createAim } from "@/utils/aim"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { setSessionHandoff } from "@/pages/session/handoff"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
@@ -67,12 +66,7 @@ import {
syncWorkspaceOrder,
workspaceKey,
} from "./layout/helpers"
import {
collectNewSessionDeepLinks,
collectOpenProjectDeepLinks,
deepLinkEvent,
drainPendingDeepLinks,
} from "./layout/deep-links"
import { collectOpenProjectDeepLinks, deepLinkEvent, drainPendingDeepLinks } from "./layout/deep-links"
import { createInlineEditorController } from "./layout/inline-editor"
import {
LocalWorkspace,
@@ -1163,20 +1157,9 @@ export default function Layout(props: ParentProps) {
const handleDeepLinks = (urls: string[]) => {
if (!server.isLocal()) return
for (const directory of collectOpenProjectDeepLinks(urls)) {
openProject(directory)
}
for (const link of collectNewSessionDeepLinks(urls)) {
openProject(link.directory, false)
const slug = base64Encode(link.directory)
if (link.prompt) {
setSessionHandoff(slug, { prompt: link.prompt })
}
const href = link.prompt ? `/${slug}/session?prompt=${encodeURIComponent(link.prompt)}` : `/${slug}/session`
navigateWithSidebarReset(href)
}
}
onMount(() => {

View File

@@ -1,17 +1,15 @@
export const deepLinkEvent = "opencode:deep-link"
const parseUrl = (input: string) => {
export const parseDeepLink = (input: string) => {
if (!input.startsWith("opencode://")) return
if (typeof URL.canParse === "function" && !URL.canParse(input)) return
try {
return new URL(input)
} catch {
return
}
}
export const parseDeepLink = (input: string) => {
const url = parseUrl(input)
const url = (() => {
try {
return new URL(input)
} catch {
return undefined
}
})()
if (!url) return
if (url.hostname !== "open-project") return
const directory = url.searchParams.get("directory")
@@ -19,23 +17,9 @@ export const parseDeepLink = (input: string) => {
return directory
}
export const parseNewSessionDeepLink = (input: string) => {
const url = parseUrl(input)
if (!url) return
if (url.hostname !== "new-session") return
const directory = url.searchParams.get("directory")
if (!directory) return
const prompt = url.searchParams.get("prompt") || undefined
if (!prompt) return { directory }
return { directory, prompt }
}
export const collectOpenProjectDeepLinks = (urls: string[]) =>
urls.map(parseDeepLink).filter((directory): directory is string => !!directory)
export const collectNewSessionDeepLinks = (urls: string[]) =>
urls.map(parseNewSessionDeepLink).filter((link): link is { directory: string; prompt?: string } => !!link)
type OpenCodeWindow = Window & {
__OPENCODE__?: {
deepLinks?: string[]

View File

@@ -1,14 +1,15 @@
import { describe, expect, test } from "bun:test"
import {
collectNewSessionDeepLinks,
collectOpenProjectDeepLinks,
drainPendingDeepLinks,
parseDeepLink,
parseNewSessionDeepLink,
} from "./deep-links"
import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers"
import { type Session } from "@opencode-ai/sdk/v2/client"
import { hasProjectPermissions, latestRootSession } from "./helpers"
import { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links"
import {
displayName,
errorMessage,
getDraggableId,
hasProjectPermissions,
latestRootSession,
syncWorkspaceOrder,
workspaceKey,
} from "./helpers"
const session = (input: Partial<Session> & Pick<Session, "id" | "directory">) =>
({
@@ -61,28 +62,6 @@ describe("layout deep links", () => {
expect(result).toEqual(["/a", "/c"])
})
test("parses new-session deep links with optional prompt", () => {
expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo")).toEqual({ directory: "/tmp/demo" })
expect(parseNewSessionDeepLink("opencode://new-session?directory=/tmp/demo&prompt=hello%20world")).toEqual({
directory: "/tmp/demo",
prompt: "hello world",
})
})
test("ignores new-session deep links without directory", () => {
expect(parseNewSessionDeepLink("opencode://new-session")).toBeUndefined()
expect(parseNewSessionDeepLink("opencode://new-session?directory=")).toBeUndefined()
})
test("collects only valid new-session deep links", () => {
const result = collectNewSessionDeepLinks([
"opencode://new-session?directory=/a",
"opencode://open-project?directory=/b",
"opencode://new-session?directory=/c&prompt=ship%20it",
])
expect(result).toEqual([{ directory: "/a" }, { directory: "/c", prompt: "ship it" }])
})
test("drains global deep links once", () => {
const target = {
__OPENCODE__: {

View File

@@ -1,35 +1,36 @@
import type { UserMessage } from "@opencode-ai/sdk/v2"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { createAutoScroll } from "@opencode-ai/ui/hooks"
import { Mark } from "@opencode-ai/ui/logo"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Select } from "@opencode-ai/ui/select"
import { base64Encode, checksum } from "@opencode-ai/util/encode"
import { onCleanup, Show, Match, Switch, createMemo, createEffect, on, onMount } from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { createEffect, createMemo, Match, on, onCleanup, onMount, Show, Switch, untrack } from "solid-js"
import { createStore } from "solid-js/store"
import { NewSessionView, SessionHeader } from "@/components/session"
import { useComments } from "@/context/comments"
import { type FileSelection, type SelectedLineRange, selectionFromLines, useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useLocal } from "@/context/local"
import { usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk"
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
import { createStore } from "solid-js/store"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Select } from "@opencode-ai/ui/select"
import { createAutoScroll } from "@opencode-ai/ui/hooks"
import { Mark } from "@opencode-ai/ui/logo"
import { useSync } from "@/context/sync"
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
import { useLayout } from "@/context/layout"
import { checksum, base64Encode } from "@opencode-ai/util/encode"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useLanguage } from "@/context/language"
import { useNavigate, useParams } from "@solidjs/router"
import { UserMessage } from "@opencode-ai/sdk/v2"
import { useSDK } from "@/context/sdk"
import { usePrompt } from "@/context/prompt"
import { useComments } from "@/context/comments"
import { SessionHeader, NewSessionView } from "@/components/session"
import { same } from "@/utils/same"
import { createOpenReviewFile } from "@/pages/session/helpers"
import { MessageTimeline } from "@/pages/session/message-timeline"
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
import { createScrollSpy } from "@/pages/session/scroll-spy"
import { SessionReviewTab, type DiffStyle, type SessionReviewTabProps } from "@/pages/session/review-tab"
import { TerminalPanel } from "@/pages/session/terminal-panel"
import { MessageTimeline } from "@/pages/session/message-timeline"
import { useSessionCommands } from "@/pages/session/use-session-commands"
import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs"
import { SessionSidePanel } from "@/pages/session/session-side-panel"
import { TerminalPanel } from "@/pages/session/terminal-panel"
import { useSessionCommands } from "@/pages/session/use-session-commands"
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
import { same } from "@/utils/same"
export default function Page() {
const layout = useLayout()
@@ -43,19 +44,6 @@ export default function Page() {
const sdk = useSDK()
const prompt = usePrompt()
const comments = useComments()
const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>()
createEffect(() => {
if (!untrack(() => prompt.ready())) return
prompt.ready()
untrack(() => {
if (params.id || !prompt.ready()) return
const text = searchParams.prompt
if (!text) return
prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length)
setSearchParams({ ...searchParams, prompt: undefined })
})
})
const [ui, setUi] = createStore({
pendingMessage: undefined as string | undefined,
@@ -490,11 +478,7 @@ export default function Page() {
on(
sessionKey,
() => {
setTree({
reviewScroll: undefined,
pendingDiff: undefined,
activeDiff: undefined,
})
setTree({ reviewScroll: undefined, pendingDiff: undefined, activeDiff: undefined })
},
{ defer: true },
),

View File

@@ -3,6 +3,7 @@ import { createStore } from "solid-js/store"
import { Button } from "@opencode-ai/ui/button"
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { showToast } from "@opencode-ai/ui/toast"
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
import { useLanguage } from "@/context/language"
@@ -22,6 +23,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
customOn: [] as boolean[],
editing: false,
sending: false,
collapsed: false,
})
let root: HTMLDivElement | undefined
@@ -31,6 +33,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const input = createMemo(() => store.custom[store.tab] ?? "")
const on = createMemo(() => store.customOn[store.tab] === true)
const multi = createMemo(() => question()?.multiple === true)
const picked = createMemo(() => store.answers[store.tab]?.length ?? 0)
const summary = createMemo(() => {
const n = Math.min(store.tab + 1, total())
@@ -39,6 +42,8 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const last = createMemo(() => store.tab >= total() - 1)
const fold = () => setStore("collapsed", (value) => !value)
const customUpdate = (value: string, selected: boolean = on()) => {
const prev = input().trim()
const next = value.trim()
@@ -228,38 +233,62 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
setStore("editing", false)
}
const jump = (tab: number) => {
const click = (target: EventTarget | null) => {
if (store.sending) return
setStore("tab", tab)
setStore("editing", false)
if (!(target instanceof Node)) {
fold()
return
}
const list = root?.querySelector('[data-slot="question-options"]')
if (list instanceof HTMLElement && list.contains(target)) return
fold()
}
return (
<DockPrompt
kind="question"
ref={(el) => (root = el)}
bodyProps={{
onClick: (event) => click(event.target),
}}
header={
<>
<div
data-action="session-question-toggle"
class="flex flex-1 min-w-0 items-center gap-2 cursor-default select-none"
role="button"
tabIndex={0}
style={{ margin: "0 -10px", padding: "0 0 0 10px" }}
onClick={(event) => {
event.stopPropagation()
fold()
}}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
fold()
}}
>
<div data-slot="question-header-title">{summary()}</div>
<div data-slot="question-progress">
<For each={questions()}>
{(_, i) => (
<button
type="button"
data-slot="question-progress-segment"
data-active={i() === store.tab}
data-answered={
(store.answers[i()]?.length ?? 0) > 0 ||
(store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0)
}
disabled={store.sending}
onClick={() => jump(i())}
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
/>
)}
</For>
<div class="ml-auto">
<IconButton
data-action="session-question-toggle-button"
icon="chevron-down"
size="normal"
variant="ghost"
classList={{ "rotate-180": store.collapsed }}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
fold()
}}
aria-label={store.collapsed ? language.t("session.todo.expand") : language.t("session.todo.collapse")}
/>
</div>
</>
</div>
}
footer={
<>
@@ -279,56 +308,122 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</>
}
>
<div data-slot="question-text">{question()?.question}</div>
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
<div
data-slot="question-text"
class="cursor-default"
classList={{
"mb-6": store.collapsed && picked() === 0,
}}
onClick={(event) => {
event.stopPropagation()
fold()
}}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
event.stopPropagation()
fold()
}}
>
{question()?.question}
</div>
<Show when={store.collapsed && picked() > 0}>
<div data-slot="question-hint" class="cursor-default mb-6">
{picked()} answer{picked() === 1 ? "" : "s"} selected
</div>
</Show>
<div data-slot="question-options">
<For each={options()}>
{(opt, i) => {
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
return (
<div data-slot="question-answers" hidden={store.collapsed} aria-hidden={store.collapsed}>
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
</Show>
<div data-slot="question-options">
<For each={options()}>
{(opt, i) => {
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
return (
<button
data-slot="question-option"
data-picked={picked()}
role={multi() ? "checkbox" : "radio"}
aria-checked={picked()}
disabled={store.sending}
onClick={() => selectOption(i())}
>
<span data-slot="question-option-check" aria-hidden="true">
<span
data-slot="question-option-box"
data-type={multi() ? "checkbox" : "radio"}
data-picked={picked()}
>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{opt.label}</span>
<Show when={opt.description}>
<span data-slot="option-description">{opt.description}</span>
</Show>
</span>
</button>
)
}}
</For>
<Show
when={store.editing}
fallback={
<button
data-slot="question-option"
data-picked={picked()}
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={picked()}
aria-checked={on()}
disabled={store.sending}
onClick={() => selectOption(i())}
onClick={customOpen}
>
<span data-slot="question-option-check" aria-hidden="true">
<span
data-slot="question-option-box"
data-type={multi() ? "checkbox" : "radio"}
data-picked={picked()}
>
<span
data-slot="question-option-check"
aria-hidden="true"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
customToggle()
}}
>
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{opt.label}</span>
<Show when={opt.description}>
<span data-slot="option-description">{opt.description}</span>
</Show>
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<span data-slot="option-description">{input() || language.t("ui.question.custom.placeholder")}</span>
</span>
</button>
)
}}
</For>
<Show
when={store.editing}
fallback={
<button
}
>
<form
data-slot="question-option"
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={on()}
disabled={store.sending}
onClick={customOpen}
onMouseDown={(e) => {
if (store.sending) {
e.preventDefault()
return
}
if (e.target instanceof HTMLTextAreaElement) return
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
if (input instanceof HTMLTextAreaElement) input.focus()
}}
onSubmit={(e) => {
e.preventDefault()
commitCustom()
}}
>
<span
data-slot="question-option-check"
@@ -347,80 +442,39 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<span data-slot="option-description">{input() || language.t("ui.question.custom.placeholder")}</span>
</span>
</button>
}
>
<form
data-slot="question-option"
data-custom="true"
data-picked={on()}
role={multi() ? "checkbox" : "radio"}
aria-checked={on()}
onMouseDown={(e) => {
if (store.sending) {
e.preventDefault()
return
}
if (e.target instanceof HTMLTextAreaElement) return
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
if (input instanceof HTMLTextAreaElement) input.focus()
}}
onSubmit={(e) => {
e.preventDefault()
commitCustom()
}}
>
<span
data-slot="question-option-check"
aria-hidden="true"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
customToggle()
}}
>
<span data-slot="question-option-box" data-type={multi() ? "checkbox" : "radio"} data-picked={on()}>
<Show when={multi()} fallback={<span data-slot="question-option-radio-dot" />}>
<Icon name="check-small" size="small" />
</Show>
</span>
</span>
<span data-slot="question-option-main">
<span data-slot="option-label">{language.t("ui.messagePart.option.typeOwnAnswer")}</span>
<textarea
ref={(el) =>
setTimeout(() => {
el.focus()
el.style.height = "0px"
el.style.height = `${el.scrollHeight}px`
}, 0)
}
data-slot="question-custom-input"
placeholder={language.t("ui.question.custom.placeholder")}
value={input()}
rows={1}
disabled={store.sending}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.preventDefault()
setStore("editing", false)
return
<textarea
ref={(el) =>
setTimeout(() => {
el.focus()
el.style.height = "0px"
el.style.height = `${el.scrollHeight}px`
}, 0)
}
if (e.key !== "Enter" || e.shiftKey) return
e.preventDefault()
commitCustom()
}}
onInput={(e) => {
customUpdate(e.currentTarget.value)
e.currentTarget.style.height = "0px"
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
}}
/>
</span>
</form>
</Show>
data-slot="question-custom-input"
placeholder={language.t("ui.question.custom.placeholder")}
value={input()}
rows={1}
disabled={store.sending}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.preventDefault()
setStore("editing", false)
return
}
if (e.key !== "Enter" || e.shiftKey) return
e.preventDefault()
commitCustom()
}}
onInput={(e) => {
customUpdate(e.currentTarget.value)
e.currentTarget.style.height = "0px"
e.currentTarget.style.height = `${e.currentTarget.scrollHeight}px`
}}
/>
</span>
</form>
</Show>
</div>
</div>
</DockPrompt>
)

View File

@@ -117,7 +117,6 @@ export function MessageTimeline(props: {
const dialog = useDialog()
const language = useLanguage()
const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id))
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const sessionID = createMemo(() => params.id)
const info = createMemo(() => {
@@ -553,16 +552,16 @@ export function MessageTimeline(props: {
</Button>
</div>
</Show>
<For each={rendered()}>
{(messageID) => {
const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []))
<For each={props.renderedUserMessages}>
{(message) => {
const comments = createMemo(() => messageComments(sync.data.part[message.id] ?? []))
return (
<div
id={props.anchor(messageID)}
data-message-id={messageID}
id={props.anchor(message.id)}
data-message-id={message.id}
ref={(el) => {
props.onRegisterMessage(el, messageID)
onCleanup(() => props.onUnregisterMessage(messageID))
props.onRegisterMessage(el, message.id)
onCleanup(() => props.onUnregisterMessage(message.id))
}}
classList={{
"min-w-0 w-full max-w-full": true,
@@ -601,7 +600,7 @@ export function MessageTimeline(props: {
</Show>
<SessionTurn
sessionID={sessionID() ?? ""}
messageID={messageID}
messageID={message.id}
lastUserMessageID={props.lastUserMessageID}
showReasoningSummaries={settings.general.showReasoningSummaries()}
shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}

View File

@@ -7,21 +7,9 @@ import { Font } from "@opencode-ai/ui/font"
import "@ibm/plex/css/ibm-plex.css"
import "./app.css"
import { LanguageProvider } from "~/context/language"
import { I18nProvider, useI18n } from "~/context/i18n"
import { I18nProvider } from "~/context/i18n"
import { strip } from "~/lib/language"
function AppMeta() {
const i18n = useI18n()
return (
<>
<Title>opencode</Title>
<Meta name="description" content={i18n.t("app.meta.description")} />
<Favicon />
<Font />
</>
)
}
export default function App() {
return (
<Router
@@ -31,7 +19,10 @@ export default function App() {
<LanguageProvider>
<I18nProvider>
<MetaProvider>
<AppMeta />
<Title>opencode</Title>
<Meta name="description" content="OpenCode - The open source coding agent." />
<Favicon />
<Font />
<Suspense>{props.children}</Suspense>
</MetaProvider>
</I18nProvider>

View File

@@ -124,8 +124,8 @@ export function Header(props: { zen?: boolean; hideGetStarted?: boolean }) {
<section data-component="top">
<div onContextMenu={handleLogoContextMenu}>
<A href={language.route("/")}>
<img data-slot="logo light" src={logoLight} alt={i18n.t("nav.logoAlt")} width="189" height="34" />
<img data-slot="logo dark" src={logoDark} alt={i18n.t("nav.logoAlt")} width="189" height="34" />
<img data-slot="logo light" src={logoLight} alt="OpenCode" width="189" height="34" />
<img data-slot="logo dark" src={logoDark} alt="OpenCode" width="189" height="34" />
</A>
</div>

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "الرئيسية",
"nav.openMenu": "فتح القائمة",
"nav.getStartedFree": "ابدأ مجانا",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "نسخ الشعار كـ SVG",
"nav.context.copyWordmark": "نسخ اسم العلامة كـ SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "الوثائق",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "شعار opencode الفاتح",
"notFound.logoDarkAlt": "شعار opencode الداكن",
"user.logout": "تسجيل الخروج",
"auth.callback.error.codeMissing": "لم يتم العثور على رمز التفويض.",
"workspace.select": "اختر مساحة العمل",
"workspace.createNew": "+ إنشاء مساحة عمل جديدة",
"workspace.modal.title": "إنشاء مساحة عمل جديدة",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "يجب أن يكون مبلغ الشحن ${{amount}} على الأقل",
"error.reloadTriggerMin": "يجب أن يكون حد الرصيد ${{amount}} على الأقل",
"app.meta.description": "OpenCode - وكيل البرمجة مفتوح المصدر.",
"home.title": "OpenCode | وكيل برمجة بالذكاء الاصطناعي مفتوح المصدر",
"temp.title": "opencode | وكيل برمجة بالذكاء الاصطناعي مبني للطرفية",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": "، بما في ذلك النماذج المحلية",
"temp.screenshot.caption": "واجهة OpenCode الطرفية مع سمة tokyonight",
"temp.screenshot.alt": "واجهة OpenCode الطرفية بسمة tokyonight",
"temp.logoLightAlt": "شعار opencode الفاتح",
"temp.logoDarkAlt": "شعار opencode الداكن",
"home.banner.badge": "جديد",
"home.banner.text": "تطبيق سطح المكتب متاح بنسخة تجريبية",
@@ -247,24 +238,6 @@ export const dict = {
"تتم استضافة جميع نماذج Zen في الولايات المتحدة. يتبع المزودون سياسة عدم الاحتفاظ بالبيانات ولا يستخدمون بياناتك لتدريب النماذج، مع",
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
"zen.api.error.modelFormatNotSupported": "النموذج {{model}} غير مدعوم للتنسيق {{format}}",
"zen.api.error.noProviderAvailable": "لا يوجد مزود متاح",
"zen.api.error.providerNotSupported": "المزود {{provider}} غير مدعوم",
"zen.api.error.missingApiKey": "مفتاح API مفقود.",
"zen.api.error.invalidApiKey": "مفتاح API غير صالح.",
"zen.api.error.subscriptionQuotaExceeded": "تم تجاوز حصة الاشتراك. أعد المحاولة خلال {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"تم تجاوز حصة الاشتراك. يمكنك الاستمرار في استخدام النماذج المجانية.",
"zen.api.error.noPaymentMethod": "لا توجد طريقة دفع. أضف طريقة دفع هنا: {{billingUrl}}",
"zen.api.error.insufficientBalance": "رصيد غير كاف. إدارة فواتيرك هنا: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"وصلت مساحة العمل الخاصة بك إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
"zen.api.error.modelDisabled": "النموذج معطل",
"black.meta.title": "OpenCode Black | الوصول إلى أفضل نماذج البرمجة في العالم",
"black.meta.description": "احصل على وصول إلى Claude، GPT، Gemini والمزيد مع خطط اشتراك OpenCode Black.",
"black.hero.title": "الوصول إلى أفضل نماذج البرمجة في العالم",
@@ -473,7 +446,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "يرجى تحديث طريقة الدفع والمحاولة مرة أخرى.",
"workspace.reload.retrying": "جارٍ إعادة المحاولة...",
"workspace.reload.retry": "أعد المحاولة",
"workspace.reload.error.paymentFailed": "فشلت عملية الدفع.",
"workspace.payments.title": "سجل المدفوعات",
"workspace.payments.subtitle": "معاملات الدفع الأخيرة.",
@@ -591,10 +563,6 @@ export const dict = {
"enterprise.form.send": "إرسال",
"enterprise.form.sending": "جارٍ الإرسال...",
"enterprise.form.success": "تم إرسال الرسالة، سنتواصل معك قريبًا.",
"enterprise.form.success.submitted": "تم إرسال النموذج بنجاح.",
"enterprise.form.error.allFieldsRequired": "جميع الحقول مطلوبة.",
"enterprise.form.error.invalidEmailFormat": "تنسيق البريد الإلكتروني غير صالح.",
"enterprise.form.error.internalServer": "خطأ داخلي في الخادم.",
"enterprise.faq.title": "الأسئلة الشائعة",
"enterprise.faq.q1": "ما هو OpenCode Enterprise؟",
"enterprise.faq.a1":
@@ -627,7 +595,6 @@ export const dict = {
"bench.list.table.agent": "الوكيل",
"bench.list.table.model": "النموذج",
"bench.list.table.score": "الدرجة",
"bench.submission.error.allFieldsRequired": "جميع الحقول مطلوبة.",
"bench.detail.title": "المعيار - {{task}}",
"bench.detail.notFound": "المهمة غير موجودة",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Início",
"nav.openMenu": "Abrir menu",
"nav.getStartedFree": "Começar grátis",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Copiar logo como SVG",
"nav.context.copyWordmark": "Copiar marca como SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Documentação",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "logo opencode claro",
"notFound.logoDarkAlt": "logo opencode escuro",
"user.logout": "Sair",
"auth.callback.error.codeMissing": "Nenhum código de autorização encontrado.",
"workspace.select": "Selecionar workspace",
"workspace.createNew": "+ Criar novo workspace",
"workspace.modal.title": "Criar novo workspace",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "O valor de recarga deve ser de pelo menos ${{amount}}",
"error.reloadTriggerMin": "O gatilho de saldo deve ser de pelo menos ${{amount}}",
"app.meta.description": "OpenCode - O agente de codificação de código aberto.",
"home.title": "OpenCode | O agente de codificação de código aberto com IA",
"temp.title": "opencode | Agente de codificação com IA feito para o terminal",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", incluindo modelos locais",
"temp.screenshot.caption": "OpenCode TUI com o tema tokyonight",
"temp.screenshot.alt": "OpenCode TUI com tema tokyonight",
"temp.logoLightAlt": "logo opencode claro",
"temp.logoDarkAlt": "logo opencode escuro",
"home.banner.badge": "Novo",
"home.banner.text": "App desktop disponível em beta",
@@ -251,24 +242,6 @@ export const dict = {
"Todos os modelos Zen são hospedados nos EUA. Os provedores seguem uma política de retenção zero e não usam seus dados para treinamento de modelo, com as",
"zen.privacy.exceptionsLink": "seguintes exceções",
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
"zen.api.error.modelFormatNotSupported": "Modelo {{model}} não suportado para o formato {{format}}",
"zen.api.error.noProviderAvailable": "Nenhum provedor disponível",
"zen.api.error.providerNotSupported": "Provedor {{provider}} não suportado",
"zen.api.error.missingApiKey": "Chave de API ausente.",
"zen.api.error.invalidApiKey": "Chave de API inválida.",
"zen.api.error.subscriptionQuotaExceeded": "Cota de assinatura excedida. Tente novamente em {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Cota de assinatura excedida. Você pode continuar usando modelos gratuitos.",
"zen.api.error.noPaymentMethod": "Nenhuma forma de pagamento. Adicione uma forma de pagamento aqui: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gerencie seu faturamento aqui: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Seu workspace atingiu o limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
"zen.api.error.modelDisabled": "O modelo está desabilitado",
"black.meta.title": "OpenCode Black | Acesse os melhores modelos de codificação do mundo",
"black.meta.description": "Tenha acesso ao Claude, GPT, Gemini e mais com os planos de assinatura OpenCode Black.",
"black.hero.title": "Acesse os melhores modelos de codificação do mundo",
@@ -478,7 +451,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Por favor, atualize sua forma de pagamento e tente novamente.",
"workspace.reload.retrying": "Tentando novamente...",
"workspace.reload.retry": "Tentar novamente",
"workspace.reload.error.paymentFailed": "Pagamento falhou.",
"workspace.payments.title": "Histórico de Pagamentos",
"workspace.payments.subtitle": "Transações de pagamento recentes.",
@@ -599,10 +571,6 @@ export const dict = {
"enterprise.form.send": "Enviar",
"enterprise.form.sending": "Enviando...",
"enterprise.form.success": "Mensagem enviada, entraremos em contato em breve.",
"enterprise.form.success.submitted": "Formulário enviado com sucesso.",
"enterprise.form.error.allFieldsRequired": "Todos os campos são obrigatórios.",
"enterprise.form.error.invalidEmailFormat": "Formato de e-mail inválido.",
"enterprise.form.error.internalServer": "Erro interno do servidor.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "O que é OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -635,7 +603,6 @@ export const dict = {
"bench.list.table.agent": "Agente",
"bench.list.table.model": "Modelo",
"bench.list.table.score": "Pontuação",
"bench.submission.error.allFieldsRequired": "Todos os campos são obrigatórios.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Tarefa não encontrada",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Hjem",
"nav.openMenu": "Åbn menu",
"nav.getStartedFree": "Kom i gang gratis",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Kopier logo som SVG",
"nav.context.copyWordmark": "Kopier wordmark som SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Dokumentation",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo light",
"notFound.logoDarkAlt": "opencode logo dark",
"user.logout": "Log ud",
"auth.callback.error.codeMissing": "Ingen autorisationskode fundet.",
"workspace.select": "Vælg workspace",
"workspace.createNew": "+ Opret nyt workspace",
"workspace.modal.title": "Opret nyt workspace",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "Genopfyldningsbeløb skal være mindst ${{amount}}",
"error.reloadTriggerMin": "Saldogrænse skal være mindst ${{amount}}",
"app.meta.description": "OpenCode - Den open source kodningsagent.",
"home.title": "OpenCode | Den open source AI-kodningsagent",
"temp.title": "opencode | AI-kodningsagent bygget til terminalen",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", inklusive lokale modeller",
"temp.screenshot.caption": "opencode TUI med tokyonight-temaet",
"temp.screenshot.alt": "opencode TUI med tokyonight-temaet",
"temp.logoLightAlt": "opencode logo light",
"temp.logoDarkAlt": "opencode logo dark",
"home.banner.badge": "Ny",
"home.banner.text": "Desktop-app tilgængelig i beta",
@@ -249,24 +240,6 @@ export const dict = {
"Alle Zen-modeller er hostet i USA. Udbydere følger en nulopbevaringspolitik og bruger ikke dine data til modeltræning med",
"zen.privacy.exceptionsLink": "følgende undtagelser",
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
"zen.api.error.modelFormatNotSupported": "Model {{model}} understøttes ikke for format {{format}}",
"zen.api.error.noProviderAvailable": "Ingen udbyder tilgængelig",
"zen.api.error.providerNotSupported": "Udbyder {{provider}} understøttes ikke",
"zen.api.error.missingApiKey": "Manglende API-nøgle.",
"zen.api.error.invalidApiKey": "Ugyldig API-nøgle.",
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igen om {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Abonnementskvote overskredet. Du kan fortsætte med at bruge gratis modeller.",
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Tilføj en betalingsmetode her: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Utilstrækkelig saldo. Administrer din fakturering her: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Dit workspace har nået sin månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
"black.meta.title": "OpenCode Black | Få adgang til verdens bedste kodningsmodeller",
"black.meta.description": "Få adgang til Claude, GPT, Gemini og mere med OpenCode Black-abonnementer.",
"black.hero.title": "Få adgang til verdens bedste kodningsmodeller",
@@ -476,7 +449,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Opdater din betalingsmetode, og prøv igen.",
"workspace.reload.retrying": "Prøver igen...",
"workspace.reload.retry": "Prøv igen",
"workspace.reload.error.paymentFailed": "Betaling mislykkedes.",
"workspace.payments.title": "Betalingshistorik",
"workspace.payments.subtitle": "Seneste betalingstransaktioner.",
@@ -595,10 +567,6 @@ export const dict = {
"enterprise.form.send": "Send",
"enterprise.form.sending": "Sender...",
"enterprise.form.success": "Besked sendt, vi vender tilbage snart.",
"enterprise.form.success.submitted": "Formular indsendt med succes.",
"enterprise.form.error.allFieldsRequired": "Alle felter er påkrævet.",
"enterprise.form.error.invalidEmailFormat": "Ugyldigt e-mailformat.",
"enterprise.form.error.internalServer": "Intern serverfejl.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Hvad er OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -631,7 +599,6 @@ export const dict = {
"bench.list.table.agent": "Agent",
"bench.list.table.model": "Model",
"bench.list.table.score": "Score",
"bench.submission.error.allFieldsRequired": "Alle felter er påkrævet.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Opgave ikke fundet",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Startseite",
"nav.openMenu": "Menü öffnen",
"nav.getStartedFree": "Kostenlos starten",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Logo als SVG kopieren",
"nav.context.copyWordmark": "Wortmarke als SVG kopieren",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Dokumentation",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "OpenCode Logo hell",
"notFound.logoDarkAlt": "OpenCode Logo dunkel",
"user.logout": "Abmelden",
"auth.callback.error.codeMissing": "Kein Autorisierungscode gefunden.",
"workspace.select": "Workspace auswählen",
"workspace.createNew": "+ Neuen Workspace erstellen",
"workspace.modal.title": "Neuen Workspace erstellen",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "Aufladebetrag muss mindestens ${{amount}} betragen",
"error.reloadTriggerMin": "Guthaben-Auslöser muss mindestens ${{amount}} betragen",
"app.meta.description": "OpenCode - Der Open-Source Coding-Agent.",
"home.title": "OpenCode | Der Open-Source AI-Coding-Agent",
"temp.title": "OpenCode | Für das Terminal gebauter AI-Coding-Agent",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", einschließlich lokaler Modelle",
"temp.screenshot.caption": "OpenCode TUI mit dem Tokyonight-Theme",
"temp.screenshot.alt": "OpenCode TUI mit Tokyonight-Theme",
"temp.logoLightAlt": "OpenCode Logo hell",
"temp.logoDarkAlt": "OpenCode Logo dunkel",
"home.banner.badge": "Neu",
"home.banner.text": "Desktop-App in der Beta verfügbar",
@@ -251,24 +242,6 @@ export const dict = {
"Alle Zen-Modelle werden in den USA gehostet. Anbieter folgen einer Zero-Retention-Policy und nutzen deine Daten nicht für Modelltraining, mit den",
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
"zen.api.error.modelFormatNotSupported": "Modell {{model}} wird für das Format {{format}} nicht unterstützt",
"zen.api.error.noProviderAvailable": "Kein Anbieter verfügbar",
"zen.api.error.providerNotSupported": "Anbieter {{provider}} wird nicht unterstützt",
"zen.api.error.missingApiKey": "Fehlender API-Key.",
"zen.api.error.invalidApiKey": "Ungültiger API-Key.",
"zen.api.error.subscriptionQuotaExceeded": "Abonnement-Quote überschritten. Erneuter Versuch in {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Abonnement-Quote überschritten. Du kannst weiterhin kostenlose Modelle nutzen.",
"zen.api.error.noPaymentMethod": "Keine Zahlungsmethode. Füge hier eine Zahlungsmethode hinzu: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Unzureichendes Guthaben. Verwalte deine Abrechnung hier: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Dein Workspace hat sein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Du hast dein monatliches Ausgabenlimit von ${{amount}} erreicht. Verwalte deine Limits hier: {{membersUrl}}",
"zen.api.error.modelDisabled": "Modell ist deaktiviert",
"black.meta.title": "OpenCode Black | Zugriff auf die weltweit besten Coding-Modelle",
"black.meta.description": "Erhalte Zugriff auf Claude, GPT, Gemini und mehr mit OpenCode Black Abos.",
"black.hero.title": "Zugriff auf die weltweit besten Coding-Modelle",
@@ -478,7 +451,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Bitte aktualisiere deine Zahlungsmethode und versuche es erneut.",
"workspace.reload.retrying": "Versuche erneut...",
"workspace.reload.retry": "Erneut versuchen",
"workspace.reload.error.paymentFailed": "Zahlung fehlgeschlagen.",
"workspace.payments.title": "Zahlungshistorie",
"workspace.payments.subtitle": "Kürzliche Zahlungstransaktionen.",
@@ -599,10 +571,6 @@ export const dict = {
"enterprise.form.send": "Senden",
"enterprise.form.sending": "Sende...",
"enterprise.form.success": "Nachricht gesendet, wir melden uns bald.",
"enterprise.form.success.submitted": "Formular erfolgreich gesendet.",
"enterprise.form.error.allFieldsRequired": "Alle Felder sind erforderlich.",
"enterprise.form.error.invalidEmailFormat": "Ungültiges E-Mail-Format.",
"enterprise.form.error.internalServer": "Interner Serverfehler.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Was ist OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -635,7 +603,6 @@ export const dict = {
"bench.list.table.agent": "Agent",
"bench.list.table.model": "Modell",
"bench.list.table.score": "Score",
"bench.submission.error.allFieldsRequired": "Alle Felder sind erforderlich.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Task nicht gefunden",

View File

@@ -11,7 +11,6 @@ export const dict = {
"nav.home": "Home",
"nav.openMenu": "Open menu",
"nav.getStartedFree": "Get started for free",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Copy logo as SVG",
"nav.context.copyWordmark": "Copy wordmark as SVG",
@@ -39,13 +38,9 @@ export const dict = {
"notFound.docs": "Docs",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo light",
"notFound.logoDarkAlt": "opencode logo dark",
"user.logout": "Logout",
"auth.callback.error.codeMissing": "No authorization code found.",
"workspace.select": "Select workspace",
"workspace.createNew": "+ Create New Workspace",
"workspace.modal.title": "Create New Workspace",
@@ -77,8 +72,6 @@ export const dict = {
"error.reloadAmountMin": "Reload amount must be at least ${{amount}}",
"error.reloadTriggerMin": "Balance trigger must be at least ${{amount}}",
"app.meta.description": "OpenCode - The open source coding agent.",
"home.title": "OpenCode | The open source AI coding agent",
"temp.title": "opencode | AI coding agent built for the terminal",
@@ -94,8 +87,6 @@ export const dict = {
"temp.feature.models.afterLink": ", including local models",
"temp.screenshot.caption": "opencode TUI with the tokyonight theme",
"temp.screenshot.alt": "opencode TUI with tokyonight theme",
"temp.logoLightAlt": "opencode logo light",
"temp.logoDarkAlt": "opencode logo dark",
"home.banner.badge": "New",
"home.banner.text": "Desktop app available in beta",
@@ -243,24 +234,6 @@ export const dict = {
"All Zen models are hosted in the US. Providers follow a zero-retention policy and do not use your data for model training, with the",
"zen.privacy.exceptionsLink": "following exceptions",
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
"zen.api.error.modelNotSupported": "Model {{model}} not supported",
"zen.api.error.modelFormatNotSupported": "Model {{model}} not supported for format {{format}}",
"zen.api.error.noProviderAvailable": "No provider available",
"zen.api.error.providerNotSupported": "Provider {{provider}} not supported",
"zen.api.error.missingApiKey": "Missing API key.",
"zen.api.error.invalidApiKey": "Invalid API key.",
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Subscription quota exceeded. You can continue using free models.",
"zen.api.error.noPaymentMethod": "No payment method. Add a payment method here: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Insufficient balance. Manage your billing here: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Your workspace has reached its monthly spending limit of ${{amount}}. Manage your limits here: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"You have reached your monthly spending limit of ${{amount}}. Manage your limits here: {{membersUrl}}",
"zen.api.error.modelDisabled": "Model is disabled",
"black.meta.title": "OpenCode Black | Access all the world's best coding models",
"black.meta.description": "Get access to Claude, GPT, Gemini and more with OpenCode Black subscription plans.",
"black.hero.title": "Access all the world's best coding models",
@@ -470,7 +443,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Please update your payment method and try again.",
"workspace.reload.retrying": "Retrying...",
"workspace.reload.retry": "Retry",
"workspace.reload.error.paymentFailed": "Payment failed.",
"workspace.payments.title": "Payments History",
"workspace.payments.subtitle": "Recent payment transactions.",
@@ -589,10 +561,6 @@ export const dict = {
"enterprise.form.send": "Send",
"enterprise.form.sending": "Sending...",
"enterprise.form.success": "Message sent, we'll be in touch soon.",
"enterprise.form.success.submitted": "Form submitted successfully.",
"enterprise.form.error.allFieldsRequired": "All fields are required.",
"enterprise.form.error.invalidEmailFormat": "Invalid email format.",
"enterprise.form.error.internalServer": "Internal server error.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "What is OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -625,7 +593,6 @@ export const dict = {
"bench.list.table.agent": "Agent",
"bench.list.table.model": "Model",
"bench.list.table.score": "Score",
"bench.submission.error.allFieldsRequired": "All fields are required.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Task not found",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Inicio",
"nav.openMenu": "Abrir menú",
"nav.getStartedFree": "Empezar gratis",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Copiar logo como SVG",
"nav.context.copyWordmark": "Copiar marca como SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Documentación",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo claro",
"notFound.logoDarkAlt": "opencode logo oscuro",
"user.logout": "Cerrar sesión",
"auth.callback.error.codeMissing": "No se encontró código de autorización.",
"workspace.select": "Seleccionar espacio de trabajo",
"workspace.createNew": "+ Crear nuevo espacio de trabajo",
"workspace.modal.title": "Crear nuevo espacio de trabajo",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "La cantidad de recarga debe ser al menos ${{amount}}",
"error.reloadTriggerMin": "El disparador de saldo debe ser al menos ${{amount}}",
"app.meta.description": "OpenCode - El agente de codificación de código abierto.",
"home.title": "OpenCode | El agente de codificación IA de código abierto",
"temp.title": "opencode | Agente de codificación IA creado para la terminal",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", incluyendo modelos locales",
"temp.screenshot.caption": "opencode TUI con el tema tokyonight",
"temp.screenshot.alt": "opencode TUI con tema tokyonight",
"temp.logoLightAlt": "logo de opencode claro",
"temp.logoDarkAlt": "logo de opencode oscuro",
"home.banner.badge": "Nuevo",
"home.banner.text": "Aplicación de escritorio disponible en beta",
@@ -252,24 +243,6 @@ export const dict = {
"Todos los modelos Zen están alojados en EE. UU. Los proveedores siguen una política de cero retención y no usan tus datos para entrenamiento de modelos, con las",
"zen.privacy.exceptionsLink": "siguientes excepciones",
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
"zen.api.error.modelFormatNotSupported": "Modelo {{model}} no soportado para el formato {{format}}",
"zen.api.error.noProviderAvailable": "Ningún proveedor disponible",
"zen.api.error.providerNotSupported": "Proveedor {{provider}} no soportado",
"zen.api.error.missingApiKey": "Falta la clave API.",
"zen.api.error.invalidApiKey": "Clave API inválida.",
"zen.api.error.subscriptionQuotaExceeded": "Cuota de suscripción excedida. Reintenta en {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Cuota de suscripción excedida. Puedes continuar usando modelos gratuitos.",
"zen.api.error.noPaymentMethod": "Sin método de pago. Añade un método de pago aquí: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gestiona tu facturación aquí: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Tu espacio de trabajo ha alcanzado su límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Has alcanzado tu límite de gasto mensual de ${{amount}}. Gestiona tus límites aquí: {{membersUrl}}",
"zen.api.error.modelDisabled": "El modelo está deshabilitado",
"black.meta.title": "OpenCode Black | Accede a los mejores modelos de codificación del mundo",
"black.meta.description": "Obtén acceso a Claude, GPT, Gemini y más con los planes de suscripción de OpenCode Black.",
"black.hero.title": "Accede a los mejores modelos de codificación del mundo",
@@ -479,7 +452,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Por favor actualiza tu método de pago e intenta de nuevo.",
"workspace.reload.retrying": "Reintentando...",
"workspace.reload.retry": "Reintentar",
"workspace.reload.error.paymentFailed": "El pago falló.",
"workspace.payments.title": "Historial de Pagos",
"workspace.payments.subtitle": "Transacciones de pago recientes.",
@@ -599,10 +571,6 @@ export const dict = {
"enterprise.form.send": "Enviar",
"enterprise.form.sending": "Enviando...",
"enterprise.form.success": "Mensaje enviado, estaremos en contacto pronto.",
"enterprise.form.success.submitted": "Formulario enviado con éxito.",
"enterprise.form.error.allFieldsRequired": "Todos los campos son obligatorios.",
"enterprise.form.error.invalidEmailFormat": "Formato de correo inválido.",
"enterprise.form.error.internalServer": "Error interno del servidor.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "¿Qué es OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -635,7 +603,6 @@ export const dict = {
"bench.list.table.agent": "Agente",
"bench.list.table.model": "Modelo",
"bench.list.table.score": "Puntuación",
"bench.submission.error.allFieldsRequired": "Todos los campos son obligatorios.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Tarea no encontrada",

View File

@@ -3,7 +3,6 @@ import { dict as en } from "./en"
export const dict = {
...en,
"app.meta.description": "OpenCode - L'agent de code open source.",
"nav.github": "GitHub",
"nav.docs": "Documentation",
"nav.changelog": "Changelog",
@@ -16,7 +15,6 @@ export const dict = {
"nav.home": "Accueil",
"nav.openMenu": "Ouvrir le menu",
"nav.getStartedFree": "Commencer gratuitement",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Copier le logo en SVG",
"nav.context.copyWordmark": "Copier le logotype en SVG",
@@ -44,8 +42,6 @@ export const dict = {
"notFound.docs": "Documentation",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo light",
"notFound.logoDarkAlt": "opencode logo dark",
"user.logout": "Se déconnecter",
@@ -79,7 +75,6 @@ export const dict = {
"error.modelRequired": "Le modèle est requis",
"error.reloadAmountMin": "Le montant de recharge doit être d'au moins {{amount}} $",
"error.reloadTriggerMin": "Le seuil de déclenchement doit être d'au moins {{amount}} $",
"auth.callback.error.codeMissing": "Aucun code d'autorisation trouvé.",
"home.title": "OpenCode | L'agent de code IA open source",
@@ -96,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", y compris les modèles locaux",
"temp.screenshot.caption": "OpenCode TUI avec le thème tokyonight",
"temp.screenshot.alt": "OpenCode TUI avec le thème tokyonight",
"temp.logoLightAlt": "opencode logo light",
"temp.logoDarkAlt": "opencode logo dark",
"home.banner.badge": "Nouveau",
"home.banner.text": "Application desktop disponible en bêta",
@@ -253,24 +246,6 @@ export const dict = {
"Tous les modèles Zen sont hébergés aux États-Unis. Les fournisseurs suivent une politique de rétention zéro et n'utilisent pas vos données pour l'entraînement des modèles, avec les",
"zen.privacy.exceptionsLink": "exceptions suivantes",
"zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.",
"zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge",
"zen.api.error.modelFormatNotSupported": "Modèle {{model}} non pris en charge pour le format {{format}}",
"zen.api.error.noProviderAvailable": "Aucun fournisseur disponible",
"zen.api.error.providerNotSupported": "Fournisseur {{provider}} non pris en charge",
"zen.api.error.missingApiKey": "Clé API manquante.",
"zen.api.error.invalidApiKey": "Clé API invalide.",
"zen.api.error.subscriptionQuotaExceeded": "Quota d'abonnement dépassé. Réessayez dans {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Quota d'abonnement dépassé. Vous pouvez continuer à utiliser les modèles gratuits.",
"zen.api.error.noPaymentMethod": "Aucune méthode de paiement. Ajoutez une méthode de paiement ici : {{billingUrl}}",
"zen.api.error.insufficientBalance": "Solde insuffisant. Gérez votre facturation ici : {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Votre espace de travail a atteint sa limite de dépense mensuelle de {{amount}} $. Gérez vos limites ici : {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Vous avez atteint votre limite de dépense mensuelle de {{amount}} $. Gérez vos limites ici : {{membersUrl}}",
"zen.api.error.modelDisabled": "Le modèle est désactivé",
"black.meta.title": "OpenCode Black | Accédez aux meilleurs modèles de code au monde",
"black.meta.description": "Accédez à Claude, GPT, Gemini et plus avec les forfaits d'abonnement OpenCode Black.",
"black.hero.title": "Accédez aux meilleurs modèles de code au monde",
@@ -482,7 +457,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Veuillez mettre à jour votre méthode de paiement et réessayer.",
"workspace.reload.retrying": "Nouvelle tentative...",
"workspace.reload.retry": "Réessayer",
"workspace.reload.error.paymentFailed": "Échec du paiement.",
"workspace.payments.title": "Historique des paiements",
"workspace.payments.subtitle": "Transactions de paiement récentes.",
@@ -607,10 +581,6 @@ export const dict = {
"enterprise.form.send": "Envoyer",
"enterprise.form.sending": "Envoi...",
"enterprise.form.success": "Message envoyé, nous vous contacterons bientôt.",
"enterprise.form.success.submitted": "Formulaire soumis avec succès.",
"enterprise.form.error.allFieldsRequired": "Tous les champs sont requis.",
"enterprise.form.error.invalidEmailFormat": "Format d'e-mail invalide.",
"enterprise.form.error.internalServer": "Erreur interne du serveur.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Qu'est-ce que OpenCode Enterprise ?",
"enterprise.faq.a1":
@@ -670,5 +640,4 @@ export const dict = {
"bench.detail.table.duration": "Durée",
"bench.detail.run.title": "Exécution {{n}}",
"bench.detail.rawJson": "JSON brut",
"bench.submission.error.allFieldsRequired": "Tous les champs sont requis.",
} satisfies Dict

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Home",
"nav.openMenu": "Apri menu",
"nav.getStartedFree": "Inizia gratis",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Copia il logo come SVG",
"nav.context.copyWordmark": "Copia il wordmark come SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Documentazione",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "logo chiaro di opencode",
"notFound.logoDarkAlt": "logo scuro di opencode",
"user.logout": "Esci",
"auth.callback.error.codeMissing": "Nessun codice di autorizzazione trovato.",
"workspace.select": "Seleziona workspace",
"workspace.createNew": "+ Crea nuovo workspace",
"workspace.modal.title": "Crea nuovo workspace",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "L'importo della ricarica deve essere almeno ${{amount}}",
"error.reloadTriggerMin": "La soglia del saldo deve essere almeno ${{amount}}",
"app.meta.description": "OpenCode - L'agente di programmazione open source.",
"home.title": "OpenCode | L'agente di coding IA open source",
"temp.title": "opencode | Agente di coding IA costruito per il terminale",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", inclusi modelli locali",
"temp.screenshot.caption": "OpenCode TUI con il tema tokyonight",
"temp.screenshot.alt": "OpenCode TUI con tema tokyonight",
"temp.logoLightAlt": "logo chiaro di opencode",
"temp.logoDarkAlt": "logo scuro di opencode",
"home.banner.badge": "Nuovo",
"home.banner.text": "App desktop disponibile in beta",
@@ -249,24 +240,6 @@ export const dict = {
"Tutti i modelli Zen sono ospitati negli Stati Uniti. I provider seguono una policy di zero-retention e non usano i tuoi dati per l'addestramento dei modelli, con le",
"zen.privacy.exceptionsLink": "seguenti eccezioni",
"zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.",
"zen.api.error.modelNotSupported": "Modello {{model}} non supportato",
"zen.api.error.modelFormatNotSupported": "Modello {{model}} non supportato per il formato {{format}}",
"zen.api.error.noProviderAvailable": "Nessun provider disponibile",
"zen.api.error.providerNotSupported": "Provider {{provider}} non supportato",
"zen.api.error.missingApiKey": "Chiave API mancante.",
"zen.api.error.invalidApiKey": "Chiave API non valida.",
"zen.api.error.subscriptionQuotaExceeded": "Quota dell'abbonamento superata. Riprova tra {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Quota dell'abbonamento superata. Puoi continuare a utilizzare modelli gratuiti.",
"zen.api.error.noPaymentMethod": "Nessun metodo di pagamento. Aggiungi un metodo di pagamento qui: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Saldo insufficiente. Gestisci la tua fatturazione qui: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"La tua area di lavoro ha raggiunto il limite di spesa mensile di ${{amount}}. Gestisci i tuoi limiti qui: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Hai raggiunto il tuo limite di spesa mensile di ${{amount}}. Gestisci i tuoi limiti qui: {{membersUrl}}",
"zen.api.error.modelDisabled": "Il modello è disabilitato",
"black.meta.title": "OpenCode Black | Accedi ai migliori modelli di coding al mondo",
"black.meta.description":
"Ottieni l'accesso a Claude, GPT, Gemini e altri con i piani di abbonamento OpenCode Black.",
@@ -478,7 +451,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Aggiorna il tuo metodo di pagamento e riprova.",
"workspace.reload.retrying": "Riprovo...",
"workspace.reload.retry": "Riprova",
"workspace.reload.error.paymentFailed": "Pagamento fallito.",
"workspace.payments.title": "Cronologia Pagamenti",
"workspace.payments.subtitle": "Transazioni di pagamento recenti.",
@@ -597,10 +569,6 @@ export const dict = {
"enterprise.form.send": "Invia",
"enterprise.form.sending": "Invio...",
"enterprise.form.success": "Messaggio inviato, ti contatteremo presto.",
"enterprise.form.success.submitted": "Modulo inviato con successo.",
"enterprise.form.error.allFieldsRequired": "Tutti i campi sono obbligatori.",
"enterprise.form.error.invalidEmailFormat": "Formato email non valido.",
"enterprise.form.error.internalServer": "Errore interno del server.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Cos'è OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -633,7 +601,6 @@ export const dict = {
"bench.list.table.agent": "Agente",
"bench.list.table.model": "Modello",
"bench.list.table.score": "Punteggio",
"bench.submission.error.allFieldsRequired": "Tutti i campi sono obbligatori.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Task non trovato",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "ホーム",
"nav.openMenu": "メニューを開く",
"nav.getStartedFree": "無料ではじめる",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "ロゴをSVGでコピー",
"nav.context.copyWordmark": "ワードマークをSVGでコピー",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "ドキュメント",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencodeのロゴライト",
"notFound.logoDarkAlt": "opencodeのロゴダーク",
"user.logout": "ログアウト",
"auth.callback.error.codeMissing": "認証コードが見つかりません。",
"workspace.select": "ワークスペースを選択",
"workspace.createNew": "+ 新しいワークスペースを作成",
"workspace.modal.title": "新しいワークスペースを作成",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "リロード額は少なくとも ${{amount}} である必要があります",
"error.reloadTriggerMin": "残高トリガーは少なくとも ${{amount}} である必要があります",
"app.meta.description": "OpenCode - オープンソースのコーディングエージェント。",
"home.title": "OpenCode | オープンソースのAIコーディングエージェント",
"temp.title": "OpenCode | ターミナル向けに構築されたAIコーディングエージェント",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": "を通じて75以上のLLMプロバイダーをサポート",
"temp.screenshot.caption": "tokyonight テーマを使用した OpenCode TUI",
"temp.screenshot.alt": "tokyonight テーマの OpenCode TUI",
"temp.logoLightAlt": "opencodeのロゴライト",
"temp.logoDarkAlt": "opencodeのロゴダーク",
"home.banner.badge": "新着",
"home.banner.text": "デスクトップアプリのベータ版が利用可能",
@@ -248,25 +239,6 @@ export const dict = {
"すべてのZenモデルは米国でホストされています。プロバイダーはゼロ保持ポリシーに従い、モデルのトレーニングにデータを使用しません",
"zen.privacy.exceptionsLink": "以下の例外",
"zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。",
"zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません",
"zen.api.error.modelFormatNotSupported": "フォーマット {{format}} ではモデル {{model}} はサポートされていません",
"zen.api.error.noProviderAvailable": "利用可能なプロバイダーがありません",
"zen.api.error.providerNotSupported": "プロバイダー {{provider}} はサポートされていません",
"zen.api.error.missingApiKey": "APIキーがありません。",
"zen.api.error.invalidApiKey": "無効なAPIキーです。",
"zen.api.error.subscriptionQuotaExceeded":
"サブスクリプションの制限を超えました。{{retryIn}} 後に再試行してください。",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"サブスクリプションの制限を超えました。無料モデルは引き続きご利用いただけます。",
"zen.api.error.noPaymentMethod": "お支払い方法がありません。こちらからお支払い方法を追加してください: {{billingUrl}}",
"zen.api.error.insufficientBalance": "残高が不足しています。こちらから請求を管理してください: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"ワークスペースが月額の利用上限 ${{amount}} に達しました。こちらから上限を管理してください: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"月額の利用上限 ${{amount}} に達しました。こちらから上限を管理してください: {{membersUrl}}",
"zen.api.error.modelDisabled": "モデルが無効です",
"black.meta.title": "OpenCode Black | 世界最高峰のコーディングモデルすべてにアクセス",
"black.meta.description": "OpenCode Black サブスクリプションプランで、Claude、GPT、Gemini などにアクセス。",
"black.hero.title": "世界最高峰のコーディングモデルすべてにアクセス",
@@ -476,7 +448,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "支払い方法を更新して、もう一度お試しください。",
"workspace.reload.retrying": "再試行中...",
"workspace.reload.retry": "再試行",
"workspace.reload.error.paymentFailed": "支払いに失敗しました。",
"workspace.payments.title": "支払い履歴",
"workspace.payments.subtitle": "最近の支払い取引。",
@@ -597,10 +568,6 @@ export const dict = {
"enterprise.form.send": "送信",
"enterprise.form.sending": "送信中...",
"enterprise.form.success": "送信しました。まもなくご連絡いたします。",
"enterprise.form.success.submitted": "フォームが正常に送信されました。",
"enterprise.form.error.allFieldsRequired": "すべての項目は必須です。",
"enterprise.form.error.invalidEmailFormat": "無効なメール形式です。",
"enterprise.form.error.internalServer": "内部サーバーエラー。",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "OpenCode Enterpriseとは",
"enterprise.faq.a1":
@@ -633,7 +600,6 @@ export const dict = {
"bench.list.table.agent": "エージェント",
"bench.list.table.model": "モデル",
"bench.list.table.score": "スコア",
"bench.submission.error.allFieldsRequired": "すべての項目は必須です。",
"bench.detail.title": "ベンチマーク - {{task}}",
"bench.detail.notFound": "タスクが見つかりません",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "홈",
"nav.openMenu": "메뉴 열기",
"nav.getStartedFree": "무료로 시작하기",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "로고를 SVG로 복사",
"nav.context.copyWordmark": "워드마크를 SVG로 복사",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "문서",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode 밝은 로고",
"notFound.logoDarkAlt": "opencode 어두운 로고",
"user.logout": "로그아웃",
"auth.callback.error.codeMissing": "인증 코드를 찾을 수 없습니다.",
"workspace.select": "워크스페이스 선택",
"workspace.createNew": "+ 새 워크스페이스 만들기",
"workspace.modal.title": "새 워크스페이스 만들기",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "충전 금액은 최소 ${{amount}}이어야 합니다",
"error.reloadTriggerMin": "잔액 트리거는 최소 ${{amount}}이어야 합니다",
"app.meta.description": "OpenCode - 오픈 소스 코딩 에이전트.",
"home.title": "OpenCode | 오픈 소스 AI 코딩 에이전트",
"temp.title": "OpenCode | 터미널을 위해 만들어진 AI 코딩 에이전트",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": "를 통해 75개 이상의 LLM 제공자 지원",
"temp.screenshot.caption": "tokyonight 테마가 적용된 OpenCode TUI",
"temp.screenshot.alt": "tokyonight 테마가 적용된 OpenCode TUI",
"temp.logoLightAlt": "opencode 밝은 로고",
"temp.logoDarkAlt": "opencode 어두운 로고",
"home.banner.badge": "신규",
"home.banner.text": "데스크톱 앱 베타 버전 출시",
@@ -245,24 +236,6 @@ export const dict = {
"모든 Zen 모델은 미국에서 호스팅됩니다. 제공자들은 데이터 보존 금지 정책을 따르며 모델 학습에 데이터를 사용하지 않습니다. 단,",
"zen.privacy.exceptionsLink": "다음 예외",
"zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.",
"zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다",
"zen.api.error.modelFormatNotSupported": "{{model}} 모델은 {{format}} 형식에 대해 지원되지 않습니다",
"zen.api.error.noProviderAvailable": "사용 가능한 제공자가 없습니다",
"zen.api.error.providerNotSupported": "{{provider}} 제공자는 지원되지 않습니다",
"zen.api.error.missingApiKey": "API 키가 누락되었습니다.",
"zen.api.error.invalidApiKey": "유효하지 않은 API 키입니다.",
"zen.api.error.subscriptionQuotaExceeded": "구독 할당량을 초과했습니다. {{retryIn}} 후 다시 시도해 주세요.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"구독 할당량을 초과했습니다. 무료 모델은 계속 사용할 수 있습니다.",
"zen.api.error.noPaymentMethod": "결제 수단이 없습니다. 결제 수단을 추가하세요: {{billingUrl}}",
"zen.api.error.insufficientBalance": "잔액이 부족합니다. 결제 관리를 여기서 하세요: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"워크스페이스의 월간 지출 한도인 ${{amount}}에 도달했습니다. 한도 관리를 여기서 하세요: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"월간 지출 한도인 ${{amount}}에 도달했습니다. 한도 관리를 여기서 하세요: {{membersUrl}}",
"zen.api.error.modelDisabled": "모델이 비활성화되었습니다",
"black.meta.title": "OpenCode Black | 세계 최고의 코딩 모델에 액세스하세요",
"black.meta.description": "OpenCode Black 구독 플랜으로 Claude, GPT, Gemini 등에 액세스하세요.",
"black.hero.title": "세계 최고의 코딩 모델에 액세스하세요",
@@ -472,7 +445,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "결제 수단을 업데이트하고 다시 시도해 주세요.",
"workspace.reload.retrying": "재시도 중...",
"workspace.reload.retry": "재시도",
"workspace.reload.error.paymentFailed": "결제에 실패했습니다.",
"workspace.payments.title": "결제 내역",
"workspace.payments.subtitle": "최근 결제 거래 내역입니다.",
@@ -590,10 +562,6 @@ export const dict = {
"enterprise.form.send": "전송",
"enterprise.form.sending": "전송 중...",
"enterprise.form.success": "메시지가 전송되었습니다. 곧 연락드리겠습니다.",
"enterprise.form.success.submitted": "양식이 성공적으로 제출되었습니다.",
"enterprise.form.error.allFieldsRequired": "모든 필드는 필수 항목입니다.",
"enterprise.form.error.invalidEmailFormat": "유효하지 않은 이메일 형식입니다.",
"enterprise.form.error.internalServer": "내부 서버 오류입니다.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "OpenCode 엔터프라이즈란 무엇인가요?",
"enterprise.faq.a1":
@@ -626,7 +594,6 @@ export const dict = {
"bench.list.table.agent": "에이전트",
"bench.list.table.model": "모델",
"bench.list.table.score": "점수",
"bench.submission.error.allFieldsRequired": "모든 필드는 필수 항목입니다.",
"bench.detail.title": "벤치마크 - {{task}}",
"bench.detail.notFound": "태스크를 찾을 수 없음",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Hjem",
"nav.openMenu": "Åpne meny",
"nav.getStartedFree": "Kom i gang gratis",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Kopier logo som SVG",
"nav.context.copyWordmark": "Kopier wordmark som SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Dokumentasjon",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo lys",
"notFound.logoDarkAlt": "opencode logo mørk",
"user.logout": "Logg ut",
"auth.callback.error.codeMissing": "Ingen autorisasjonskode funnet.",
"workspace.select": "Velg arbeidsområde",
"workspace.createNew": "+ Opprett nytt arbeidsområde",
"workspace.modal.title": "Opprett nytt arbeidsområde",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "Påfyllingsbeløp må være minst ${{amount}}",
"error.reloadTriggerMin": "Saldo-trigger må være minst ${{amount}}",
"app.meta.description": "OpenCode - Den åpne kildekode kodingsagenten.",
"home.title": "OpenCode | Den åpne kildekode AI-kodingsagenten",
"temp.title": "opencode | AI-kodingsagent bygget for terminalen",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", inkludert lokale modeller",
"temp.screenshot.caption": "opencode TUI med tokyonight-tema",
"temp.screenshot.alt": "opencode TUI med tokyonight-tema",
"temp.logoLightAlt": "opencode logo lys",
"temp.logoDarkAlt": "opencode logo mørk",
"home.banner.badge": "Ny",
"home.banner.text": "Desktop-app tilgjengelig i beta",
@@ -249,24 +240,6 @@ export const dict = {
"Alle Zen-modeller hostes i USA. Leverandører følger en policy om null oppbevaring og bruker ikke dataene dine til modelltrening, med",
"zen.privacy.exceptionsLink": "følgende unntak",
"zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.",
"zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke",
"zen.api.error.modelFormatNotSupported": "Modell {{model}} støttes ikke for format {{format}}",
"zen.api.error.noProviderAvailable": "Ingen leverandør tilgjengelig",
"zen.api.error.providerNotSupported": "Leverandør {{provider}} støttes ikke",
"zen.api.error.missingApiKey": "Mangler API-nøkkel.",
"zen.api.error.invalidApiKey": "Ugyldig API-nøkkel.",
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igjen om {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Abonnementskvote overskredet. Du kan fortsette å bruke gratis modeller.",
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Legg til en betalingsmetode her: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Utilstrekkelig saldo. Administrer faktureringen din her: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Arbeidsområdet ditt har nådd sin månedlige utgiftsgrense på ${{amount}}. Administrer grensene dine her: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Du har nådd din månedlige utgiftsgrense på ${{amount}}. Administrer grensene dine her: {{membersUrl}}",
"zen.api.error.modelDisabled": "Modellen er deaktivert",
"black.meta.title": "OpenCode Black | Få tilgang til verdens beste kodemodeller",
"black.meta.description": "Få tilgang til Claude, GPT, Gemini og mer med OpenCode Black-abonnementer.",
"black.hero.title": "Få tilgang til verdens beste kodemodeller",
@@ -476,7 +449,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Vennligst oppdater betalingsmetoden din og prøv på nytt.",
"workspace.reload.retrying": "Prøver på nytt...",
"workspace.reload.retry": "Prøv på nytt",
"workspace.reload.error.paymentFailed": "Betaling mislyktes.",
"workspace.payments.title": "Betalingshistorikk",
"workspace.payments.subtitle": "Nylige betalingstransaksjoner.",
@@ -595,10 +567,6 @@ export const dict = {
"enterprise.form.send": "Send",
"enterprise.form.sending": "Sender...",
"enterprise.form.success": "Melding sendt, vi tar kontakt snart.",
"enterprise.form.success.submitted": "Skjemaet ble sendt inn.",
"enterprise.form.error.allFieldsRequired": "Alle felt er obligatoriske.",
"enterprise.form.error.invalidEmailFormat": "Ugyldig e-postformat.",
"enterprise.form.error.internalServer": "Intern serverfeil.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Hva er OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -631,7 +599,6 @@ export const dict = {
"bench.list.table.agent": "Agent",
"bench.list.table.model": "Modell",
"bench.list.table.score": "Poengsum",
"bench.submission.error.allFieldsRequired": "Alle felt er obligatoriske.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Oppgave ikke funnet",

View File

@@ -14,7 +14,6 @@ export const dict = {
"nav.home": "Strona główna",
"nav.openMenu": "Otwórz menu",
"nav.getStartedFree": "Zacznij za darmo",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Skopiuj logo jako SVG",
"nav.context.copyWordmark": "Skopiuj logotyp jako SVG",
@@ -42,13 +41,9 @@ export const dict = {
"notFound.docs": "Dokumentacja",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "jasne logo opencode",
"notFound.logoDarkAlt": "ciemne logo opencode",
"user.logout": "Wyloguj się",
"auth.callback.error.codeMissing": "Nie znaleziono kodu autoryzacji.",
"workspace.select": "Wybierz obszar roboczy",
"workspace.createNew": "+ Utwórz nowy obszar roboczy",
"workspace.modal.title": "Utwórz nowy obszar roboczy",
@@ -80,8 +75,6 @@ export const dict = {
"error.reloadAmountMin": "Kwota doładowania musi wynosić co najmniej ${{amount}}",
"error.reloadTriggerMin": "Próg salda musi wynosić co najmniej ${{amount}}",
"app.meta.description": "OpenCode - Otwartoźródłowy agent programistyczny.",
"home.title": "OpenCode | Open source'owy agent AI do kodowania",
"temp.title": "opencode | Agent AI do kodowania zbudowany dla terminala",
@@ -97,8 +90,6 @@ export const dict = {
"temp.feature.models.afterLink": ", w tym modele lokalne",
"temp.screenshot.caption": "OpenCode TUI z motywem tokyonight",
"temp.screenshot.alt": "OpenCode TUI z motywem tokyonight",
"temp.logoLightAlt": "jasne logo opencode",
"temp.logoDarkAlt": "ciemne logo opencode",
"home.banner.badge": "Nowość",
"home.banner.text": "Aplikacja desktopowa dostępna w wersji beta",
@@ -250,24 +241,6 @@ export const dict = {
"Wszystkie modele Zen są hostowane w USA. Dostawcy stosują politykę zerowej retencji i nie wykorzystują Twoich danych do trenowania modeli, z",
"zen.privacy.exceptionsLink": "następującymi wyjątkami",
"zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.",
"zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany",
"zen.api.error.modelFormatNotSupported": "Model {{model}} nie jest obsługiwany dla formatu {{format}}",
"zen.api.error.noProviderAvailable": "Brak dostępnego dostawcy",
"zen.api.error.providerNotSupported": "Dostawca {{provider}} nie jest obsługiwany",
"zen.api.error.missingApiKey": "Brak klucza API.",
"zen.api.error.invalidApiKey": "Nieprawidłowy klucz API.",
"zen.api.error.subscriptionQuotaExceeded": "Przekroczono limit subskrypcji. Spróbuj ponownie za {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Przekroczono limit subskrypcji. Możesz kontynuować korzystanie z darmowych modeli.",
"zen.api.error.noPaymentMethod": "Brak metody płatności. Dodaj metodę płatności tutaj: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Niewystarczające saldo. Zarządzaj swoimi płatnościami tutaj: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Twoja przestrzeń robocza osiągnęła miesięczny limit wydatków w wysokości ${{amount}}. Zarządzaj swoimi limitami tutaj: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Osiągnąłeś swój miesięczny limit wydatków w wysokości ${{amount}}. Zarządzaj swoimi limitami tutaj: {{membersUrl}}",
"zen.api.error.modelDisabled": "Model jest wyłączony",
"black.meta.title": "OpenCode Black | Dostęp do najlepszych na świecie modeli kodujących",
"black.meta.description": "Uzyskaj dostęp do Claude, GPT, Gemini i innych dzięki planom subskrypcji OpenCode Black.",
"black.hero.title": "Dostęp do najlepszych na świecie modeli kodujących",
@@ -477,7 +450,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Zaktualizuj metodę płatności i spróbuj ponownie.",
"workspace.reload.retrying": "Ponawianie...",
"workspace.reload.retry": "Spróbuj ponownie",
"workspace.reload.error.paymentFailed": "Płatność nie powiodła się.",
"workspace.payments.title": "Historia płatności",
"workspace.payments.subtitle": "Ostatnie transakcje płatnicze.",
@@ -598,10 +570,6 @@ export const dict = {
"enterprise.form.send": "Wyślij",
"enterprise.form.sending": "Wysyłanie...",
"enterprise.form.success": "Wiadomość wysłana, skontaktujemy się wkrótce.",
"enterprise.form.success.submitted": "Formularz został pomyślnie wysłany.",
"enterprise.form.error.allFieldsRequired": "Wszystkie pola są wymagane.",
"enterprise.form.error.invalidEmailFormat": "Nieprawidłowy format adresu e-mail.",
"enterprise.form.error.internalServer": "Wewnętrzny błąd serwera.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Czym jest OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -634,7 +602,6 @@ export const dict = {
"bench.list.table.agent": "Agent",
"bench.list.table.model": "Model",
"bench.list.table.score": "Wynik",
"bench.submission.error.allFieldsRequired": "Wszystkie pola są wymagane.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Nie znaleziono zadania",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Главная",
"nav.openMenu": "Открыть меню",
"nav.getStartedFree": "Начать бесплатно",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Скопировать логотип как SVG",
"nav.context.copyWordmark": "Скопировать название как SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Документация",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "светлый логотип opencode",
"notFound.logoDarkAlt": "темный логотип opencode",
"user.logout": "Выйти",
"auth.callback.error.codeMissing": "Код авторизации не найден.",
"workspace.select": "Выбрать рабочее пространство",
"workspace.createNew": "+ Создать рабочее пространство",
"workspace.modal.title": "Создать рабочее пространство",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "Сумма пополнения должна быть не менее ${{amount}}",
"error.reloadTriggerMin": "Порог баланса должен быть не менее ${{amount}}",
"app.meta.description": "OpenCode - AI-агент с открытым кодом для программирования.",
"home.title": "OpenCode | AI-агент с открытым кодом для программирования",
"temp.title": "opencode | AI-агент для программирования в терминале",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ", включая локальные модели",
"temp.screenshot.caption": "OpenCode TUI с темой tokyonight",
"temp.screenshot.alt": "OpenCode TUI с темой tokyonight",
"temp.logoLightAlt": "светлый логотип opencode",
"temp.logoDarkAlt": "темный логотип opencode",
"home.banner.badge": "Новое",
"home.banner.text": "Доступно десктопное приложение (бета)",
@@ -253,24 +244,6 @@ export const dict = {
"Все модели Zen размещены в США. Провайдеры следуют политике нулевого хранения и не используют ваши данные для обучения моделей, за",
"zen.privacy.exceptionsLink": "следующими исключениями",
"zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.",
"zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается",
"zen.api.error.modelFormatNotSupported": "Модель {{model}} не поддерживается для формата {{format}}",
"zen.api.error.noProviderAvailable": "Нет доступных провайдеров",
"zen.api.error.providerNotSupported": "Провайдер {{provider}} не поддерживается",
"zen.api.error.missingApiKey": "Отсутствует API ключ.",
"zen.api.error.invalidApiKey": "Неверный API ключ.",
"zen.api.error.subscriptionQuotaExceeded": "Квота подписки превышена. Повторите попытку через {{retryIn}}.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Квота подписки превышена. Вы можете продолжить использовать бесплатные модели.",
"zen.api.error.noPaymentMethod": "Нет способа оплаты. Добавьте способ оплаты здесь: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Недостаточно средств. Управляйте оплатой здесь: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Ваше рабочее пространство достигло ежемесячного лимита расходов в ${{amount}}. Управляйте лимитами здесь: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Вы достигли ежемесячного лимита расходов в ${{amount}}. Управляйте лимитами здесь: {{membersUrl}}",
"zen.api.error.modelDisabled": "Модель отключена",
"black.meta.title": "OpenCode Black | Доступ к лучшим моделям для кодинга в мире",
"black.meta.description": "Получите доступ к Claude, GPT, Gemini и другим моделям с подпиской OpenCode Black.",
"black.hero.title": "Доступ к лучшим моделям для кодинга в мире",
@@ -482,7 +455,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Пожалуйста, обновите способ оплаты и попробуйте снова.",
"workspace.reload.retrying": "Повторная попытка...",
"workspace.reload.retry": "Повторить",
"workspace.reload.error.paymentFailed": "Ошибка оплаты.",
"workspace.payments.title": "История платежей",
"workspace.payments.subtitle": "Недавние транзакции.",
@@ -602,10 +574,6 @@ export const dict = {
"enterprise.form.send": "Отправить",
"enterprise.form.sending": "Отправка...",
"enterprise.form.success": "Сообщение отправлено, мы скоро свяжемся с вами.",
"enterprise.form.success.submitted": "Форма успешно отправлена.",
"enterprise.form.error.allFieldsRequired": "Все поля обязательны.",
"enterprise.form.error.invalidEmailFormat": "Неверный формат email.",
"enterprise.form.error.internalServer": "Внутренняя ошибка сервера.",
"enterprise.faq.title": "FAQ",
"enterprise.faq.q1": "Что такое OpenCode Enterprise?",
"enterprise.faq.a1":
@@ -638,7 +606,6 @@ export const dict = {
"bench.list.table.agent": "Агент",
"bench.list.table.model": "Модель",
"bench.list.table.score": "Оценка",
"bench.submission.error.allFieldsRequired": "Все поля обязательны.",
"bench.detail.title": "Бенчмарк - {{task}}",
"bench.detail.notFound": "Задача не найдена",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "หน้าหลัก",
"nav.openMenu": "เปิดเมนู",
"nav.getStartedFree": "เริ่มต้นฟรี",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "คัดลอกโลโก้เป็น SVG",
"nav.context.copyWordmark": "คัดลอกตัวอักษรแบรนด์เป็น SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "เอกสาร",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "โลโก้ opencode แบบสว่าง",
"notFound.logoDarkAlt": "โลโก้ opencode แบบมืด",
"user.logout": "ออกจากระบบ",
"auth.callback.error.codeMissing": "ไม่พบ authorization code",
"workspace.select": "เลือก Workspace",
"workspace.createNew": "+ สร้าง Workspace ใหม่",
"workspace.modal.title": "สร้าง Workspace ใหม่",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "จำนวนเงินที่โหลดซ้ำต้องมีอย่างน้อย ${{amount}}",
"error.reloadTriggerMin": "ยอดคงเหลือที่กระตุ้นต้องมีอย่างน้อย ${{amount}}",
"app.meta.description": "OpenCode - เอเจนต์เขียนโค้ดแบบโอเพนซอร์ส",
"home.title": "OpenCode | เอเจนต์เขียนโค้ดด้วย AI แบบโอเพนซอร์ส",
"temp.title": "OpenCode | เอเจนต์เขียนโค้ด AI ที่สร้างมาเพื่อเทอร์มินัล",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": "รวมถึงโมเดล Local",
"temp.screenshot.caption": "OpenCode TUI พร้อมธีม tokyonight",
"temp.screenshot.alt": "OpenCode TUI พร้อมธีม tokyonight",
"temp.logoLightAlt": "โลโก้ opencode แบบสว่าง",
"temp.logoDarkAlt": "โลโก้ opencode แบบมืด",
"home.banner.badge": "ใหม่",
"home.banner.text": "แอปเดสก์ท็อปพร้อมใช้งานในเวอร์ชันเบต้า",
@@ -248,24 +239,6 @@ export const dict = {
"โมเดล Zen ทั้งหมดโฮสต์ในสหรัฐอเมริกา ผู้ให้บริการปฏิบัติตามนโยบายไม่เก็บรักษาข้อมูล (zero-retention policy) และไม่ใช้ข้อมูลของคุณสำหรับการฝึกโมเดล โดยมี",
"zen.privacy.exceptionsLink": "ข้อยกเว้นดังนี้",
"zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง",
"zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}",
"zen.api.error.modelFormatNotSupported": "ไม่รองรับโมเดล {{model}} สำหรับรูปแบบ {{format}}",
"zen.api.error.noProviderAvailable": "ไม่มีผู้ให้บริการที่พร้อมใช้งาน",
"zen.api.error.providerNotSupported": "ไม่รองรับผู้ให้บริการ {{provider}}",
"zen.api.error.missingApiKey": "ไม่มี API key",
"zen.api.error.invalidApiKey": "API key ไม่ถูกต้อง",
"zen.api.error.subscriptionQuotaExceeded": "โควต้าการสมัครสมาชิกเกินขีดจำกัด ลองใหม่ในอีก {{retryIn}}",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"โควต้าการสมัครสมาชิกเกินขีดจำกัด คุณสามารถดำเนินการต่อโดยใช้โมเดลฟรี",
"zen.api.error.noPaymentMethod": "ไม่มีวิธีการชำระเงิน เพิ่มวิธีการชำระเงินที่นี่: {{billingUrl}}",
"zen.api.error.insufficientBalance": "ยอดเงินคงเหลือไม่เพียงพอ จัดการการเรียกเก็บเงินของคุณที่นี่: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Workspace ของคุณถึงขีดจำกัดการใช้จ่ายรายเดือนที่ ${{amount}} แล้ว จัดการขีดจำกัดของคุณที่นี่: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"คุณถึงขีดจำกัดการใช้จ่ายรายเดือนที่ ${{amount}} แล้ว จัดการขีดจำกัดของคุณที่นี่: {{membersUrl}}",
"zen.api.error.modelDisabled": "โมเดลถูกปิดใช้งาน",
"black.meta.title": "OpenCode Black | เข้าถึงโมเดลเขียนโค้ดที่ดีที่สุดในโลก",
"black.meta.description": "เข้าถึง Claude, GPT, Gemini และอื่นๆ ด้วยแผนสมาชิก OpenCode Black",
"black.hero.title": "เข้าถึงโมเดลเขียนโค้ดที่ดีที่สุดในโลก",
@@ -475,7 +448,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "โปรดอัปเดตวิธีการชำระเงินของคุณแล้วลองอีกครั้ง",
"workspace.reload.retrying": "กำลังลองอีกครั้ง...",
"workspace.reload.retry": "ลองอีกครั้ง",
"workspace.reload.error.paymentFailed": "การชำระเงินล้มเหลว",
"workspace.payments.title": "ประวัติการชำระเงิน",
"workspace.payments.subtitle": "รายการธุรกรรมการชำระเงินล่าสุด",
@@ -594,10 +566,6 @@ export const dict = {
"enterprise.form.send": "ส่ง",
"enterprise.form.sending": "กำลังส่ง...",
"enterprise.form.success": "ส่งข้อความแล้ว เราจะติดต่อกลับเร็วๆ นี้",
"enterprise.form.success.submitted": "ส่งแบบฟอร์มสำเร็จแล้ว",
"enterprise.form.error.allFieldsRequired": "จำเป็นต้องกรอกทุกช่อง",
"enterprise.form.error.invalidEmailFormat": "รูปแบบอีเมลไม่ถูกต้อง",
"enterprise.form.error.internalServer": "เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์",
"enterprise.faq.title": "คำถามที่พบบ่อย",
"enterprise.faq.q1": "OpenCode Enterprise คืออะไร?",
"enterprise.faq.a1":
@@ -630,7 +598,6 @@ export const dict = {
"bench.list.table.agent": "เอเจนต์",
"bench.list.table.model": "โมเดล",
"bench.list.table.score": "คะแนน",
"bench.submission.error.allFieldsRequired": "จำเป็นต้องกรอกทุกช่อง",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "ไม่พบงาน",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "Ana sayfa",
"nav.openMenu": "Menüyü aç",
"nav.getStartedFree": "Ücretsiz başla",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "Logoyu SVG olarak kopyala",
"nav.context.copyWordmark": "Wordmark'ı SVG olarak kopyala",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "Dokümantasyon",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode açık logo",
"notFound.logoDarkAlt": "opencode koyu logo",
"user.logout": ıkış",
"auth.callback.error.codeMissing": "Yetkilendirme kodu bulunamadı.",
"workspace.select": "Çalışma alanı seç",
"workspace.createNew": "+ Yeni çalışma alanı oluştur",
"workspace.modal.title": "Yeni çalışma alanı oluştur",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "Yükleme tutarı en az ${{amount}} olmalıdır",
"error.reloadTriggerMin": "Bakiye tetikleyicisi en az ${{amount}} olmalıdır",
"app.meta.description": "OpenCode - Açık kaynaklı kodlama ajanı.",
"home.title": "OpenCode | Açık kaynaklı yapay zeka kodlama ajanı",
"temp.title": "opencode | Terminal için geliştirilmiş yapay zeka kodlama ajanı",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": " üzerinden destekler",
"temp.screenshot.caption": "opencode TUI ve tokyonight teması",
"temp.screenshot.alt": "tokyonight temalı opencode TUI",
"temp.logoLightAlt": "opencode açık logo",
"temp.logoDarkAlt": "opencode koyu logo",
"home.banner.badge": "Yeni",
"home.banner.text": "Masaüstü uygulaması beta olarak kullanılabilir",
@@ -251,24 +242,6 @@ export const dict = {
"Tüm Zen modelleri ABD'de barındırılmaktadır. Sağlayıcılar sıfır saklama politikası izler ve verilerinizi model eğitimi için kullanmaz; şu",
"zen.privacy.exceptionsLink": "aşağıdaki istisnalar",
"zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.",
"zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor",
"zen.api.error.modelFormatNotSupported": "{{model}} modeli {{format}} formatı için desteklenmiyor",
"zen.api.error.noProviderAvailable": "Kullanılabilir sağlayıcı yok",
"zen.api.error.providerNotSupported": "{{provider}} sağlayıcısı desteklenmiyor",
"zen.api.error.missingApiKey": "API anahtarı eksik.",
"zen.api.error.invalidApiKey": "Geçersiz API anahtarı.",
"zen.api.error.subscriptionQuotaExceeded": "Abonelik kotasııldı. {{retryIn}} içinde tekrar deneyin.",
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
"Abonelik kotasııldı. Ücretsiz modelleri kullanmaya devam edebilirsiniz.",
"zen.api.error.noPaymentMethod": "Ödeme yöntemi bulunamadı. Buradan bir ödeme yöntemi ekleyin: {{billingUrl}}",
"zen.api.error.insufficientBalance": "Yetersiz bakiye. Faturalandırmanızı buradan yönetin: {{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"Çalışma alanınız aylık ${{amount}} harcama limitine ulaştı. Limitlerinizi buradan yönetin: {{billingUrl}}",
"zen.api.error.userMonthlyLimitReached":
"Aylık ${{amount}} harcama limitinize ulaştınız. Limitlerinizi buradan yönetin: {{membersUrl}}",
"zen.api.error.modelDisabled": "Model devre dışı",
"black.meta.title": "OpenCode Black | Dünyanın en iyi kodlama modellerine erişin",
"black.meta.description": "OpenCode Black abonelik planlarıyla Claude, GPT, Gemini ve daha fazlasına erişin.",
"black.hero.title": "Dünyanın en iyi kodlama modellerine erişin",
@@ -478,7 +451,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "Lütfen ödeme yönteminizi güncelleyin ve tekrar deneyin.",
"workspace.reload.retrying": "Yeniden deneniyor...",
"workspace.reload.retry": "Yeniden dene",
"workspace.reload.error.paymentFailed": "Ödeme başarısız.",
"workspace.payments.title": "Ödeme Geçmişi",
"workspace.payments.subtitle": "Son ödeme işlemleri.",
@@ -599,10 +571,6 @@ export const dict = {
"enterprise.form.send": "Gönder",
"enterprise.form.sending": "Gönderiliyor...",
"enterprise.form.success": "Mesaj gönderildi, yakında size dönüş yapacağız.",
"enterprise.form.success.submitted": "Form başarıyla gönderildi.",
"enterprise.form.error.allFieldsRequired": "Tüm alanlar gereklidir.",
"enterprise.form.error.invalidEmailFormat": "Geçersiz e-posta formatı.",
"enterprise.form.error.internalServer": "İç sunucu hatası.",
"enterprise.faq.title": "SSS",
"enterprise.faq.q1": "OpenCode Enterprise nedir?",
"enterprise.faq.a1":
@@ -635,7 +603,6 @@ export const dict = {
"bench.list.table.agent": "Ajan",
"bench.list.table.model": "Model",
"bench.list.table.score": "Puan",
"bench.submission.error.allFieldsRequired": "Tüm alanlar gereklidir.",
"bench.detail.title": "Benchmark - {{task}}",
"bench.detail.notFound": "Görev bulunamadı",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "首页",
"nav.openMenu": "打开菜单",
"nav.getStartedFree": "免费开始",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "复制 Logo (SVG)",
"nav.context.copyWordmark": "复制商标 (SVG)",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "文档",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode logo 亮色",
"notFound.logoDarkAlt": "opencode logo 暗色",
"user.logout": "退出登录",
"auth.callback.error.codeMissing": "未找到授权码。",
"workspace.select": "选择工作区",
"workspace.createNew": "+ 新建工作区",
"workspace.modal.title": "新建工作区",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "充值金额必须至少为 ${{amount}}",
"error.reloadTriggerMin": "余额触发阈值必须至少为 ${{amount}}",
"app.meta.description": "OpenCode - 开源编程代理。",
"home.title": "OpenCode | 开源 AI 编程代理",
"temp.title": "OpenCode | 专为终端打造的 AI 编程代理",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": ",包括本地模型",
"temp.screenshot.caption": "使用 Tokyonight 主题的 OpenCode TUI",
"temp.screenshot.alt": "使用 Tokyonight 主题的 OpenCode TUI",
"temp.logoLightAlt": "opencode logo 亮色",
"temp.logoDarkAlt": "opencode logo 暗色",
"home.banner.badge": "新",
"home.banner.text": "桌面应用 Beta 版现已推出",
@@ -238,22 +229,6 @@ export const dict = {
"zen.privacy.beforeExceptions": "所有 Zen 模型均托管在美国。提供商遵循零留存政策,不使用您的数据进行模型训练,",
"zen.privacy.exceptionsLink": "以下例外情况除外",
"zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。",
"zen.api.error.modelNotSupported": "不支持模型 {{model}}",
"zen.api.error.modelFormatNotSupported": "格式 {{format}} 不支持模型 {{model}}",
"zen.api.error.noProviderAvailable": "没有可用的提供商",
"zen.api.error.providerNotSupported": "不支持提供商 {{provider}}",
"zen.api.error.missingApiKey": "缺少 API 密钥。",
"zen.api.error.invalidApiKey": "无效的 API 密钥。",
"zen.api.error.subscriptionQuotaExceeded": "超出订阅配额。请在 {{retryIn}} 后重试。",
"zen.api.error.subscriptionQuotaExceededUseFreeModels": "超出订阅配额。您可以继续使用免费模型。",
"zen.api.error.noPaymentMethod": "没有付款方式。请在此处添加付款方式:{{billingUrl}}",
"zen.api.error.insufficientBalance": "余额不足。请在此处管理您的计费:{{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"您的工作区已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{billingUrl}}",
"zen.api.error.userMonthlyLimitReached": "您已达到每月支出限额 ${{amount}}。请在此处管理您的限额:{{membersUrl}}",
"zen.api.error.modelDisabled": "模型已禁用",
"black.meta.title": "OpenCode Black | 访问全球顶尖编程模型",
"black.meta.description": "通过 OpenCode Black 订阅计划使用 Claude, GPT, Gemini 等模型。",
"black.hero.title": "访问全球顶尖编程模型",
@@ -461,7 +436,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "请更新您的付款方式并重试。",
"workspace.reload.retrying": "正在重试...",
"workspace.reload.retry": "重试",
"workspace.reload.error.paymentFailed": "支付失败。",
"workspace.payments.title": "支付历史",
"workspace.payments.subtitle": "近期支付交易。",
@@ -578,10 +552,6 @@ export const dict = {
"enterprise.form.send": "发送",
"enterprise.form.sending": "正在发送...",
"enterprise.form.success": "消息已发送,我们会尽快与您联系。",
"enterprise.form.success.submitted": "表单提交成功。",
"enterprise.form.error.allFieldsRequired": "所有字段均为必填项。",
"enterprise.form.error.invalidEmailFormat": "邮箱格式无效。",
"enterprise.form.error.internalServer": "内部服务器错误。",
"enterprise.faq.title": "常见问题",
"enterprise.faq.q1": "什么是 OpenCode 企业版?",
"enterprise.faq.a1":
@@ -614,7 +584,6 @@ export const dict = {
"bench.list.table.agent": "代理",
"bench.list.table.model": "模型",
"bench.list.table.score": "分数",
"bench.submission.error.allFieldsRequired": "所有字段均为必填项。",
"bench.detail.title": "基准测试 - {{task}}",
"bench.detail.notFound": "未找到任务",

View File

@@ -15,7 +15,6 @@ export const dict = {
"nav.home": "首頁",
"nav.openMenu": "開啟選單",
"nav.getStartedFree": "免費開始使用",
"nav.logoAlt": "OpenCode",
"nav.context.copyLogo": "複製標誌SVG",
"nav.context.copyWordmark": "複製字標SVG",
@@ -43,13 +42,9 @@ export const dict = {
"notFound.docs": "文件",
"notFound.github": "GitHub",
"notFound.discord": "Discord",
"notFound.logoLightAlt": "opencode 淺色標誌",
"notFound.logoDarkAlt": "opencode 深色標誌",
"user.logout": "登出",
"auth.callback.error.codeMissing": "找不到授權碼。",
"workspace.select": "選取工作區",
"workspace.createNew": "+ 建立新工作區",
"workspace.modal.title": "建立新工作區",
@@ -81,8 +76,6 @@ export const dict = {
"error.reloadAmountMin": "儲值金額必須至少為 ${{amount}}",
"error.reloadTriggerMin": "餘額觸發門檻必須至少為 ${{amount}}",
"app.meta.description": "OpenCode - 開源編碼代理。",
"home.title": "OpenCode | 開源 AI 編碼代理",
"temp.title": "OpenCode | 專為終端打造的 AI 編碼代理",
@@ -98,8 +91,6 @@ export const dict = {
"temp.feature.models.afterLink": "支援 75+ 家 LLM 供應商,包括本地模型",
"temp.screenshot.caption": "使用 tokyonight 主題的 OpenCode TUI",
"temp.screenshot.alt": "使用 tokyonight 主題的 OpenCode TUI",
"temp.logoLightAlt": "opencode 淺色標誌",
"temp.logoDarkAlt": "opencode 深色標誌",
"home.banner.badge": "新",
"home.banner.text": "桌面應用已推出 Beta",
@@ -238,22 +229,6 @@ export const dict = {
"zen.privacy.beforeExceptions": "所有 Zen 模型均在美國託管。供應商遵循零留存政策,不會將你的資料用於模型訓練,並且有",
"zen.privacy.exceptionsLink": "以下例外情況",
"zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。",
"zen.api.error.modelNotSupported": "不支援模型 {{model}}",
"zen.api.error.modelFormatNotSupported": "模型 {{model}} 不支援格式 {{format}}",
"zen.api.error.noProviderAvailable": "無可用的供應商",
"zen.api.error.providerNotSupported": "不支援供應商 {{provider}}",
"zen.api.error.missingApiKey": "缺少 API 金鑰。",
"zen.api.error.invalidApiKey": "無效的 API 金鑰。",
"zen.api.error.subscriptionQuotaExceeded": "超出訂閱配額。請在 {{retryIn}} 後重試。",
"zen.api.error.subscriptionQuotaExceededUseFreeModels": "超出訂閱配額。你可以繼續使用免費模型。",
"zen.api.error.noPaymentMethod": "無付款方式。請在此處新增付款方式:{{billingUrl}}",
"zen.api.error.insufficientBalance": "餘額不足。請在此處管理你的帳務:{{billingUrl}}",
"zen.api.error.workspaceMonthlyLimitReached":
"你的工作區已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{billingUrl}}",
"zen.api.error.userMonthlyLimitReached": "你已達到每月支出限額 ${{amount}}。請在此處管理你的限額:{{membersUrl}}",
"zen.api.error.modelDisabled": "模型已停用",
"black.meta.title": "OpenCode Black | 存取全球最佳編碼模型",
"black.meta.description": "透過 OpenCode Black 訂閱方案存取 Claude、GPT、Gemini 等模型。",
"black.hero.title": "存取全球最佳編碼模型",
@@ -461,7 +436,6 @@ export const dict = {
"workspace.reload.updatePaymentMethod": "請更新你的付款方式並重試。",
"workspace.reload.retrying": "重試中...",
"workspace.reload.retry": "重試",
"workspace.reload.error.paymentFailed": "付款失敗。",
"workspace.payments.title": "付款紀錄",
"workspace.payments.subtitle": "最近的付款交易。",
@@ -577,10 +551,6 @@ export const dict = {
"enterprise.form.send": "傳送",
"enterprise.form.sending": "傳送中...",
"enterprise.form.success": "訊息已傳送,我們會盡快與你聯絡。",
"enterprise.form.success.submitted": "表單已成功送出。",
"enterprise.form.error.allFieldsRequired": "所有欄位均為必填。",
"enterprise.form.error.invalidEmailFormat": "無效的電子郵件格式。",
"enterprise.form.error.internalServer": "內部伺服器錯誤。",
"enterprise.faq.title": "常見問題",
"enterprise.faq.q1": "什麼是 OpenCode Enterprise",
"enterprise.faq.a1":
@@ -613,7 +583,6 @@ export const dict = {
"bench.list.table.agent": "代理",
"bench.list.table.model": "模型",
"bench.list.table.score": "分數",
"bench.submission.error.allFieldsRequired": "所有欄位均為必填。",
"bench.detail.title": "評測 - {{task}}",
"bench.detail.notFound": "找不到任務",

View File

@@ -48,9 +48,6 @@ const map = {
"Provider is required": "error.providerRequired",
"API key is required": "error.apiKeyRequired",
"Model is required": "error.modelRequired",
"workspace.reload.error.paymentFailed": "workspace.reload.error.paymentFailed",
"Payment failed": "workspace.reload.error.paymentFailed",
"Payment failed.": "workspace.reload.error.paymentFailed",
} as const satisfies Record<string, Key>
export function formErrorReloadAmountMin(amount: number) {

View File

@@ -16,8 +16,8 @@ export default function NotFound() {
<div data-component="content">
<section data-component="top">
<a href={language.route("/")} data-slot="logo-link">
<img data-slot="logo light" src={logoLight} alt={i18n.t("notFound.logoLightAlt")} />
<img data-slot="logo dark" src={logoDark} alt={i18n.t("notFound.logoDarkAlt")} />
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
</a>
<h1 data-slot="title">{i18n.t("notFound.heading")}</h1>
</section>

View File

@@ -1,7 +1,5 @@
import type { APIEvent } from "@solidjs/start/server"
import { AWS } from "@opencode-ai/console-core/aws.js"
import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
interface EnterpriseFormData {
name: string
@@ -11,19 +9,18 @@ interface EnterpriseFormData {
}
export async function POST(event: APIEvent) {
const dict = i18n(localeFromRequest(event.request))
try {
const body = (await event.request.json()) as EnterpriseFormData
// Validate required fields
if (!body.name || !body.role || !body.email || !body.message) {
return Response.json({ error: dict["enterprise.form.error.allFieldsRequired"] }, { status: 400 })
return Response.json({ error: "All fields are required" }, { status: 400 })
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(body.email)) {
return Response.json({ error: dict["enterprise.form.error.invalidEmailFormat"] }, { status: 400 })
return Response.json({ error: "Invalid email format" }, { status: 400 })
}
// Create email content
@@ -42,9 +39,9 @@ ${body.email}`.trim()
replyTo: body.email,
})
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
} catch (error) {
console.error("Error processing enterprise form:", error)
return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
return Response.json({ error: "Internal server error" }, { status: 500 })
}
}

View File

@@ -2,17 +2,15 @@ import { redirect } from "@solidjs/router"
import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
import { useAuthSession } from "~/context/auth"
import { i18n } from "~/i18n"
import { localeFromRequest, route } from "~/lib/language"
export async function GET(input: APIEvent) {
const url = new URL(input.request.url)
const locale = localeFromRequest(input.request)
const dict = i18n(locale)
try {
const code = url.searchParams.get("code")
if (!code) throw new Error(dict["auth.callback.error.codeMissing"])
if (!code) throw new Error("No code found")
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
if (result.err) throw new Error(result.err.message)
const decoded = AuthClient.decode(result.tokens.access, {} as any)

View File

@@ -2,8 +2,6 @@ import type { APIEvent } from "@solidjs/start/server"
import { Database } from "@opencode-ai/console-core/drizzle/index.js"
import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js"
import { Identifier } from "@opencode-ai/console-core/identifier.js"
import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
interface SubmissionBody {
model: string
@@ -12,11 +10,10 @@ interface SubmissionBody {
}
export async function POST(event: APIEvent) {
const dict = i18n(localeFromRequest(event.request))
const body = (await event.request.json()) as SubmissionBody
if (!body.model || !body.agent || !body.result) {
return Response.json({ error: dict["bench.submission.error.allFieldsRequired"] }, { status: 400 })
return Response.json({ error: "All fields are required" }, { status: 400 })
}
await Database.use((tx) =>

View File

@@ -33,7 +33,6 @@ const brandAssets = "/opencode-brand-assets.zip"
export default function Brand() {
const i18n = useI18n()
const alt = i18n.t("brand.meta.description")
const downloadFile = async (url: string, filename: string) => {
try {
const response = await fetch(url)
@@ -89,7 +88,7 @@ export default function Brand() {
<div data-component="brand-grid">
<div>
<img src={previewLogoLight} alt={alt} />
<img src={previewLogoLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightPng, "opencode-logo-light.png")}>
PNG
@@ -116,7 +115,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewLogoDark} alt={alt} />
<img src={previewLogoDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkPng, "opencode-logo-dark.png")}>
PNG
@@ -143,7 +142,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewLogoLightSquare} alt={alt} />
<img src={previewLogoLightSquare} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightSquarePng, "opencode-logo-light-square.png")}>
PNG
@@ -170,7 +169,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewLogoDarkSquare} alt={alt} />
<img src={previewLogoDarkSquare} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkSquarePng, "opencode-logo-dark-square.png")}>
PNG
@@ -197,7 +196,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewWordmarkLight} alt={alt} />
<img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkLightPng, "opencode-wordmark-light.png")}>
PNG
@@ -224,7 +223,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewWordmarkDark} alt={alt} />
<img src={previewWordmarkDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkDarkPng, "opencode-wordmark-dark.png")}>
PNG
@@ -251,7 +250,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewWordmarkSimpleLight} alt={alt} />
<img src={previewWordmarkSimpleLight} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleLightPng, "opencode-wordmark-simple-light.png")}>
PNG
@@ -278,7 +277,7 @@ export default function Brand() {
</div>
</div>
<div>
<img src={previewWordmarkSimpleDark} alt={alt} />
<img src={previewWordmarkSimpleDark} alt="OpenCode brand guidelines" />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleDarkPng, "opencode-wordmark-simple-dark.png")}>
PNG

View File

@@ -19,7 +19,7 @@ const downloadNames: Record<string, string> = {
export async function GET({ params: { platform, channel } }: APIEvent) {
const assetName = assetNames[platform]
if (!assetName) return new Response(null, { status: 404 })
if (!assetName) return new Response("Not Found", { status: 404 })
const resp = await fetch(
`https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/latest/download/${assetName}`,

View File

@@ -306,7 +306,7 @@ export async function POST(input: APIEvent) {
.update(BillingTable)
.set({
reload: false,
reloadError: errorMessage ?? "workspace.reload.error.paymentFailed",
reloadError: errorMessage ?? "Payment failed.",
timeReloadError: sql`now()`,
})
.where(eq(BillingTable.workspaceID, Actor.workspace())),

View File

@@ -47,8 +47,8 @@ export default function Home() {
<div data-component="content">
<section data-component="top">
<img data-slot="logo light" src={logoLight} alt={i18n.t("temp.logoLightAlt")} />
<img data-slot="logo dark" src={logoDark} alt={i18n.t("temp.logoDarkAlt")} />
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<h1 data-slot="title">{i18n.t("temp.hero.title")}</h1>
<div data-slot="login">
<a href="/auth">{i18n.t("temp.zen")}</a>

View File

@@ -12,7 +12,6 @@ import { queryBillingInfo } from "../../common"
import styles from "./lite-section.module.css"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
import { formError } from "~/lib/form-error"
const queryLiteSubscription = query(async (workspaceID: string) => {
"use server"
@@ -115,7 +114,7 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: formError.workspaceRequired }
if (!workspaceID) return { error: "Workspace ID is required" }
const useBalance = form.get("useBalance")?.toString() === "true"
return json(

View File

@@ -202,8 +202,7 @@ export function ReloadSection() {
minute: "2-digit",
second: "2-digit",
})}
. {i18n.t("workspace.reload.reason")}{" "}
{localizeError(i18n.t, billingInfo()?.reloadError ?? undefined).replace(/\.$/, "")}.{" "}
. {i18n.t("workspace.reload.reason")} {billingInfo()?.reloadError?.replace(/\.$/, "")}.{" "}
{i18n.t("workspace.reload.updatePaymentMethod")}
</p>
<form action={reload} method="post" data-slot="create-form">

View File

@@ -35,8 +35,6 @@ import { createTrialLimiter } from "./trialLimiter"
import { createStickyTracker } from "./stickyProviderTracker"
import { LiteData } from "@opencode-ai/console-core/lite.js"
import { Resource } from "@opencode-ai/console-resource"
import { i18n, type Key } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
type RetryOptions = {
@@ -45,15 +43,6 @@ type RetryOptions = {
}
type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "lite" | "balance"
function resolve(text: string, params?: Record<string, string | number>) {
if (!params) return text
return text.replace(/\{\{(\w+)\}\}/g, (raw, key) => {
const value = params[key]
if (value === undefined || value === null) return raw
return String(value)
})
}
export async function handler(
input: APIEvent,
opts: {
@@ -71,8 +60,6 @@ export async function handler(
const MAX_FAILOVER_RETRIES = 3
const MAX_429_RETRIES = 3
const dict = i18n(localeFromRequest(input.request))
const t = (key: Key, params?: Record<string, string | number>) => resolve(dict[key], params)
const ADMIN_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
@@ -99,7 +86,7 @@ export async function handler(
const dataDumper = createDataDumper(sessionId, requestId, projectId)
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
const isTrial = await trialLimiter?.isTrial()
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request)
const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request.headers)
await rateLimiter?.check()
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
const stickyProvider = await stickyTracker?.get()
@@ -372,20 +359,14 @@ export async function handler(
}
function validateModel(zenData: ZenData, reqModel: string) {
if (!(reqModel in zenData.models)) throw new ModelError(t("zen.api.error.modelNotSupported", { model: reqModel }))
if (!(reqModel in zenData.models)) throw new ModelError(`Model ${reqModel} not supported`)
const modelId = reqModel as keyof typeof zenData.models
const modelData = Array.isArray(zenData.models[modelId])
? zenData.models[modelId].find((model) => opts.format === model.formatFilter)
: zenData.models[modelId]
if (!modelData)
throw new ModelError(
t("zen.api.error.modelFormatNotSupported", {
model: reqModel,
format: opts.format,
}),
)
if (!modelData) throw new ModelError(`Model ${reqModel} not supported for format ${opts.format}`)
logger.metric({ model: modelId })
@@ -437,9 +418,8 @@ export async function handler(
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
})()
if (!modelProvider) throw new ModelError(t("zen.api.error.noProviderAvailable"))
if (!(modelProvider.id in zenData.providers))
throw new ModelError(t("zen.api.error.providerNotSupported", { provider: modelProvider.id }))
if (!modelProvider) throw new ModelError("No provider available")
if (!(modelProvider.id in zenData.providers)) throw new ModelError(`Provider ${modelProvider.id} not supported`)
return {
...modelProvider,
@@ -459,7 +439,7 @@ export async function handler(
const apiKey = opts.parseApiKey(input.request.headers)
if (!apiKey || apiKey === "public") {
if (modelInfo.allowAnonymous) return
throw new AuthError(t("zen.api.error.missingApiKey"))
throw new AuthError("Missing API key.")
}
const data = await Database.use((tx) =>
@@ -540,13 +520,13 @@ export async function handler(
.then((rows) => rows[0]),
)
if (!data) throw new AuthError(t("zen.api.error.invalidApiKey"))
if (!data) throw new AuthError("Invalid API key.")
if (
modelInfo.id.startsWith("alpha-") &&
Resource.App.stage === "production" &&
!ADMIN_WORKSPACES.includes(data.workspaceID)
)
throw new AuthError(t("zen.api.error.modelNotSupported", { model: modelInfo.id }))
throw new AuthError(`Model ${modelInfo.id} not supported`)
logger.metric({
api_key: data.apiKey,
@@ -610,9 +590,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
t("zen.api.error.subscriptionQuotaExceeded", {
retryIn: formatRetryTime(result.resetInSec),
}),
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
result.resetInSec,
)
}
@@ -628,9 +606,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
t("zen.api.error.subscriptionQuotaExceeded", {
retryIn: formatRetryTime(result.resetInSec),
}),
`Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
result.resetInSec,
)
}
@@ -656,7 +632,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}
@@ -671,7 +647,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}
@@ -686,7 +662,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
`Subscription quota exceeded. You can continue using free models.`,
result.resetInSec,
)
}
@@ -699,10 +675,14 @@ export async function handler(
// Validate pay as you go billing
const billing = authInfo.billing
const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing`
const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members`
if (!billing.paymentMethodID) throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl }))
if (billing.balance <= 0) throw new CreditsError(t("zen.api.error.insufficientBalance", { billingUrl }))
if (!billing.paymentMethodID)
throw new CreditsError(
`No payment method. Add a payment method here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
)
if (billing.balance <= 0)
throw new CreditsError(
`Insufficient balance. Manage your billing here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
)
const now = new Date()
const currentYear = now.getUTCFullYear()
@@ -716,10 +696,7 @@ export async function handler(
currentMonth === billing.timeMonthlyUsageUpdated.getUTCMonth()
)
throw new MonthlyLimitError(
t("zen.api.error.workspaceMonthlyLimitReached", {
amount: billing.monthlyLimit,
billingUrl,
}),
`Your workspace has reached its monthly spending limit of $${billing.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
)
if (
@@ -731,10 +708,7 @@ export async function handler(
currentMonth === authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()
)
throw new UserLimitError(
t("zen.api.error.userMonthlyLimitReached", {
amount: authInfo.user.monthlyLimit,
membersUrl,
}),
`You have reached your monthly spending limit of $${authInfo.user.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/members`,
)
return "balance"
@@ -742,7 +716,7 @@ export async function handler(
function validateModelSettings(authInfo: AuthInfo) {
if (!authInfo) return
if (authInfo.isDisabled) throw new ModelError(t("zen.api.error.modelDisabled"))
if (authInfo.isDisabled) throw new ModelError("Model is disabled")
}
function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {

View File

@@ -3,14 +3,11 @@ import { IpRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
import { FreeUsageLimitError } from "./error"
import { logger } from "./logger"
import { ZenData } from "@opencode-ai/console-core/model.js"
import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, request: Request) {
export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, headers: Headers) {
if (!limit) return
const dict = i18n(localeFromRequest(request))
const limitValue = limit.checkHeader && !request.headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value
const limitValue = limit.checkHeader && !headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value
const ip = !rawIp.length ? "unknown" : rawIp
const now = Date.now()
@@ -39,7 +36,7 @@ export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: s
logger.debug(`rate limit total: ${total}`)
if (total >= limitValue)
throw new FreeUsageLimitError(
dict["zen.api.error.rateLimitExceeded"],
`Rate limit exceeded. Please try again later.`,
limit.period === "day" ? getRetryAfterDay(now) : getRetryAfterHour(rows, intervals, limitValue, now),
)
},

View File

@@ -1,7 +0,0 @@
CREATE TABLE `workspace` (
`id` text PRIMARY KEY,
`branch` text,
`project_id` text NOT NULL,
`config` text NOT NULL,
CONSTRAINT `fk_workspace_project_id_project_id_fk` FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON DELETE CASCADE
);

View File

@@ -1,959 +0,0 @@
{
"version": "7",
"dialect": "sqlite",
"id": "1f1dbf2d-bf66-4b25-8af4-4ba7633b7e40",
"prevIds": ["d2736e43-700f-4e9e-8151-9f2f0d967bc8"],
"ddl": [
{
"name": "workspace",
"entityType": "tables"
},
{
"name": "control_account",
"entityType": "tables"
},
{
"name": "project",
"entityType": "tables"
},
{
"name": "message",
"entityType": "tables"
},
{
"name": "part",
"entityType": "tables"
},
{
"name": "permission",
"entityType": "tables"
},
{
"name": "session",
"entityType": "tables"
},
{
"name": "todo",
"entityType": "tables"
},
{
"name": "session_share",
"entityType": "tables"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "branch",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "project_id",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "config",
"entityType": "columns",
"table": "workspace"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "email",
"entityType": "columns",
"table": "control_account"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "url",
"entityType": "columns",
"table": "control_account"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "access_token",
"entityType": "columns",
"table": "control_account"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "refresh_token",
"entityType": "columns",
"table": "control_account"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "token_expiry",
"entityType": "columns",
"table": "control_account"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "active",
"entityType": "columns",
"table": "control_account"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "control_account"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "control_account"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "worktree",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "vcs",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "name",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "icon_url",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "icon_color",
"entityType": "columns",
"table": "project"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "project"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "project"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_initialized",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "sandboxes",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "commands",
"entityType": "columns",
"table": "project"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "message"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "session_id",
"entityType": "columns",
"table": "message"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "message"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "message"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "data",
"entityType": "columns",
"table": "message"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "part"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "message_id",
"entityType": "columns",
"table": "part"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "session_id",
"entityType": "columns",
"table": "part"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "part"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "part"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "data",
"entityType": "columns",
"table": "part"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "project_id",
"entityType": "columns",
"table": "permission"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "permission"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "permission"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "data",
"entityType": "columns",
"table": "permission"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "project_id",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "parent_id",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "slug",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "directory",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "title",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "version",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "share_url",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "summary_additions",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "summary_deletions",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "summary_files",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "summary_diffs",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "revert",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "permission",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_compacting",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_archived",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "session_id",
"entityType": "columns",
"table": "todo"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "content",
"entityType": "columns",
"table": "todo"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "status",
"entityType": "columns",
"table": "todo"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "priority",
"entityType": "columns",
"table": "todo"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "position",
"entityType": "columns",
"table": "todo"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "todo"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "todo"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "session_id",
"entityType": "columns",
"table": "session_share"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "session_share"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "secret",
"entityType": "columns",
"table": "session_share"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "url",
"entityType": "columns",
"table": "session_share"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "session_share"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "session_share"
},
{
"columns": ["project_id"],
"tableTo": "project",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_workspace_project_id_project_id_fk",
"entityType": "fks",
"table": "workspace"
},
{
"columns": ["session_id"],
"tableTo": "session",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_message_session_id_session_id_fk",
"entityType": "fks",
"table": "message"
},
{
"columns": ["message_id"],
"tableTo": "message",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_part_message_id_message_id_fk",
"entityType": "fks",
"table": "part"
},
{
"columns": ["project_id"],
"tableTo": "project",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_permission_project_id_project_id_fk",
"entityType": "fks",
"table": "permission"
},
{
"columns": ["project_id"],
"tableTo": "project",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_session_project_id_project_id_fk",
"entityType": "fks",
"table": "session"
},
{
"columns": ["session_id"],
"tableTo": "session",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_todo_session_id_session_id_fk",
"entityType": "fks",
"table": "todo"
},
{
"columns": ["session_id"],
"tableTo": "session",
"columnsTo": ["id"],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_session_share_session_id_session_id_fk",
"entityType": "fks",
"table": "session_share"
},
{
"columns": ["email", "url"],
"nameExplicit": false,
"name": "control_account_pk",
"entityType": "pks",
"table": "control_account"
},
{
"columns": ["session_id", "position"],
"nameExplicit": false,
"name": "todo_pk",
"entityType": "pks",
"table": "todo"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "workspace_pk",
"table": "workspace",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "project_pk",
"table": "project",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "message_pk",
"table": "message",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "part_pk",
"table": "part",
"entityType": "pks"
},
{
"columns": ["project_id"],
"nameExplicit": false,
"name": "permission_pk",
"table": "permission",
"entityType": "pks"
},
{
"columns": ["id"],
"nameExplicit": false,
"name": "session_pk",
"table": "session",
"entityType": "pks"
},
{
"columns": ["session_id"],
"nameExplicit": false,
"name": "session_share_pk",
"table": "session_share",
"entityType": "pks"
},
{
"columns": [
{
"value": "session_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "message_session_idx",
"entityType": "indexes",
"table": "message"
},
{
"columns": [
{
"value": "message_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "part_message_idx",
"entityType": "indexes",
"table": "part"
},
{
"columns": [
{
"value": "session_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "part_session_idx",
"entityType": "indexes",
"table": "part"
},
{
"columns": [
{
"value": "project_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "session_project_idx",
"entityType": "indexes",
"table": "session"
},
{
"columns": [
{
"value": "parent_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "session_parent_idx",
"entityType": "indexes",
"table": "session"
},
{
"columns": [
{
"value": "session_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "todo_session_idx",
"entityType": "indexes",
"table": "todo"
}
],
"renames": []
}

View File

@@ -43,7 +43,6 @@
"@types/mime-types": "3.0.1",
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@types/which": "3.0.4",
"@typescript/native-preview": "catalog:",
"drizzle-kit": "1.0.0-beta.12-a5629fb",
"drizzle-orm": "1.0.0-beta.12-a5629fb",
@@ -90,8 +89,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@opentui/core": "0.1.84",
"@opentui/solid": "0.1.84",
"@opentui/core": "0.1.81",
"@opentui/solid": "0.1.81",
"@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -128,7 +127,6 @@
"ulid": "catalog:",
"vscode-jsonrpc": "8.2.1",
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",

View File

@@ -63,7 +63,6 @@ export namespace Agent {
question: "deny",
plan_enter: "deny",
plan_exit: "deny",
edit: "ask",
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
read: {
"*": "allow",

View File

@@ -13,7 +13,6 @@ import { Instance } from "../../project/instance"
import type { Hooks } from "@opencode-ai/plugin"
import { Process } from "../../util/process"
import { text } from "node:stream/consumers"
import { setTimeout as sleep } from "node:timers/promises"
type PluginAuth = NonNullable<Hooks["auth"]>
@@ -39,7 +38,7 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string):
const method = plugin.auth.methods[index]
// Handle prompts for all auth types
await sleep(10)
await Bun.sleep(10)
const inputs: Record<string, string> = {}
if (method.prompts) {
for (const prompt of method.prompts) {

View File

@@ -3,7 +3,6 @@ import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
import { Log } from "../../../util/log"
import { EOL } from "os"
import { setTimeout as sleep } from "node:timers/promises"
export const LSPCommand = cmd({
command: "lsp",
@@ -20,7 +19,7 @@ const DiagnosticsCommand = cmd({
async handler(args) {
await bootstrap(process.cwd(), async () => {
await LSP.touchFile(args.file, true)
await sleep(1000)
await Bun.sleep(1000)
process.stdout.write(JSON.stringify(await LSP.diagnostics(), null, 2) + EOL)
})
},

View File

@@ -28,7 +28,6 @@ import { Bus } from "../../bus"
import { MessageV2 } from "../../session/message-v2"
import { SessionPrompt } from "@/session/prompt"
import { $ } from "bun"
import { setTimeout as sleep } from "node:timers/promises"
type GitHubAuthor = {
login: string
@@ -354,7 +353,7 @@ export const GithubInstallCommand = cmd({
}
retries++
await sleep(1000)
await Bun.sleep(1000)
} while (true)
s.stop("Installed GitHub app")
@@ -1373,7 +1372,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
} catch (e) {
if (retries > 0) {
console.log(`Retrying after ${delayMs}ms...`)
await sleep(delayMs)
await Bun.sleep(delayMs)
return withRetry(fn, retries - 1, delayMs)
}
throw e

View File

@@ -365,11 +365,6 @@ export const RunCommand = cmd({
action: "deny",
pattern: "*",
},
{
permission: "edit",
action: "allow",
pattern: "*",
},
]
function title() {

View File

@@ -2,9 +2,6 @@ import { Server } from "../../server/server"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag"
import { Workspace } from "../../control-plane/workspace"
import { Project } from "../../project/project"
import { Installation } from "../../installation"
export const ServeCommand = cmd({
command: "serve",
@@ -17,15 +14,7 @@ export const ServeCommand = cmd({
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
let workspaceSync: Array<ReturnType<typeof Workspace.startSyncing>> = []
// Only available in development right now
if (Installation.isLocal()) {
workspaceSync = Project.list().map((project) => Workspace.startSyncing(project))
}
await new Promise(() => {})
await server.stop()
await Promise.all(workspaceSync.map((item) => item.stop()))
},
})

View File

@@ -9,7 +9,6 @@ import { Filesystem } from "../../util/filesystem"
import { Process } from "../../util/process"
import { EOL } from "os"
import path from "path"
import { which } from "../../util/which"
function pagerCmd(): string[] {
const lessOptions = ["-R", "-S"]
@@ -18,7 +17,7 @@ function pagerCmd(): string[] {
}
// user could have less installed via other options
const lessOnPath = which("less")
const lessOnPath = Bun.which("less")
if (lessOnPath) {
if (Filesystem.stat(lessOnPath)?.size) return [lessOnPath, ...lessOptions]
}
@@ -28,7 +27,7 @@ function pagerCmd(): string[] {
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]
}
const git = which("git")
const git = Bun.which("git")
if (git) {
const less = path.join(git, "..", "..", "usr", "bin", "less.exe")
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]

View File

@@ -462,7 +462,6 @@ function App() {
{
title: "Toggle MCPs",
value: "mcp.list",
search: "toggle mcps",
category: "Agent",
slash: {
name: "mcps",
@@ -538,9 +537,8 @@ function App() {
category: "System",
},
{
title: mode() === "dark" ? "Light mode" : "Dark mode",
title: "Toggle appearance",
value: "theme.switch_mode",
search: "toggle appearance",
onSelect: (dialog) => {
setMode(mode() === "dark" ? "light" : "dark")
dialog.clear()
@@ -579,7 +577,6 @@ function App() {
},
{
title: "Toggle debug panel",
search: "toggle debug",
category: "System",
value: "app.debug",
onSelect: (dialog) => {
@@ -589,7 +586,6 @@ function App() {
},
{
title: "Toggle console",
search: "toggle console",
category: "System",
value: "app.console",
onSelect: (dialog) => {
@@ -630,7 +626,6 @@ function App() {
{
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
value: "terminal.title.toggle",
search: "toggle terminal title",
keybind: "terminal_title_toggle",
category: "System",
onSelect: (dialog) => {
@@ -646,7 +641,6 @@ function App() {
{
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
value: "app.toggle.animations",
search: "toggle animations",
category: "System",
onSelect: (dialog) => {
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -656,7 +650,6 @@ function App() {
{
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
value: "app.toggle.diffwrap",
search: "toggle diff wrapping",
category: "System",
onSelect: (dialog) => {
const current = kv.get("diff_wrap_mode", "word")

View File

@@ -7,27 +7,6 @@ import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { useKeybind } from "../context/keybind"
import * as fuzzysort from "fuzzysort"
import type { Provider } from "@opencode-ai/sdk/v2"
function pickLatest(models: [string, Provider["models"][string]][]) {
const picks: Record<string, [string, Provider["models"][string]]> = {}
for (const item of models) {
const model = item[0]
const info = item[1]
const key = info.family ?? model
const prev = picks[key]
if (!prev) {
picks[key] = item
continue
}
if (info.release_date !== prev[1].release_date) {
if (info.release_date > prev[1].release_date) picks[key] = item
continue
}
if (model > prev[0]) picks[key] = item
}
return Object.values(picks)
}
export function useConnected() {
const sync = useSync()
@@ -42,7 +21,6 @@ export function DialogModel(props: { providerID?: string }) {
const dialog = useDialog()
const keybind = useKeybind()
const [query, setQuery] = createSignal("")
const [all, setAll] = createSignal(false)
const connected = useConnected()
const providers = createDialogProviderOptions()
@@ -94,8 +72,8 @@ export function DialogModel(props: { providerID?: string }) {
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) => {
const items = pipe(
flatMap((provider) =>
pipe(
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
@@ -126,9 +104,8 @@ export function DialogModel(props: { providerID?: string }) {
(x) => x.footer !== "Free",
(x) => x.title,
),
)
return items
}),
),
),
)
const popularProviders = !connected()
@@ -177,13 +154,6 @@ export function DialogModel(props: { providerID?: string }) {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
},
},
{
keybind: keybind.all.model_show_all_toggle?.[0],
title: all() ? "Show latest only" : "Show all models",
onTrigger: () => {
setAll((value) => !value)
},
},
]}
onFilter={setQuery}
flat={true}

View File

@@ -77,7 +77,6 @@ export function Prompt(props: PromptProps) {
const renderer = useRenderer()
const { theme, syntax } = useTheme()
const kv = useKV()
const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
function promptModelWarning() {
toast.show({
@@ -171,17 +170,6 @@ export function Prompt(props: PromptProps) {
command.register(() => {
return [
{
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
value: "permission.auto_accept.toggle",
search: "toggle permissions",
keybind: "permission_auto_accept_toggle",
category: "Agent",
onSelect: (dialog) => {
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
dialog.clear()
},
},
{
title: "Clear prompt",
value: "prompt.clear",
@@ -1008,30 +996,23 @@ export function Prompt(props: PromptProps) {
cursorColor={theme.text}
syntaxStyle={syntax()}
/>
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
<box flexDirection="row" gap={1}>
<text fg={highlight()}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
</text>
<Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}>
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
{local.model.parsed().model}
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
<text fg={highlight()}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
</text>
<Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}>
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
{local.model.parsed().model}
</text>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
<Show when={showVariant()}>
<text fg={theme.textMuted}>·</text>
<text>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
</text>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
<Show when={showVariant()}>
<text fg={theme.textMuted}>·</text>
<text>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
</text>
</Show>
</box>
</Show>
</box>
<Show when={autoaccept() === "edit"}>
<text>
<span style={{ fg: theme.warning }}>autoedit</span>
</text>
</Show>
</box>
</Show>
</box>
</box>

View File

@@ -25,7 +25,6 @@ import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { useArgs } from "./args"
import { useKV } from "./kv"
import { batch, onMount } from "solid-js"
import { Log } from "@/util/log"
import type { Path } from "@opencode-ai/sdk"
@@ -104,8 +103,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
})
const sdk = useSDK()
const kv = useKV()
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
sdk.event.listen((e) => {
const event = e.details
@@ -130,13 +127,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
case "permission.asked": {
const request = event.properties
if (autoaccept() === "edit" && request.permission === "edit") {
sdk.client.permission.reply({
reply: "once",
requestID: request.id,
})
break
}
const requests = store.permission[request.sessionID]
if (!requests) {
setStore("permission", request.sessionID, [request])
@@ -451,7 +441,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
get ready() {
return store.status !== "loading"
},
session: {
get(sessionID: string) {
const match = Binary.search(store.session, sessionID, (s) => s.id)

View File

@@ -46,7 +46,6 @@ export function Home() {
{
title: tipsHidden() ? "Show tips" : "Hide tips",
value: "tips.toggle",
search: "toggle tips",
keybind: "tips_toggle",
category: "System",
onSelect: (dialog) => {

View File

@@ -153,7 +153,7 @@ export function Session() {
const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
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", 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)
@@ -552,7 +552,6 @@ export function Session() {
{
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
value: "session.sidebar.toggle",
search: "toggle sidebar",
keybind: "sidebar_toggle",
category: "Session",
onSelect: (dialog) => {
@@ -567,7 +566,6 @@ export function Session() {
{
title: conceal() ? "Disable code concealment" : "Enable code concealment",
value: "session.toggle.conceal",
search: "toggle code concealment",
keybind: "messages_toggle_conceal" as any,
category: "Session",
onSelect: (dialog) => {
@@ -578,7 +576,6 @@ export function Session() {
{
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
value: "session.toggle.timestamps",
search: "toggle timestamps",
category: "Session",
slash: {
name: "timestamps",
@@ -592,7 +589,6 @@ export function Session() {
{
title: showThinking() ? "Hide thinking" : "Show thinking",
value: "session.toggle.thinking",
search: "toggle thinking",
keybind: "display_thinking",
category: "Session",
slash: {
@@ -607,7 +603,6 @@ export function Session() {
{
title: showDetails() ? "Hide tool details" : "Show tool details",
value: "session.toggle.actions",
search: "toggle tool details",
keybind: "tool_details",
category: "Session",
onSelect: (dialog) => {
@@ -616,9 +611,8 @@ export function Session() {
},
},
{
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
title: "Toggle session scrollbar",
value: "session.toggle.scrollbar",
search: "toggle session scrollbar",
keybind: "scrollbar_toggle",
category: "Session",
onSelect: (dialog) => {
@@ -1442,10 +1436,6 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
streaming={true}
content={props.part.text.trim()}
conceal={ctx.conceal()}
tableOptions={{
widthMode: "full",
columnFitter: "balanced",
}}
/>
</Match>
<Match when={!Flag.OPENCODE_EXPERIMENTAL_MARKDOWN}>
@@ -2035,9 +2025,7 @@ function Edit(props: ToolProps<typeof EditTool>) {
</Match>
<Match when={true}>
<InlineTool icon="←" pending="Preparing edit..." complete={props.input.filePath} part={props.part}>
Edit{" "}
{normalizePath(props.input.filePath!)}{" "}
{input({ replaceAll: "replaceAll" in props.input ? props.input.replaceAll : undefined })}
Edit {normalizePath(props.input.filePath!)} {input({ replaceAll: props.input.replaceAll })}
</InlineTool>
</Match>
</Switch>

View File

@@ -34,7 +34,6 @@ export interface DialogSelectOption<T = any> {
title: string
value: T
description?: string
search?: string
footer?: JSX.Element | string
category?: string
disabled?: boolean
@@ -86,8 +85,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
// users typically search by the item name, and not its category.
const result = fuzzysort
.go(needle, options, {
keys: ["title", "category", "search"],
scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
keys: ["title", "category"],
scoreFn: (r) => r[0].score * 2 + r[1].score,
})
.map((x) => x.obj)

View File

@@ -6,7 +6,6 @@ import { tmpdir } from "os"
import path from "path"
import { Filesystem } from "../../../../util/filesystem"
import { Process } from "../../../../util/process"
import { which } from "../../../../util/which"
/**
* Writes text to clipboard via OSC 52 escape sequence.
@@ -77,7 +76,7 @@ export namespace Clipboard {
const getCopyMethod = lazy(() => {
const os = platform()
if (os === "darwin" && which("osascript")) {
if (os === "darwin" && Bun.which("osascript")) {
console.log("clipboard: using osascript")
return async (text: string) => {
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
@@ -86,7 +85,7 @@ export namespace Clipboard {
}
if (os === "linux") {
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) {
console.log("clipboard: using wl-copy")
return async (text: string) => {
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
@@ -96,7 +95,7 @@ export namespace Clipboard {
await proc.exited.catch(() => {})
}
}
if (which("xclip")) {
if (Bun.which("xclip")) {
console.log("clipboard: using xclip")
return async (text: string) => {
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
@@ -110,7 +109,7 @@ export namespace Clipboard {
await proc.exited.catch(() => {})
}
}
if (which("xsel")) {
if (Bun.which("xsel")) {
console.log("clipboard: using xsel")
return async (text: string) => {
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {

View File

@@ -10,7 +10,6 @@ import { GlobalBus } from "@/bus/global"
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2"
import type { BunWebSocketData } from "hono/bun"
import { Flag } from "@/flag/flag"
import { setTimeout as sleep } from "node:timers/promises"
await Log.init({
print: process.argv.includes("--print-logs"),
@@ -76,7 +75,7 @@ const startEventStream = (directory: string) => {
).catch(() => undefined)
if (!events) {
await sleep(250)
await Bun.sleep(250)
continue
}
@@ -85,7 +84,7 @@ const startEventStream = (directory: string) => {
}
if (!signal.aborted) {
await sleep(250)
await Bun.sleep(250)
}
}
})().catch((error) => {

View File

@@ -1,16 +1,59 @@
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { WorkspaceServer } from "../../control-plane/workspace-server/server"
import { Installation } from "../../installation"
export const WorkspaceServeCommand = cmd({
command: "workspace-serve",
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a remote workspace event server",
describe: "starts a remote workspace websocket server",
handler: async (args) => {
const opts = await resolveNetworkOptions(args)
const server = WorkspaceServer.Listen(opts)
console.log(`workspace event server listening on http://${server.hostname}:${server.port}/event`)
const server = Bun.serve<{ id: string }>({
hostname: opts.hostname,
port: opts.port,
fetch(req, server) {
const url = new URL(req.url)
if (url.pathname === "/ws") {
const id = Bun.randomUUIDv7()
if (server.upgrade(req, { data: { id } })) return
return new Response("Upgrade failed", { status: 400 })
}
if (url.pathname === "/health") {
return new Response("ok", {
status: 200,
headers: {
"content-type": "text/plain; charset=utf-8",
},
})
}
return new Response(
JSON.stringify({
service: "workspace-server",
ws: `ws://${server.hostname}:${server.port}/ws`,
}),
{
status: 200,
headers: {
"content-type": "application/json; charset=utf-8",
},
},
)
},
websocket: {
open(ws) {
ws.send(JSON.stringify({ type: "ready", id: ws.data.id }))
},
message(ws, msg) {
const text = typeof msg === "string" ? msg : msg.toString()
ws.send(JSON.stringify({ type: "message", id: ws.data.id, text }))
},
close() {},
},
})
console.log(`workspace websocket server listening on ws://${server.hostname}:${server.port}/ws`)
await new Promise(() => {})
await server.stop()
},
})

View File

@@ -771,7 +771,6 @@ export namespace Config {
stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
model_show_all_toggle: z.string().optional().default("ctrl+o").describe("Toggle showing all models"),
session_share: z.string().optional().default("none").describe("Share current session"),
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
@@ -812,12 +811,7 @@ export namespace Config {
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
permission_auto_accept_toggle: z
.string()
.optional()
.default("shift+tab")
.describe("Toggle auto-accept mode for permissions"),
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
@@ -1156,16 +1150,6 @@ export namespace Config {
.object({
disable_paste_summary: z.boolean().optional(),
batch_tool: z.boolean().optional().describe("Enable the batch tool"),
hashline_edit: z
.boolean()
.optional()
.describe("Enable hashline-backed edit/read tool behavior (default true, set false to disable)"),
hashline_autocorrect: z
.boolean()
.optional()
.describe(
"Enable hashline autocorrect cleanup for copied prefixes and formatting artifacts (default true)",
),
openTelemetry: z
.boolean()
.optional()

View File

@@ -1,10 +0,0 @@
import { WorktreeAdaptor } from "./worktree"
import type { Config } from "../config"
import type { Adaptor } from "./types"
export function getAdaptor(config: Config): Adaptor {
switch (config.type) {
case "worktree":
return WorktreeAdaptor
}
}

View File

@@ -1,7 +0,0 @@
import type { Config } from "../config"
export type Adaptor<T extends Config = Config> = {
create(from: T, branch?: string | null): Promise<{ config: T; init: () => Promise<void> }>
remove(from: T): Promise<void>
request(from: T, method: string, url: string, data?: BodyInit, signal?: AbortSignal): Promise<Response | undefined>
}

View File

@@ -1,26 +0,0 @@
import { Worktree } from "@/worktree"
import type { Config } from "../config"
import type { Adaptor } from "./types"
type WorktreeConfig = Extract<Config, { type: "worktree" }>
export const WorktreeAdaptor: Adaptor<WorktreeConfig> = {
async create(_from: WorktreeConfig, _branch: string) {
const next = await Worktree.create(undefined)
return {
config: {
type: "worktree",
directory: next.directory,
},
// Hack for now: `Worktree.create` puts all its async code in a
// `setTimeout` so it doesn't use this, but we should change that
init: async () => {},
}
},
async remove(config: WorktreeConfig) {
await Worktree.remove({ directory: config.directory })
},
async request(_from: WorktreeConfig, _method: string, _url: string, _data?: BodyInit, _signal?: AbortSignal) {
throw new Error("worktree does not support request")
},
}

View File

@@ -1,10 +0,0 @@
import z from "zod"
export const Config = z.discriminatedUnion("type", [
z.object({
directory: z.string(),
type: z.literal("worktree"),
}),
])
export type Config = z.infer<typeof Config>

View File

@@ -1,46 +0,0 @@
import { Instance } from "@/project/instance"
import type { MiddlewareHandler } from "hono"
import { Installation } from "../installation"
import { getAdaptor } from "./adaptors"
import { Workspace } from "./workspace"
// This middleware forwards all non-GET requests if the workspace is a
// remote. The remote workspace needs to handle session mutations
async function proxySessionRequest(req: Request) {
if (req.method === "GET") return
if (!Instance.directory.startsWith("wrk_")) return
const workspace = await Workspace.get(Instance.directory)
if (!workspace) {
return new Response(`Workspace not found: ${Instance.directory}`, {
status: 500,
headers: {
"content-type": "text/plain; charset=utf-8",
},
})
}
if (workspace.config.type === "worktree") return
const url = new URL(req.url)
const body = req.method === "HEAD" ? undefined : await req.arrayBuffer()
return getAdaptor(workspace.config).request(
workspace.config,
req.method,
`${url.pathname}${url.search}`,
body,
req.signal,
)
}
export const SessionProxyMiddleware: MiddlewareHandler = async (c, next) => {
// Only available in development for now
if (!Installation.isLocal()) {
return next()
}
const response = await proxySessionRequest(c.req.raw)
if (response) {
return response
}
return next()
}

View File

@@ -1,66 +0,0 @@
export async function parseSSE(
body: ReadableStream<Uint8Array>,
signal: AbortSignal,
onEvent: (event: unknown) => void,
) {
const reader = body.getReader()
const decoder = new TextDecoder()
let buf = ""
let last = ""
let retry = 1000
const abort = () => {
void reader.cancel().catch(() => undefined)
}
signal.addEventListener("abort", abort)
try {
while (!signal.aborted) {
const chunk = await reader.read().catch(() => ({ done: true, value: undefined as Uint8Array | undefined }))
if (chunk.done) break
buf += decoder.decode(chunk.value, { stream: true })
buf = buf.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
const chunks = buf.split("\n\n")
buf = chunks.pop() ?? ""
chunks.forEach((chunk) => {
const data: string[] = []
chunk.split("\n").forEach((line) => {
if (line.startsWith("data:")) {
data.push(line.replace(/^data:\s*/, ""))
return
}
if (line.startsWith("id:")) {
last = line.replace(/^id:\s*/, "")
return
}
if (line.startsWith("retry:")) {
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10)
if (!Number.isNaN(parsed)) retry = parsed
}
})
if (!data.length) return
const raw = data.join("\n")
try {
onEvent(JSON.parse(raw))
} catch {
onEvent({
type: "sse.message",
properties: {
data: raw,
id: last || undefined,
retry,
},
})
}
})
}
} finally {
signal.removeEventListener("abort", abort)
reader.releaseLock()
}
}

View File

@@ -1,33 +0,0 @@
import { GlobalBus } from "../../bus/global"
import { Hono } from "hono"
import { streamSSE } from "hono/streaming"
export function WorkspaceServerRoutes() {
return new Hono().get("/event", async (c) => {
c.header("X-Accel-Buffering", "no")
c.header("X-Content-Type-Options", "nosniff")
return streamSSE(c, async (stream) => {
const send = async (event: unknown) => {
await stream.writeSSE({
data: JSON.stringify(event),
})
}
const handler = async (event: { directory?: string; payload: unknown }) => {
await send(event.payload)
}
GlobalBus.on("event", handler)
await send({ type: "server.connected", properties: {} })
const heartbeat = setInterval(() => {
void send({ type: "server.heartbeat", properties: {} })
}, 10_000)
await new Promise<void>((resolve) => {
stream.onAbort(() => {
clearInterval(heartbeat)
GlobalBus.off("event", handler)
resolve()
})
})
})
})
}

View File

@@ -1,24 +0,0 @@
import { Hono } from "hono"
import { SessionRoutes } from "../../server/routes/session"
import { WorkspaceServerRoutes } from "./routes"
export namespace WorkspaceServer {
export function App() {
const session = new Hono()
.use("*", async (c, next) => {
if (c.req.method === "GET") return c.notFound()
await next()
})
.route("/", SessionRoutes())
return new Hono().route("/session", session).route("/", WorkspaceServerRoutes())
}
export function Listen(opts: { hostname: string; port: number }) {
return Bun.serve({
hostname: opts.hostname,
port: opts.port,
fetch: App().fetch,
})
}
}

View File

@@ -1,12 +0,0 @@
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "@/project/project.sql"
import type { Config } from "./config"
export const WorkspaceTable = sqliteTable("workspace", {
id: text().primaryKey(),
branch: text(),
project_id: text()
.notNull()
.references(() => ProjectTable.id, { onDelete: "cascade" }),
config: text({ mode: "json" }).notNull().$type<Config>(),
})

View File

@@ -1,160 +0,0 @@
import z from "zod"
import { Identifier } from "@/id/id"
import { fn } from "@/util/fn"
import { Database, eq } from "@/storage/db"
import { Project } from "@/project/project"
import { BusEvent } from "@/bus/bus-event"
import { GlobalBus } from "@/bus/global"
import { Log } from "@/util/log"
import { WorkspaceTable } from "./workspace.sql"
import { Config } from "./config"
import { getAdaptor } from "./adaptors"
import { parseSSE } from "./sse"
export namespace Workspace {
export const Event = {
Ready: BusEvent.define(
"workspace.ready",
z.object({
name: z.string(),
}),
),
Failed: BusEvent.define(
"workspace.failed",
z.object({
message: z.string(),
}),
),
}
export const Info = z
.object({
id: Identifier.schema("workspace"),
branch: z.string().nullable(),
projectID: z.string(),
config: Config,
})
.meta({
ref: "Workspace",
})
export type Info = z.infer<typeof Info>
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
return {
id: row.id,
branch: row.branch,
projectID: row.project_id,
config: row.config,
}
}
export const create = fn(
z.object({
id: Identifier.schema("workspace").optional(),
projectID: Info.shape.projectID,
branch: Info.shape.branch,
config: Info.shape.config,
}),
async (input) => {
const id = Identifier.ascending("workspace", input.id)
const { config, init } = await getAdaptor(input.config).create(input.config, input.branch)
const info: Info = {
id,
projectID: input.projectID,
branch: input.branch,
config,
}
setTimeout(async () => {
await init()
Database.use((db) => {
db.insert(WorkspaceTable)
.values({
id: info.id,
branch: info.branch,
project_id: info.projectID,
config: info.config,
})
.run()
})
GlobalBus.emit("event", {
directory: id,
payload: {
type: Event.Ready.type,
properties: {},
},
})
}, 0)
return info
},
)
export function list(project: Project.Info) {
const rows = Database.use((db) =>
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.project_id, project.id)).all(),
)
return rows.map(fromRow).sort((a, b) => a.id.localeCompare(b.id))
}
export const get = fn(Identifier.schema("workspace"), async (id) => {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (!row) return
return fromRow(row)
})
export const remove = fn(Identifier.schema("workspace"), async (id) => {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (row) {
const info = fromRow(row)
await getAdaptor(info.config).remove(info.config)
Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run())
return info
}
})
const log = Log.create({ service: "workspace-sync" })
async function workspaceEventLoop(space: Info, stop: AbortSignal) {
while (!stop.aborted) {
const res = await getAdaptor(space.config)
.request(space.config, "GET", "/event", undefined, stop)
.catch(() => undefined)
if (!res || !res.ok || !res.body) {
await Bun.sleep(1000)
continue
}
await parseSSE(res.body, stop, (event) => {
GlobalBus.emit("event", {
directory: space.id,
payload: event,
})
})
// Wait 250ms and retry if SSE connection fails
await Bun.sleep(250)
}
}
export function startSyncing(project: Project.Info) {
const stop = new AbortController()
const spaces = list(project).filter((space) => space.config.type !== "worktree")
spaces.forEach((space) => {
void workspaceEventLoop(space, stop.signal).catch((error) => {
log.warn("workspace sync listener failed", {
workspaceID: space.id,
error,
})
})
})
return {
async stop() {
stop.abort()
},
}
}
}

View File

@@ -379,7 +379,7 @@ export namespace File {
}
const set = new Set<string>()
for await (const file of Ripgrep.files({ cwd: Instance.directory, maxDepth: 3 })) {
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
result.files.push(file)
let current = file
while (true) {
@@ -411,9 +411,7 @@ export namespace File {
})
export function init() {
// Disabled automatic scanning to prevent CPU/memory exhaustion
// Scanning will be triggered lazily on first access
// state()
state()
}
export async function status() {

Some files were not shown because too many files have changed in this diff Show More