mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-28 03:34:39 +00:00
Compare commits
3 Commits
beta
...
collapse-q
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d419a0211 | ||
|
|
8b168981aa | ||
|
|
724dd665ec |
@@ -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> |
|
||||
|
||||
68
bun.lock
68
bun.lock
@@ -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=="],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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": "التالي",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "次へ",
|
||||
|
||||
@@ -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": "다음",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Далее",
|
||||
|
||||
@@ -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": "ถัดไป",
|
||||
|
||||
@@ -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": "下一步",
|
||||
|
||||
@@ -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": "下一步",
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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__: {
|
||||
|
||||
@@ -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 },
|
||||
),
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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": "المهمة غير موجودة",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "タスクが見つかりません",
|
||||
|
||||
@@ -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": "태스크를 찾을 수 없음",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Задача не найдена",
|
||||
|
||||
@@ -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": "ไม่พบงาน",
|
||||
|
||||
@@ -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ı aşıldı. {{retryIn}} içinde tekrar deneyin.",
|
||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
||||
"Abonelik kotası aşı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ı",
|
||||
|
||||
@@ -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": "未找到任务",
|
||||
|
||||
@@ -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": "找不到任務",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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())),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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
|
||||
);
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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:",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -365,11 +365,6 @@ export const RunCommand = cmd({
|
||||
action: "deny",
|
||||
pattern: "*",
|
||||
},
|
||||
{
|
||||
permission: "edit",
|
||||
action: "allow",
|
||||
pattern: "*",
|
||||
},
|
||||
]
|
||||
|
||||
function title() {
|
||||
|
||||
@@ -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()))
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"], {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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")
|
||||
},
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>(),
|
||||
})
|
||||
@@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user