mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-18 14:56:27 +00:00
Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21197e4e6f | ||
|
|
db80806651 | ||
|
|
47f66de8dd | ||
|
|
c076066c33 | ||
|
|
5c09d051e5 | ||
|
|
0a6111291e | ||
|
|
609044ef0a | ||
|
|
4f32bfca57 | ||
|
|
90112f52db | ||
|
|
d4303a9ca5 | ||
|
|
735556eab8 | ||
|
|
10cac9ab5d | ||
|
|
2d0ce64111 | ||
|
|
3355c93efd | ||
|
|
469e1c035e | ||
|
|
b2e3569add | ||
|
|
0ac458b3b3 | ||
|
|
b278ef6b82 | ||
|
|
3e3a4ae46b | ||
|
|
fe0d9579a7 | ||
|
|
a2594ddefb | ||
|
|
3555cef1c8 | ||
|
|
0fa881efd0 | ||
|
|
37ff501cc6 | ||
|
|
fa126d68e8 | ||
|
|
a0571a8b14 | ||
|
|
cae475df37 | ||
|
|
19781a4447 | ||
|
|
e32de90d88 | ||
|
|
0ee5ef6c43 | ||
|
|
a72f988c90 | ||
|
|
2a132d6f50 | ||
|
|
23418b3885 | ||
|
|
b4ac938c3d | ||
|
|
4b25739e93 | ||
|
|
2f97e1324f | ||
|
|
0018f08832 | ||
|
|
b52f241294 | ||
|
|
254ace8594 | ||
|
|
657f700f5d | ||
|
|
7e1d28b549 | ||
|
|
040d2ffb48 | ||
|
|
0675975f98 | ||
|
|
130232feb6 | ||
|
|
54f2356166 | ||
|
|
ac0dd75c6a | ||
|
|
430886142b | ||
|
|
5a02ec776e | ||
|
|
c303a11725 | ||
|
|
dfceca8de7 | ||
|
|
f04c3fd82b | ||
|
|
1685e70c4b | ||
|
|
73e7b7bc79 | ||
|
|
a5b3802ca3 | ||
|
|
f28d1b44e3 | ||
|
|
b81be469c8 | ||
|
|
fca4a8fa3b | ||
|
|
8520617ca8 | ||
|
|
ab3566ab82 | ||
|
|
b642c1d9ea | ||
|
|
c8cc984aa0 | ||
|
|
5a9448acbb | ||
|
|
f6604cd367 | ||
|
|
acfacede2d | ||
|
|
4a27842fe6 | ||
|
|
d797722187 | ||
|
|
7390832f13 | ||
|
|
7689c3654e | ||
|
|
7df0935ada | ||
|
|
bcd43760df | ||
|
|
9073c522ef | ||
|
|
04c296310e |
@@ -184,6 +184,7 @@ const table = sqliteTable("session", {
|
||||
- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
|
||||
- Preserve one explicit `llm.stream(request)` call per Physical Attempt and reload projected history before durable continuation. A logical Step may use generic pre-output retries, one full-context retry after continuation rejection, incomplete-stream continuation, or one overflow-compaction rebuild. Generic retries retain the logical step number and do not consume another agent-step allowance. Do not delegate orchestration to an in-memory tool loop.
|
||||
- Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. A write-ahead execution claim marks a process-local busy period for restart recovery: terminal completion, failure, or user interruption releases it, while shutdown interruption and process death preserve it. Startup recovery resumes claimed top-level Sessions with durable per-execution attempt accounting. The claim is a recovery marker, not clustered ownership, fencing, or an exactly-once guarantee.
|
||||
- Keep native compaction mechanisms out of `SessionCompaction`. Plugins register `native` strategies through the `SessionCompaction` editor that turn a prepared request into a replacement window (the built-in `NativeCompactionPlugin` handles `@opencode/ai` compaction operations); later registrations win. Core owns the provider-mode decision, route provenance, the retry policy, overflow recovery, interruption, usage accounting, and checkpoint persistence.
|
||||
- Keep delivery vocabulary explicit. Prompts steer by default. At safe step boundaries, steered compaction takes priority up to the first steered move control; other steers retain enqueue order. At an idle boundary, steers take priority; otherwise exactly one queued item delivers before the runner reevaluates continuation. Inbox items may be cancelled or changed between queue and steer before delivery. Promoting new user input resets the selected agent's step allowance; a batch of steers resets it once.
|
||||
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
|
||||
- Keep event replay ownership separate from clustered Session execution ownership.
|
||||
|
||||
@@ -54,7 +54,7 @@ Review endpoints in document order. For each endpoint, select one disposition an
|
||||
### [x] `GET /api/health` and `GET /api/server`
|
||||
|
||||
- **Decision:** Merge and rename
|
||||
- **Replacement:** `GET /api/status` with operation ID `server.status`.
|
||||
- **Replacement:** `GET /api/info` with operation ID `server.info`.
|
||||
- **Notes:** Returns `version`, `pid`, and connection `urls`; readiness is conveyed by HTTP status.
|
||||
|
||||
### [x] `GET /api/project/current`
|
||||
@@ -74,7 +74,7 @@ Review endpoints in document order. For each endpoint, select one disposition an
|
||||
|
||||
| Done | Method | Path | Operation ID | Decision | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| [x] 001â002 | `GET` | `/api/status` | `server.status` | Keep | Replaces the former health and server endpoints. |
|
||||
| [x] 001â002 | `GET` | `/api/info` | `server.info` | Keep | Replaces the former health and server endpoints. |
|
||||
| [x] 003 | `GET` | `/api/location` | `location.get` | Keep | Workspace selectors and response fields removed until workspace support ships. |
|
||||
| [x] 004 | `GET` | `/api/project` | `project.list` | Keep | Removed unused `time.initialized`; the database column remains for migration data. |
|
||||
| [x] 005 | `PATCH` | `/api/project/{projectID}` | `project.update` | Keep | Request and response accepted as-is. |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@opencode/ai",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.1057.0",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"bin": {
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
},
|
||||
@@ -177,7 +177,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/protocol": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -203,7 +203,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -253,7 +253,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -297,7 +297,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -321,7 +321,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode/core",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
@@ -409,7 +409,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode/desktop",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"electron-context-menu": "4.1.2",
|
||||
@@ -456,12 +456,11 @@
|
||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.12",
|
||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.12",
|
||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.12",
|
||||
"msgpackr-extract": "3.0.4",
|
||||
},
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode/enterprise",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/sdk": "1.18.21",
|
||||
@@ -498,7 +497,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -514,7 +513,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode/http-recorder",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@effect/platform-node-shared": "4.0.0-rc.112",
|
||||
},
|
||||
@@ -533,7 +532,7 @@
|
||||
},
|
||||
"packages/httpapi-codegen": {
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"prettier": "3.6.2",
|
||||
@@ -546,7 +545,7 @@
|
||||
},
|
||||
"packages/latex": {
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -560,7 +559,7 @@
|
||||
},
|
||||
"packages/merman": {
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -575,7 +574,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode/ai": "workspace:*",
|
||||
@@ -614,7 +613,7 @@
|
||||
},
|
||||
"packages/plugin-browser": {
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -644,7 +643,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -659,7 +658,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -683,7 +682,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@opencode/sdk",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -704,7 +703,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@effect/platform-node-shared": "catalog:",
|
||||
@@ -726,7 +725,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
@@ -761,7 +760,7 @@
|
||||
},
|
||||
"packages/simulation": {
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/ai": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -781,7 +780,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -815,7 +814,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -834,7 +833,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode/stats-server",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -880,7 +879,7 @@
|
||||
},
|
||||
"packages/theme": {
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -894,7 +893,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -929,7 +928,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -964,7 +963,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode/util",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -997,7 +996,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1038,7 +1037,7 @@
|
||||
},
|
||||
"services/update": {
|
||||
"name": "@opencode/update",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"dependencies": {
|
||||
"jose": "6.0.11",
|
||||
"semver": "catalog:",
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-U9IuP/ev6w4urvogOwQyl3rdumY6W4YaY18NkFaOVHU=",
|
||||
"aarch64-linux": "sha256-Wc8OT2DRZpVo56KaoGE0Hsj1NDknakbWXO9w2qy6j+0=",
|
||||
"aarch64-darwin": "sha256-wAea8+jajnMDxZ6XJL+Hsrf0621hwtBtWyD1+dS45dE=",
|
||||
"x86_64-darwin": "sha256-g8PCNBSV6rO+VQjKU9AtYqj+r18o+fhLDXEQq+X2EZ4="
|
||||
"x86_64-linux": "sha256-TlCPtuIvTNF01fLI0yHCLzVkx53yF9xxvAObDMGeo8M=",
|
||||
"aarch64-linux": "sha256-aEjyPIXHeA5w1iZXbPKJAOIJQBfXg+LAx/y7C5uFz4g=",
|
||||
"aarch64-darwin": "sha256-lthMTio2qNy+H2tL9cAPmqC41O+wdSADI61cnW4dIb0=",
|
||||
"x86_64-darwin": "sha256-ONFbRQnwzOYHo0Pyh327QfJG2uzqzNEn/SS2vbYYMdM="
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "opencode",
|
||||
"description": "AI-powered development tool",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.4.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"name": "@opencode/ai",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -59,7 +59,15 @@ export const isContextOverflowFailure = (failure: unknown) =>
|
||||
: Schema.is(ProviderErrorEvent)(failure) && failure.classification === "context-overflow"
|
||||
|
||||
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown))
|
||||
const QUOTA_CODES = new Set(["insufficient_quota", "usage_not_included", "billing_error"])
|
||||
// OpenCode Zen reports account caps as typed 429/402 errors that are not throttles.
|
||||
const QUOTA_CODES = new Set([
|
||||
"insufficient_quota",
|
||||
"usage_not_included",
|
||||
"billing_error",
|
||||
"gousagelimiterror",
|
||||
"freeusagelimiterror",
|
||||
"creditlimitexceeded",
|
||||
])
|
||||
const AUTH_CODES = new Set(["authentication_error", "permission_error"])
|
||||
const SERVER_CODES = new Set([
|
||||
"api_error",
|
||||
@@ -87,7 +95,8 @@ const CONTENT_POLICY_CODES = new Set([
|
||||
// as a `[code]` label at the start of the rewritten message.
|
||||
const GATEWAY_CODE_LABEL = /^[^:\n]+: \[([A-Za-z0-9_.-]+)\]/
|
||||
const RATE_LIMIT_TEXT = /rate increased too quickly|rate[-_\s]?limit|too[_\s]?many[_\s]?requests/i
|
||||
const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded/i
|
||||
// Only consulted on 429, where throttles and account caps share a status.
|
||||
const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded|budget exceeded|usage limit/i
|
||||
// Policy rejections without a dedicated code, matched against the provider's own
|
||||
// explanation only. OpenAI reuses `invalid_prompt` for usage-policy rejections while
|
||||
// Bedrock Mantle reuses it for schema validation; Anthropic reports blocked output
|
||||
@@ -143,7 +152,11 @@ export function classifyProviderFailure(input: ProviderFailure): AIError["reason
|
||||
return new InvalidRequestError({ ...details, classification: "payload-too-large" })
|
||||
if (codes.some((code) => CONTENT_POLICY_CODES.has(code)) || (clientScoped && CONTENT_POLICY_TEXT.test(input.message)))
|
||||
return new ContentPolicyError(details)
|
||||
if (codes.some((code) => QUOTA_CODES.has(code)) || (input.status === 429 && QUOTA_TEXT.test(text)))
|
||||
if (
|
||||
input.status === 402 ||
|
||||
codes.some((code) => QUOTA_CODES.has(code)) ||
|
||||
(input.status === 429 && QUOTA_TEXT.test(text))
|
||||
)
|
||||
return new QuotaExceededError(details)
|
||||
if (input.status === 401 || input.status === 403 || codes.some((code) => AUTH_CODES.has(code)))
|
||||
return new AuthenticationError(details)
|
||||
@@ -163,10 +176,12 @@ export function classifyProviderFailure(input: ProviderFailure): AIError["reason
|
||||
input.status === 408 ||
|
||||
input.status === 409 ||
|
||||
(input.status !== undefined && input.status >= 500) ||
|
||||
// Server codes and phrasing only decide when no HTTP status contradicts them:
|
||||
// gateways such as OpenCode Zen substitute `server_error` for codes they do
|
||||
// not forward, so a 4xx with a server code is still a rejected request.
|
||||
((input.status === undefined || input.status < 400) &&
|
||||
!codes.some((code) => INVALID_REQUEST_CODES.has(code)) &&
|
||||
SERVER_ERROR_TEXT.test(text)) ||
|
||||
codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable"))
|
||||
((!codes.some((code) => INVALID_REQUEST_CODES.has(code)) && SERVER_ERROR_TEXT.test(text)) ||
|
||||
codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable"))))
|
||||
)
|
||||
return new ProviderInternalError({
|
||||
...details,
|
||||
|
||||
@@ -309,7 +309,7 @@ describe("RequestExecutor", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("classifies provider overloads hidden behind HTTP 400", () =>
|
||||
it.effect("does not let server codes override a 4xx rejection", () =>
|
||||
Effect.gen(function* () {
|
||||
const classify = (body: string) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -317,11 +317,11 @@ describe("RequestExecutor", () => {
|
||||
const error = yield* executor.execute(request).pipe(Effect.flip)
|
||||
|
||||
expectAIError(error)
|
||||
expect(error.reason).toMatchObject({ _tag: "ProviderInternal" })
|
||||
expect(error.reason).toMatchObject({ _tag: "InvalidRequest" })
|
||||
}).pipe(Effect.provide(fixedResponse(body, { status: 400 })))
|
||||
|
||||
yield* classify('{"code":"resource_exhausted"}')
|
||||
yield* classify('{"code":"service_unavailable"}')
|
||||
yield* classify('{"error":{"type":"server_error","message":"Upstream request failed: Model is unavailable."}}')
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -249,10 +249,54 @@ describe("provider error classification", () => {
|
||||
|
||||
test("classifies any remaining 4xx status as an invalid request", () => {
|
||||
expect(
|
||||
[400, 402, 404, 418, 422, 451].map(
|
||||
(status) => classifyProviderFailure({ message: `HTTP ${status}`, status })._tag,
|
||||
[400, 404, 418, 422, 451].map((status) => classifyProviderFailure({ message: `HTTP ${status}`, status })._tag),
|
||||
).toEqual(Array(5).fill("InvalidRequest"))
|
||||
})
|
||||
|
||||
test("classifies 402 as exhausted quota", () => {
|
||||
expect(classifyProviderFailure({ message: "Payment Required", status: 402 })._tag).toBe("QuotaExceeded")
|
||||
})
|
||||
|
||||
test("classifies OpenCode Zen account limits as quota rather than throttling", () => {
|
||||
const typed = (type: string, message: string) => ({ type: "error", error: { type, message } })
|
||||
const substituted = (message: string) => ({
|
||||
error: { type: "server_error", message: `Upstream request failed: ${message}` },
|
||||
})
|
||||
const cases: ReadonlyArray<[number, { error: { message: string } }]> = [
|
||||
[429, typed("GoUsageLimitError", "Go usage limit exceeded")],
|
||||
[429, typed("FreeUsageLimitError", "Rate limit exceeded. Please try again later.")],
|
||||
[402, typed("CreditLimitExceeded", "Credit limit exceeded.")],
|
||||
[402, substituted("Insufficient account funds")],
|
||||
[402, substituted("Account invoice is overdue")],
|
||||
[429, substituted("Account budget exceeded")],
|
||||
]
|
||||
expect(
|
||||
cases.map(
|
||||
([status, body]) =>
|
||||
classifyProviderFailure({ message: body.error.message, status, rawBody: JSON.stringify(body) })._tag,
|
||||
),
|
||||
).toEqual(Array(6).fill("InvalidRequest"))
|
||||
).toEqual(Array(6).fill("QuotaExceeded"))
|
||||
})
|
||||
|
||||
test("does not let substituted server codes make a 4xx retryable", () => {
|
||||
const openai = { error: { type: "server_error", message: "Upstream request failed: Model is unavailable." } }
|
||||
const anthropic = {
|
||||
type: "error",
|
||||
error: { type: "api_error", message: "Upstream request failed: Model is unavailable." },
|
||||
}
|
||||
expect(
|
||||
[openai, anthropic].map(
|
||||
(body) =>
|
||||
classifyProviderFailure({ message: body.error.message, status: 400, rawBody: JSON.stringify(body) })._tag,
|
||||
),
|
||||
).toEqual(["InvalidRequest", "InvalidRequest"])
|
||||
// Without a contradicting status the same codes still mark provider trouble.
|
||||
expect(classifyProviderFailure({ message: openai.error.message, rawBody: JSON.stringify(openai) })._tag).toBe(
|
||||
"ProviderInternal",
|
||||
)
|
||||
expect(
|
||||
classifyProviderFailure({ message: openai.error.message, status: 200, rawBody: JSON.stringify(openai) })._tag,
|
||||
).toBe("ProviderInternal")
|
||||
})
|
||||
|
||||
test("classifies nested provider codes when a top-level code is also present", () => {
|
||||
|
||||
@@ -42,3 +42,18 @@ story("hides and restores the same registration for Review tabs and unmount", as
|
||||
await root.getByRole("button", { name: "Unmount pane", exact: true }).click()
|
||||
await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
|
||||
})
|
||||
|
||||
story("hides the native view immediately while the pane stays mounted", async ({ page }) => {
|
||||
const root = page.getByTestId("browser-pane-fixture")
|
||||
const toggle = root.getByRole("button", { name: "Toggle Review tab", exact: true })
|
||||
await expect(toggle).toBeEnabled()
|
||||
// Read in the same task as the click so a deferred animation-frame hide cannot pass.
|
||||
const visible = await toggle.evaluate((element) => {
|
||||
element.dispatchEvent(new MouseEvent("click", { bubbles: true }))
|
||||
return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
|
||||
})
|
||||
expect(visible).toBe("false")
|
||||
await expect(root.locator("#browser-panel")).toHaveCount(1)
|
||||
await toggle.click()
|
||||
await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
|
||||
})
|
||||
|
||||
@@ -9,12 +9,17 @@ for (const theme of ["light", "dark"]) {
|
||||
const reveal = logo.locator('[data-slot="wordmark-reveal"]')
|
||||
const shimmer = logo.locator(".wordmark-shimmer")
|
||||
const base = logo.locator("svg").first()
|
||||
await expect(base).toHaveCSS("opacity", theme === "dark" ? "0.5" : "0.6")
|
||||
await expect(base).toHaveCSS("opacity", "0.16")
|
||||
await expect(shimmer).toHaveCSS("animation-iteration-count", "1")
|
||||
await expect(logo.locator("g[mask]")).toHaveCount(0)
|
||||
await expect(shimmer).toHaveCSS("color", "rgb(255, 255, 255)")
|
||||
await expect(shimmer).toHaveCSS("mix-blend-mode", "screen")
|
||||
await expect(base.locator("g[fill]")).toHaveAttribute("fill", "currentColor")
|
||||
await expect(base.locator("path").nth(1)).toHaveAttribute("fill", "var(--icon-base)")
|
||||
await expect(base.locator("path").nth(9)).toHaveCSS(
|
||||
"fill",
|
||||
await base.locator("path").nth(1).evaluate((element) => getComputedStyle(element).fill),
|
||||
)
|
||||
await expect(base.locator('path[fill="var(--icon-weak-base)"]')).toHaveCount(8)
|
||||
|
||||
await logo.evaluate((element) => {
|
||||
element.getAnimations({ subtree: true }).forEach((animation) => {
|
||||
@@ -77,7 +82,7 @@ for (const theme of ["light", "dark"]) {
|
||||
await page.setViewportSize({ width: 360, height: 640 })
|
||||
const component = await mount("app-new-session-wordmark--reveal", { globals: { theme } })
|
||||
const logo = component.locator('[data-component="new-session-wordmark"]')
|
||||
await expect(logo.locator("svg").first()).toHaveCSS("opacity", theme === "dark" ? "0.5" : "0.6")
|
||||
await expect(logo.locator("svg").first()).toHaveCSS("opacity", "0.16")
|
||||
await expect(logo.locator('[data-slot="wordmark-reveal"]')).toHaveCSS("opacity", "1")
|
||||
await expect(logo.locator(".wordmark-shimmer")).toHaveCSS("opacity", "0")
|
||||
expect(await logo.evaluate((element) => element.getAnimations({ subtree: true }).length)).toBe(0)
|
||||
|
||||
@@ -485,6 +485,7 @@ export function stepStarted(message: SessionMessageAssistant) {
|
||||
assistantMessageID: message.id,
|
||||
agent: message.agent,
|
||||
model: message.model,
|
||||
started: message.time.created,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ async function mockServers(page: Page, requests: string[]) {
|
||||
const current = url.origin === serverA ? sessionA : sessionB
|
||||
const directory = url.searchParams.get("directory")
|
||||
if (directory && directory !== current.directory) return json(route, { name: "InvalidDirectory" }, 500)
|
||||
if (url.pathname === "/api/status") return json(route, { version: "test", pid: 1, urls: [url.origin] })
|
||||
if (url.pathname === "/api/info")
|
||||
return json(route, { version: "test", pid: 1, urls: [url.origin], paths: { tmp: "/tmp/opencode" } })
|
||||
if (url.pathname === "/api/session") return json(route, { data: [currentSession(current)], cursor: {} })
|
||||
if (url.pathname === "/api/session/active") return json(route, { data: {} })
|
||||
if (url.pathname === `/api/session/${current.id}`) return json(route, { data: currentSession(current) })
|
||||
|
||||
@@ -108,6 +108,15 @@ test("non-Git folders show their status without offering worktree actions", asyn
|
||||
).toBeEnabled()
|
||||
})
|
||||
|
||||
test("submits locally after changing a new worktree draft to Local", async ({ page }) => {
|
||||
const mock = await openDraft(page, "create", { currentDirectory: workspace })
|
||||
await page.getByRole("button", { name: "New worktree", exact: true }).click()
|
||||
await page.getByRole("menuitem", { name: "Local repository", exact: true }).click()
|
||||
await page.locator('[data-component="composer-editor"]').fill("Run locally")
|
||||
await page.locator('[data-action="composer-submit"]').click()
|
||||
await expect.poll(() => mock.calls.find((call) => call.type === "session")?.directory).toBe(directory)
|
||||
})
|
||||
|
||||
test("new worktree MCP choices persist per draft and apply before the first prompt", async ({ page }, testInfo) => {
|
||||
const mock = await openDraft(page, "create")
|
||||
await page.locator('[data-component="composer-editor"]').fill("Use my selected MCPs")
|
||||
@@ -296,7 +305,12 @@ test("new worktree sign-in completes before the draft can send", async ({ page,
|
||||
expect(attempts).toHaveLength(1)
|
||||
})
|
||||
|
||||
async function openDraft(page: Page, worktree = "main", options: { git?: boolean; direction?: "ltr" | "rtl" } = {}) {
|
||||
async function openDraft(
|
||||
page: Page,
|
||||
worktree = "main",
|
||||
options: { git?: boolean; direction?: "ltr" | "rtl"; currentDirectory?: string } = {},
|
||||
) {
|
||||
const currentDirectory = options.currentDirectory ?? directory
|
||||
const project = {
|
||||
id: "proj_new_summary",
|
||||
worktree: directory,
|
||||
@@ -315,7 +329,7 @@ async function openDraft(page: Page, worktree = "main", options: { git?: boolean
|
||||
const prompts: { sessionID: string; body: Record<string, unknown> }[] = []
|
||||
const state: { fail: boolean; hold?: Promise<void>; holdDirectory?: string } = { fail: false }
|
||||
await mockOpenCodeServer(page, {
|
||||
directory,
|
||||
directory: currentDirectory,
|
||||
project,
|
||||
sessions,
|
||||
provider: {
|
||||
@@ -442,7 +456,7 @@ async function openDraft(page: Page, worktree = "main", options: { git?: boolean
|
||||
},
|
||||
)
|
||||
await page.addInitScript(
|
||||
({ directory, server, draftID, secondDraftID, worktree }) => {
|
||||
({ directory, currentDirectory, server, draftID, secondDraftID, worktree }) => {
|
||||
if (!localStorage.getItem("opencode.global.dat:server"))
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:server",
|
||||
@@ -455,12 +469,12 @@ async function openDraft(page: Page, worktree = "main", options: { git?: boolean
|
||||
localStorage.setItem(
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify([
|
||||
{ type: "draft", draftID, server, directory, worktree },
|
||||
{ type: "draft", draftID: secondDraftID, server, directory, worktree },
|
||||
{ type: "draft", draftID, server, directory: currentDirectory, worktree },
|
||||
{ type: "draft", draftID: secondDraftID, server, directory: currentDirectory, worktree },
|
||||
]),
|
||||
)
|
||||
},
|
||||
{ directory, server, draftID, secondDraftID, worktree },
|
||||
{ directory, currentDirectory, server, draftID, secondDraftID, worktree },
|
||||
)
|
||||
if (options.direction) await openWithDirection(page, draftPath, options.direction)
|
||||
if (!options.direction) await page.goto(draftPath)
|
||||
|
||||
@@ -16,8 +16,13 @@ test("server dialog keeps focus above fullscreen settings", async ({ page }) =>
|
||||
body: 'data: {"id":"evt_connected","type":"server.connected","data":{}}\n\n',
|
||||
})
|
||||
}
|
||||
if (url.pathname === "/api/status") {
|
||||
return json(route, { version: "2.0.0", pid: 1, urls: [url.origin] })
|
||||
if (url.pathname === "/api/info") {
|
||||
return json(route, {
|
||||
version: "2.0.0",
|
||||
pid: 1,
|
||||
urls: [url.origin],
|
||||
paths: { tmp: "/tmp/opencode" },
|
||||
})
|
||||
}
|
||||
return json(route, {})
|
||||
})
|
||||
|
||||
@@ -284,7 +284,7 @@ for (const delivery of ["steer", "queue"] as const) {
|
||||
await expect(thinking).toHaveCount(0)
|
||||
|
||||
// The next assistant step still belongs to U1: U2 has been admitted, not delivered.
|
||||
mock.emit("session.step.started", { sessionID, assistantMessageID: assistantID, agent: "build", model })
|
||||
mock.emit("session.step.started", { sessionID, assistantMessageID: assistantID, agent: "build", model, started: Date.now() })
|
||||
for (const tool of [
|
||||
{ id: "tool_queue_read", name: "read", input: { path: "src/queue.ts" } },
|
||||
{ id: "tool_queue_grep", name: "grep", input: { pattern: "retry", path: "src" } },
|
||||
@@ -341,7 +341,7 @@ for (const delivery of ["steer", "queue"] as const) {
|
||||
)
|
||||
|
||||
const later = { sessionID, assistantMessageID: "msg_queue_follow_up_assistant" }
|
||||
mock.emit("session.step.started", { ...later, agent: "build", model })
|
||||
mock.emit("session.step.started", { ...later, agent: "build", model, started: Date.now() })
|
||||
mock.emit("session.text.started", { ...later, ordinal: 0 })
|
||||
mock.emit("session.text.ended", { ...later, ordinal: 0, text: "A3: Now checking the retry path for U2." })
|
||||
const response = transcript
|
||||
|
||||
@@ -138,7 +138,14 @@ test("MCP authentication starts before a slow resource catalog finishes", async
|
||||
test("multiple desktop connections show the session's server name", async ({ page }) => {
|
||||
await mockStressTimeline(page)
|
||||
await page.route("http://secondary.test/**", (route) =>
|
||||
route.fulfill({ json: { version: "2.0.0", pid: 1, urls: ["http://secondary.test"] } }),
|
||||
route.fulfill({
|
||||
json: {
|
||||
version: "2.0.0",
|
||||
pid: 1,
|
||||
urls: ["http://secondary.test"],
|
||||
paths: { tmp: "/tmp/opencode" },
|
||||
},
|
||||
}),
|
||||
)
|
||||
await page.addInitScript(
|
||||
({ directory, server }) => {
|
||||
|
||||
@@ -109,11 +109,11 @@ test("passes through non-event fetches", async ({ page }) => {
|
||||
const timeline = await setupTimeline(page)
|
||||
|
||||
const health = await page.evaluate(async () => {
|
||||
const response = await fetch("/api/status")
|
||||
const response = await fetch("/api/info")
|
||||
return response.json()
|
||||
})
|
||||
|
||||
expect(health).toEqual({ version: "2.0.0", pid: 1, urls: [] })
|
||||
expect(health).toEqual({ version: "2.0.0", pid: 1, urls: [], paths: { tmp: "/tmp/opencode" } })
|
||||
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1)
|
||||
})
|
||||
|
||||
|
||||
@@ -172,64 +172,6 @@ test("Models and Shortcuts autofocus their filters on normal navigation", async
|
||||
await expect(result).toBeFocused()
|
||||
})
|
||||
|
||||
for (const count of [7, 8]) {
|
||||
test(`Projects search uses the full list threshold with ${count} projects`, async ({ page }) => {
|
||||
await page.route("**/api/project", (route) => route.fulfill({ json: projectList(count) }))
|
||||
await page.reload()
|
||||
const view = ui(page)
|
||||
await view.search.fill("OpenCode")
|
||||
await expect(view.results.getByRole("option")).toHaveCount(count)
|
||||
await view.search.clear()
|
||||
await view.settings.getByRole("tab", { name: "Projects", exact: true }).click()
|
||||
const search = view.settings.getByRole("searchbox", { name: "Search projects", exact: true })
|
||||
const projects = view.settings.getByRole("button", { name: /^OpenCode / })
|
||||
await expect(projects).toHaveCount(count)
|
||||
if (count === 7) {
|
||||
await expect(search).toHaveCount(0)
|
||||
return
|
||||
}
|
||||
await expect(search).toBeFocused()
|
||||
await search.fill(" CODE 06 ")
|
||||
await expect(projects).toHaveCount(1)
|
||||
await expect(projects).toHaveAccessibleName("OpenCode 06")
|
||||
await expect(search).toBeVisible()
|
||||
await search.fill("missing-project")
|
||||
await expect(projects).toHaveCount(0)
|
||||
await expect(view.settings.getByText("No projects found", { exact: true })).toBeVisible()
|
||||
await view.settings.getByRole("button", { name: "Clear", exact: true }).click()
|
||||
await expect(search).toBeFocused()
|
||||
await expect(projects).toHaveCount(count)
|
||||
await view.settings.getByRole("tab", { name: "Models", exact: true }).click()
|
||||
await view.settings.getByRole("tab", { name: "Projects", exact: true }).click()
|
||||
await expect(search).toBeFocused()
|
||||
await search.fill("OpenCode 06")
|
||||
await projects.click()
|
||||
await expect(view.settings.getByRole("heading", { name: "OpenCode 06", exact: true })).toBeVisible()
|
||||
})
|
||||
}
|
||||
|
||||
test("Projects search focuses when the qualifying inventory arrives after opening", async ({ page }) => {
|
||||
const inventory = Promise.withResolvers<void>()
|
||||
await page.route("**/api/project", async (route) => {
|
||||
await inventory.promise
|
||||
await route.fulfill({ json: projectList(8) })
|
||||
})
|
||||
const requested = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/project")
|
||||
await page.reload()
|
||||
await requested
|
||||
const view = ui(page)
|
||||
const search = view.settings.getByRole("searchbox", { name: "Search projects", exact: true })
|
||||
try {
|
||||
await view.settings.getByRole("tab", { name: "Projects", exact: true }).click()
|
||||
await expect(view.settings.getByRole("heading", { name: "Projects", exact: true })).toBeVisible()
|
||||
await expect(search).toHaveCount(0)
|
||||
} finally {
|
||||
inventory.resolve()
|
||||
}
|
||||
await expect(search).toBeFocused()
|
||||
await expect(view.settings.getByRole("button", { name: /^OpenCode / })).toHaveCount(8)
|
||||
})
|
||||
|
||||
test("all indexed client controls resolve to visible production controls", async ({ page }) => {
|
||||
const view = ui(page)
|
||||
for (const entry of clientSettings.filter((entry) => entry.target && !entry.available)) {
|
||||
|
||||
@@ -81,10 +81,10 @@ const fixture = test.extend<{ site: Site }, { builds: Record<string, Record<stri
|
||||
response.setHeader("cache-control", "no-store")
|
||||
if (path === "/observer.html")
|
||||
return void response.writeHead(200, { "content-type": "text/html" }).end("<title>Worker observer</title>")
|
||||
if (path === "/api/status")
|
||||
if (path === "/api/info")
|
||||
return void response
|
||||
.writeHead(200, { "content-type": "application/json" })
|
||||
.end(`{"version":"test","pid":1,"urls":["${url.origin}"]}`)
|
||||
.end(`{"version":"test","pid":1,"urls":["${url.origin}"],"paths":{"tmp":"/tmp/opencode"}}`)
|
||||
if (path === "/sw.js" && state.legacy && state.version === "old") {
|
||||
// Model the shipped worker's shared precache name and cache-first navigation behavior.
|
||||
const urls = Object.keys(builds.old).filter(
|
||||
@@ -334,8 +334,13 @@ fixture("upgrades the legacy shared precache only after old tabs close", async (
|
||||
|
||||
fixture("does not substitute cached HTML for API or missing asset navigations", async ({ page, site }) => {
|
||||
await install(page, site.url)
|
||||
const api = await page.goto(`${site.url}/api/status`)
|
||||
expect(await api?.json()).toEqual({ version: "test", pid: 1, urls: ["http://localhost"] })
|
||||
const api = await page.goto(`${site.url}/api/info`)
|
||||
expect(await api?.json()).toEqual({
|
||||
version: "test",
|
||||
pid: 1,
|
||||
urls: ["http://localhost"],
|
||||
paths: { tmp: "/tmp/opencode" },
|
||||
})
|
||||
expect(api?.fromServiceWorker()).toBe(false)
|
||||
const asset = await page.goto(`${site.url}/_assets/missing.js`)
|
||||
expect(asset?.status()).toBe(404)
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MockBadRequest extends Schema.TaggedError<MockBadRequest>()("MockBa
|
||||
}) {}
|
||||
|
||||
const Group = HttpApiGroup.make("mock")
|
||||
.add(HttpApiEndpoint.get("status", "/api/status", { success: Json }))
|
||||
.add(HttpApiEndpoint.get("info", "/api/info", { success: Json }))
|
||||
.add(
|
||||
HttpApiEndpoint.get("event", "/api/event", {
|
||||
success: Schema.String.pipe(HttpApiSchema.asText({ contentType: "text/event-stream" })),
|
||||
|
||||
@@ -219,7 +219,13 @@ function mockHandlers(config: MockServerConfig, state: { cursors: Map<string, st
|
||||
}),
|
||||
)
|
||||
.handleAll({
|
||||
status: () => Effect.succeed({ version: "2.0.0", pid: 1, urls: config.server ? [config.server] : [] }),
|
||||
info: () =>
|
||||
Effect.succeed({
|
||||
version: "2.0.0",
|
||||
pid: 1,
|
||||
urls: config.server ? [config.server] : [],
|
||||
paths: { tmp: "/tmp/opencode" },
|
||||
}),
|
||||
config: () => Effect.succeed(configEntries),
|
||||
reference: () =>
|
||||
Effect.succeed({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.8",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -16,6 +16,86 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="upload-row"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
& + & {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
[data-slot="upload-row-label"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
line-height: var(--line-height-base);
|
||||
letter-spacing: -0.04px;
|
||||
}
|
||||
|
||||
[data-slot="upload-row-name"] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--v2-text-text-base);
|
||||
font-weight: 530;
|
||||
}
|
||||
|
||||
[data-slot="upload-row-percent"] {
|
||||
flex-shrink: 0;
|
||||
color: var(--v2-text-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
[data-slot="upload-row-track"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
[data-component="upload-progress"] {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: var(--v2-background-bg-layer-02);
|
||||
}
|
||||
|
||||
[data-slot="upload-progress-bar"] {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--v2-icon-icon-base);
|
||||
transition: width 160ms ease-out;
|
||||
}
|
||||
|
||||
[data-slot="upload-row-cancel"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--v2-icon-icon-muted);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--v2-overlay-simple-overlay-hover);
|
||||
color: var(--v2-icon-icon-base);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="composer-attachments"] {
|
||||
timeline-scope: --composer-attachments-scroll;
|
||||
|
||||
|
||||
@@ -1,66 +1,12 @@
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { createBlobReference } from "@/runtime/persistence/drafts"
|
||||
import { uuid } from "@/runtime/persistence/uuid"
|
||||
import type { ComposerAttachment, ComposerPrompt } from "../types"
|
||||
|
||||
const accepted = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"application/pdf",
|
||||
"text/*",
|
||||
"application/json",
|
||||
"application/ld+json",
|
||||
"application/toml",
|
||||
"application/x-toml",
|
||||
"application/x-yaml",
|
||||
"application/xml",
|
||||
"application/yaml",
|
||||
".c",
|
||||
".cc",
|
||||
".cjs",
|
||||
".conf",
|
||||
".cpp",
|
||||
".css",
|
||||
".csv",
|
||||
".cts",
|
||||
".env",
|
||||
".go",
|
||||
".gql",
|
||||
".graphql",
|
||||
".h",
|
||||
".hh",
|
||||
".hpp",
|
||||
".htm",
|
||||
".html",
|
||||
".ini",
|
||||
".java",
|
||||
".js",
|
||||
".json",
|
||||
".jsx",
|
||||
".log",
|
||||
".md",
|
||||
".mdx",
|
||||
".mjs",
|
||||
".mts",
|
||||
".py",
|
||||
".rb",
|
||||
".rs",
|
||||
".sass",
|
||||
".scss",
|
||||
".sh",
|
||||
".sql",
|
||||
".toml",
|
||||
".ts",
|
||||
".tsx",
|
||||
".txt",
|
||||
".xml",
|
||||
".yaml",
|
||||
".yml",
|
||||
".zsh",
|
||||
]
|
||||
import type { ComposerPrompt } from "../types"
|
||||
import type { ImageAttachmentPart, PathAttachmentPart } from "../state"
|
||||
import type { AttachmentDestination } from "./destination"
|
||||
import { uploads } from "./uploads"
|
||||
|
||||
type PromptTarget = {
|
||||
current: () => ComposerPrompt
|
||||
@@ -74,10 +20,11 @@ export type ComposerAttachmentConfig = {
|
||||
onFile: (file: File) => Promise<unknown>,
|
||||
) => Promise<void>
|
||||
directory: () => string
|
||||
destination: () => AttachmentDestination
|
||||
isDialogActive: () => boolean
|
||||
warn: () => void
|
||||
duplicate: () => void
|
||||
onError: (error: unknown) => void
|
||||
onUploadError: (error: unknown) => void
|
||||
readClipboardImage?: () => Promise<File | null>
|
||||
getPathForFile?: (file: File) => string
|
||||
onDragCancel?: (callback: () => void) => () => void
|
||||
@@ -102,13 +49,23 @@ export function createComposerAttachments(
|
||||
if (!editor) return
|
||||
return { prompt, cursor: prompt.cursor() ?? cursorPosition(editor) }
|
||||
}
|
||||
const add = async (file: File, toast = true, target = capture(), clipboard = false) => {
|
||||
// Uploads this composer started; they finish (or fail) even if the composer unmounts.
|
||||
const [pending, setPending] = createStore<{ ids: string[] }>({ ids: [] })
|
||||
|
||||
// Media the model reads natively travels inline with the prompt, so its bytes live in the draft
|
||||
// store. Everything else, including text, reaches the model as a path on the server that its
|
||||
// tools open; those bytes never enter the store, and never get base64-encoded into the request.
|
||||
const add = async (file: File, target = capture(), clipboard = false) => {
|
||||
if (!target) return false
|
||||
const mime = await attachmentMime(file)
|
||||
if (!mime) {
|
||||
if (toast) input.warn()
|
||||
return false
|
||||
}
|
||||
const destination = input.destination()
|
||||
if (native(mime, destination.input) && file.size <= MAX_INLINE_BYTES) return addInline(file, mime, target, clipboard)
|
||||
const sourcePath = input.getPathForFile?.(file) || undefined
|
||||
if (destination.local && sourcePath) return addPath(target, { filename: file.name, mime, path: sourcePath })
|
||||
void stage(file, mime, target, destination)
|
||||
return true
|
||||
}
|
||||
const addInline = async (file: File, mime: string, target: NonNullable<ReturnType<typeof capture>>, clipboard: boolean) => {
|
||||
const blob = input.store ? await input.store(file) : await createBlobReference(file)
|
||||
const sourcePath = input.getPathForFile?.(file) || undefined
|
||||
// Native clipboard images arrive with a fresh timestamped filename on every paste, so identical
|
||||
@@ -127,24 +84,45 @@ export function createComposerAttachments(
|
||||
input.duplicate()
|
||||
return true
|
||||
}
|
||||
const attachment: ComposerAttachment = {
|
||||
type: "image",
|
||||
id: uuid(),
|
||||
filename: file.name,
|
||||
sourcePath,
|
||||
mime,
|
||||
blob,
|
||||
}
|
||||
const attachment: ImageAttachmentPart = { type: "image", id: uuid(), filename: file.name, sourcePath, mime, blob }
|
||||
target.prompt.set([...target.prompt.current(), attachment], target.cursor)
|
||||
return true
|
||||
}
|
||||
const addAttachments = async (files: File[], toast = true, target = capture()) => {
|
||||
const found = await files.reduce(async (result, file) => {
|
||||
const addPath = (
|
||||
target: NonNullable<ReturnType<typeof capture>>,
|
||||
attachment: Pick<PathAttachmentPart, "filename" | "mime" | "path">,
|
||||
) => {
|
||||
if (target.prompt.current().some((part) => part.type === "path" && part.path === attachment.path)) {
|
||||
input.duplicate()
|
||||
return true
|
||||
}
|
||||
target.prompt.set([...target.prompt.current(), { type: "path", id: uuid(), ...attachment }], target.prompt.cursor())
|
||||
return true
|
||||
}
|
||||
const stage = async (
|
||||
file: File,
|
||||
mime: string,
|
||||
target: NonNullable<ReturnType<typeof capture>>,
|
||||
destination: AttachmentDestination,
|
||||
) => {
|
||||
const id = uuid()
|
||||
setPending("ids", (ids) => [...ids, id])
|
||||
const path = await uploads
|
||||
.track({ id, filename: file.name, mime, size: file.size }, (report, signal) =>
|
||||
destination.upload(file, report, signal),
|
||||
)
|
||||
.catch((error: unknown) => {
|
||||
input.onUploadError(error)
|
||||
return undefined
|
||||
})
|
||||
.finally(() => setPending("ids", (ids) => ids.filter((item) => item !== id)))
|
||||
if (path) addPath(target, { filename: file.name, mime, path })
|
||||
}
|
||||
const addAttachments = async (files: File[], target = capture()) => {
|
||||
return files.reduce(async (result, file) => {
|
||||
const previous = await result
|
||||
return (await add(file, false, target)) || previous
|
||||
return (await add(file, target)) || previous
|
||||
}, Promise.resolve(false))
|
||||
if (!found && files.length > 0 && toast) input.warn()
|
||||
return found
|
||||
}
|
||||
const handlePaste = async (event: ClipboardEvent) => {
|
||||
const clipboardData = event.clipboardData
|
||||
@@ -159,13 +137,13 @@ export function createComposerAttachments(
|
||||
return file ? [file] : []
|
||||
})
|
||||
if (files.length > 0) {
|
||||
await addAttachments(files, true, target)
|
||||
await addAttachments(files, target)
|
||||
return
|
||||
}
|
||||
const plainText = clipboardData.getData("text/plain") ?? ""
|
||||
if (input.readClipboardImage && !plainText) {
|
||||
const file = await input.readClipboardImage()
|
||||
if (file && (await add(file, true, target, true))) return
|
||||
if (file && (await add(file, target, true))) return
|
||||
}
|
||||
if (!plainText) return
|
||||
const text = plainText.includes("\r") ? plainText.replace(/\r\n?/g, "\n") : plainText
|
||||
@@ -218,20 +196,33 @@ export function createComposerAttachments(
|
||||
addAttachments,
|
||||
handlePaste,
|
||||
handleDrop,
|
||||
/** Uploads still in flight for this composer; sending waits for them. */
|
||||
pending: () => uploads.items().filter((item) => pending.ids.includes(item.id)),
|
||||
cancel(id: string) {
|
||||
uploads.items().find((item) => item.id === id)?.cancel()
|
||||
},
|
||||
pick(fallback: () => void) {
|
||||
if (!input.picker) {
|
||||
fallback()
|
||||
return
|
||||
}
|
||||
void input
|
||||
.picker({ defaultPath: input.directory(), multiple: true, accept: accepted }, (file) => add(file))
|
||||
.catch(input.onError)
|
||||
void input.picker({ defaultPath: input.directory(), multiple: true }, (file) => add(file)).catch(input.onError)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const imageMimes = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"])
|
||||
|
||||
// The server rejects inline attachments above this size, so larger media takes the path route.
|
||||
const MAX_INLINE_BYTES = 20 * 1024 * 1024
|
||||
|
||||
// Mirrors the media the server forwards to the model as message content.
|
||||
function native(mime: string, input: AttachmentDestination["input"]) {
|
||||
if (imageMimes.has(mime)) return input.image
|
||||
if (mime === "application/pdf") return input.pdf
|
||||
return false
|
||||
}
|
||||
|
||||
const imageExtensions = new Map([
|
||||
["gif", "image/gif"],
|
||||
["jpeg", "image/jpeg"],
|
||||
@@ -249,6 +240,8 @@ const textMimes = new Set([
|
||||
"application/yaml",
|
||||
])
|
||||
|
||||
// Text-like files normalize to text/plain so the chip labels them as text; every other file keeps
|
||||
// a binary type. Delivery is decided separately: native media inline, everything else by path.
|
||||
async function attachmentMime(file: File) {
|
||||
const type = file.type.split(";", 1)[0]?.trim().toLowerCase() ?? ""
|
||||
if (imageMimes.has(type) || type === "application/pdf") return type
|
||||
@@ -259,10 +252,11 @@ async function attachmentMime(file: File) {
|
||||
if (type.startsWith("text/") || textMimes.has(type) || type.endsWith("+json") || type.endsWith("+xml")) {
|
||||
return "text/plain"
|
||||
}
|
||||
const binary = type || "application/octet-stream"
|
||||
const bytes = new Uint8Array(await file.slice(0, 4096).arrayBuffer())
|
||||
if (bytes.some((byte) => byte === 0)) return
|
||||
if (bytes.some((byte) => byte === 0)) return binary
|
||||
const control = bytes.filter((byte) => byte < 9 || (byte > 13 && byte < 32)).length
|
||||
if (bytes.length > 0 && control / bytes.length > 0.3) return
|
||||
if (bytes.length > 0 && control / bytes.length > 0.3) return binary
|
||||
return "text/plain"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Accessor } from "solid-js"
|
||||
import { useServer } from "@/runtime/server/current"
|
||||
import { useServerSDK } from "@/runtime/server/client"
|
||||
import { authTokenFromCredentials } from "@/runtime/server/api"
|
||||
import { useWorkspaceLocation } from "@/workspaces/location"
|
||||
import type { ComposerControls } from "../adapter"
|
||||
|
||||
// Where a prompt is headed: the model that reads it and the server that runs its tools.
|
||||
export type AttachmentDestination = {
|
||||
/** Input modalities the selected model reads natively. */
|
||||
input: { image: boolean; pdf: boolean }
|
||||
/** The server shares the client's filesystem, so an attachment's source path resolves as-is. */
|
||||
local: boolean
|
||||
/** Streams a file into the server's temporary directory and returns its absolute path there. */
|
||||
upload: (file: File, report: (loaded: number) => void, signal: AbortSignal) => Promise<string>
|
||||
}
|
||||
|
||||
export function useAttachmentDestination(controls: Accessor<ComposerControls>) {
|
||||
const server = useServer()
|
||||
const sdk = useServerSDK()
|
||||
const location = useWorkspaceLocation()
|
||||
return (): AttachmentDestination => ({
|
||||
input: controls().model.selection.current()?.capabilities.input ?? { image: false, pdf: false },
|
||||
local: server.isLocal,
|
||||
upload: async (file, report, signal) => {
|
||||
const info = await sdk.api.server.info({ signal })
|
||||
// One directory per upload keeps the original filename without collisions; the server
|
||||
// normalizes the separators and returns the resolved path.
|
||||
const url = new URL("/api/experimental/fs/write", server.conn.http.url)
|
||||
url.searchParams.set("location[directory]", location().directory)
|
||||
url.searchParams.set("path", `${info.paths.tmp}/uploads/${crypto.randomUUID()}/${file.name}`)
|
||||
return write(url, file, server.conn.http.password, report, signal)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// fetch cannot report upload progress and Chromium only streams request bodies over HTTP/2, so
|
||||
// the one request that needs both goes through XMLHttpRequest. The browser streams the File
|
||||
// from disk; nothing is buffered in the renderer.
|
||||
function write(url: URL, file: File, password: string | undefined, report: (loaded: number) => void, signal: AbortSignal) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", url)
|
||||
xhr.responseType = "json"
|
||||
xhr.setRequestHeader("content-type", "application/octet-stream")
|
||||
if (password) xhr.setRequestHeader("authorization", `Basic ${authTokenFromCredentials({ password })}`)
|
||||
xhr.upload.addEventListener("progress", (event) => report(event.loaded))
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status !== 200) return reject(new Error(`Upload failed with status ${xhr.status}`))
|
||||
resolve((xhr.response as { data: { path: string } }).data.path)
|
||||
})
|
||||
xhr.addEventListener("error", () => reject(new Error("Upload failed")))
|
||||
xhr.addEventListener("abort", () => reject(new DOMException("Upload aborted", "AbortError")))
|
||||
signal.addEventListener("abort", () => xhr.abort(), { once: true })
|
||||
xhr.send(file)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { createEffect, createRoot, For, on, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { Toast, toaster } from "@opencode/ui/toast"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
|
||||
export type Upload = {
|
||||
id: string
|
||||
filename: string
|
||||
mime: string
|
||||
size: number
|
||||
loaded: number
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
// Uploads outlive the composer that started them, so one process-wide list feeds every chip
|
||||
// and the single progress toast.
|
||||
const [state, setState] = createStore<{ items: Upload[] }>({ items: [] })
|
||||
|
||||
export const uploads = {
|
||||
items: () => state.items,
|
||||
/** Runs `work` while the upload is listed. Resolves to undefined when the user cancels it. */
|
||||
async track<T>(
|
||||
input: Pick<Upload, "id" | "filename" | "mime" | "size">,
|
||||
work: (report: (loaded: number) => void, signal: AbortSignal) => Promise<T>,
|
||||
): Promise<T | undefined> {
|
||||
const controller = new AbortController()
|
||||
setState("items", (items) => [...items, { ...input, loaded: 0, cancel: () => controller.abort() }])
|
||||
try {
|
||||
return await work((loaded) => setState("items", (item) => item.id === input.id, "loaded", loaded), controller.signal)
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) return undefined
|
||||
throw error
|
||||
} finally {
|
||||
setState("items", (items) => items.filter((item) => item.id !== input.id))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Sonner builds toast content outside the app's Solid tree: no context and no owner. This host
|
||||
// lives inside the providers, lends the toast its language instance, and gives the content a
|
||||
// root of its own so progress stays reactive.
|
||||
export function UploadToastHost() {
|
||||
const language = useLanguage()
|
||||
let active: { id: number; dispose: () => void } | undefined
|
||||
const dismiss = () => {
|
||||
if (!active) return
|
||||
toaster.dismiss(active.id)
|
||||
active.dispose()
|
||||
active = undefined
|
||||
}
|
||||
createEffect(
|
||||
on(
|
||||
() => state.items.length > 0,
|
||||
(uploading) => {
|
||||
if (!uploading) return dismiss()
|
||||
if (active) return
|
||||
const id = toaster.show(
|
||||
(props) =>
|
||||
createRoot((dispose) => {
|
||||
active = { id: props.toastId, dispose }
|
||||
return <UploadToast toastId={props.toastId} language={language} />
|
||||
}),
|
||||
{ persistent: true, resize: () => state.items.length },
|
||||
)
|
||||
active ??= { id, dispose: () => {} }
|
||||
},
|
||||
),
|
||||
)
|
||||
onCleanup(dismiss)
|
||||
return null
|
||||
}
|
||||
|
||||
function UploadToast(props: { toastId: number; language: ReturnType<typeof useLanguage> }) {
|
||||
const percent = (item: Upload) => (item.size === 0 ? 100 : Math.floor((item.loaded / item.size) * 100))
|
||||
return (
|
||||
<Toast toastId={props.toastId}>
|
||||
<Toast.Content>
|
||||
<For each={state.items}>
|
||||
{(item) => (
|
||||
<div data-component="upload-row">
|
||||
<div data-slot="upload-row-label">
|
||||
<span data-slot="upload-row-name" title={item.filename}>
|
||||
{item.filename}
|
||||
</span>
|
||||
<span data-slot="upload-row-percent">
|
||||
{props.language.t("prompt.toast.uploading.percent", { percent: percent(item) })}
|
||||
</span>
|
||||
</div>
|
||||
<div data-slot="upload-row-track">
|
||||
<div
|
||||
data-component="upload-progress"
|
||||
role="progressbar"
|
||||
aria-label={item.filename}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={percent(item)}
|
||||
>
|
||||
<div data-slot="upload-progress-bar" style={{ width: `${percent(item)}%` }} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="upload-row-cancel"
|
||||
aria-label={props.language.t("prompt.toast.uploading.cancel")}
|
||||
onClick={() => item.cancel()}
|
||||
>
|
||||
<Icon name="outline-xmark" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Toast.Content>
|
||||
</Toast>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,13 @@ export type PromptComment = {
|
||||
origin?: "review" | "file"
|
||||
}
|
||||
|
||||
/** An attachment the model receives as a path on the server rather than inline bytes. */
|
||||
export type PromptAttachmentReference = {
|
||||
name: string
|
||||
mime: string
|
||||
path: string
|
||||
}
|
||||
|
||||
function selection(selection: unknown) {
|
||||
if (!selection || typeof selection !== "object") return undefined
|
||||
const startLine = Number((selection as FileSelection).startLine)
|
||||
@@ -58,8 +65,17 @@ export function readPromptPresentation(value: unknown) {
|
||||
const displayText = (value as { displayText?: unknown }).displayText
|
||||
const comments = (value as { comments?: unknown }).comments
|
||||
if (typeof displayText !== "string" || !Array.isArray(comments)) return
|
||||
const attachments = (value as { attachments?: unknown }).attachments
|
||||
return {
|
||||
displayText,
|
||||
attachments: (Array.isArray(attachments) ? attachments : []).flatMap((item): PromptAttachmentReference[] => {
|
||||
if (!item || typeof item !== "object") return []
|
||||
const name = (item as { name?: unknown }).name
|
||||
const mime = (item as { mime?: unknown }).mime
|
||||
const path = (item as { path?: unknown }).path
|
||||
if (typeof name !== "string" || typeof mime !== "string" || typeof path !== "string") return []
|
||||
return [{ name, mime, path }]
|
||||
}),
|
||||
comments: comments.flatMap((item): PromptComment[] => {
|
||||
if (!item || typeof item !== "object") return []
|
||||
const path = (item as { path?: unknown }).path
|
||||
@@ -80,6 +96,10 @@ export function readPromptPresentation(value: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatAttachmentReference(input: PromptAttachmentReference) {
|
||||
return `Attached file: \`${input.path}\``
|
||||
}
|
||||
|
||||
export function formatCommentNote(input: { path: string; selection?: FileSelection; comment: string }) {
|
||||
const start = input.selection ? Math.min(input.selection.startLine, input.selection.endLine) : undefined
|
||||
const end = input.selection ? Math.max(input.selection.startLine, input.selection.endLine) : undefined
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
ComposerPersistedState,
|
||||
ComposerPrompt,
|
||||
} from "../types"
|
||||
import { promptLength } from "../prompt-parts"
|
||||
import { isAttachment, promptLength } from "../prompt-parts"
|
||||
|
||||
export type ComposerStateStore = [
|
||||
Store<ComposerPersistedState> | Accessor<Store<ComposerPersistedState>>,
|
||||
@@ -48,7 +48,7 @@ export function createComposerEditorActions(input: ComposerStateStoreInput) {
|
||||
setStore()((state) => ({
|
||||
prompt: [
|
||||
{ type: "text", content, start: 0, end: content.length },
|
||||
...state.prompt.filter((part) => part.type === "image"),
|
||||
...state.prompt.filter(isAttachment),
|
||||
],
|
||||
cursor: content.length,
|
||||
retry: undefined,
|
||||
@@ -83,7 +83,7 @@ export function createComposerEditorActions(input: ComposerStateStoreInput) {
|
||||
clearRetry()
|
||||
},
|
||||
removeAttachment(id: string) {
|
||||
setStore()("prompt", (parts) => parts.filter((part) => part.type !== "image" || part.id !== id))
|
||||
setStore()("prompt", (parts) => parts.filter((part) => !isAttachment(part) || part.id !== id))
|
||||
clearRetry()
|
||||
},
|
||||
}
|
||||
@@ -93,7 +93,7 @@ function insertText(prompt: ComposerPrompt, cursor: number, content: string): Co
|
||||
let position = 0
|
||||
let inserted = false
|
||||
const parts = prompt.flatMap<ComposerPrompt[number]>((part) => {
|
||||
if (part.type === "image") return [part]
|
||||
if (isAttachment(part)) return [part]
|
||||
const start = position
|
||||
position += part.content.length
|
||||
if (inserted) return [part]
|
||||
@@ -121,7 +121,7 @@ function insertMention(
|
||||
}
|
||||
let position = 0
|
||||
const parts = prompt.flatMap<ComposerPrompt[number]>((part) => {
|
||||
if (part.type === "image") return [part]
|
||||
if (isAttachment(part)) return [part]
|
||||
const partStart = position
|
||||
position += part.content.length
|
||||
if (part.type !== "text" || start < partStart || end > position) return [part]
|
||||
@@ -139,7 +139,7 @@ function insertMention(
|
||||
function withOffsets(prompt: ComposerPrompt): ComposerPrompt {
|
||||
let offset = 0
|
||||
return prompt.map((part) => {
|
||||
if (part.type === "image") return part
|
||||
if (isAttachment(part)) return part
|
||||
const next = { ...part, start: offset, end: offset + part.content.length }
|
||||
offset = next.end
|
||||
return next
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show, type JSX } from "solid-js"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, onCleanup, onMount, Show, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { FileIcon } from "@opencode/ui/file-icon"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { createAnimatedPresence } from "@/runtime/animated-presence"
|
||||
import { resolveBlobUrl } from "@/runtime/persistence/drafts"
|
||||
import { ProviderIcon } from "@opencode/ui/provider-icon"
|
||||
import { useI18n } from "@opencode/ui/context/i18n"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
@@ -12,6 +13,8 @@ import { Menu } from "@opencode/ui/menu"
|
||||
import { Tooltip } from "@opencode/ui/tooltip"
|
||||
import { ScrollView } from "@opencode/ui/scroll-view"
|
||||
import { AttachmentCard } from "@opencode/session-ui/attachment-card"
|
||||
import { ProgressCircle } from "@opencode/ui/progress-circle"
|
||||
import type { Upload } from "../attachments/uploads"
|
||||
import { CommentCard } from "@opencode/session-ui/comment-card"
|
||||
import { typeLabel } from "@opencode/session-ui/message-file"
|
||||
import { Skill } from "@opencode/schema/skill"
|
||||
@@ -24,6 +27,7 @@ import type {
|
||||
ComposerSuggestion,
|
||||
} from "../types"
|
||||
import type { ComposerEditorModel, ComposerSelectControl } from "./interaction"
|
||||
import { isAttachment } from "../prompt-parts"
|
||||
import "../attachments/attachments.css"
|
||||
import "./editor.css"
|
||||
|
||||
@@ -102,7 +106,6 @@ export function ComposerEditor(props: ComposerEditorProps) {
|
||||
ref={props.controller.setFileInput}
|
||||
type="file"
|
||||
multiple
|
||||
accept="image/png,image/jpeg,image/gif,image/webp,application/pdf,text/*,application/json,application/ld+json,application/toml,application/x-toml,application/x-yaml,application/xml,application/yaml,.c,.cc,.cjs,.conf,.cpp,.css,.csv,.cts,.env,.go,.gql,.graphql,.h,.hh,.hpp,.htm,.html,.ini,.java,.js,.json,.jsx,.log,.md,.mdx,.mjs,.mts,.py,.rb,.rs,.sass,.scss,.sh,.sql,.toml,.ts,.tsx,.txt,.xml,.yaml,.yml,.zsh"
|
||||
class="hidden"
|
||||
onChange={(event) => {
|
||||
const list = event.currentTarget.files
|
||||
@@ -149,11 +152,13 @@ export function ComposerEditor(props: ComposerEditorProps) {
|
||||
<Show when={state.mode === "normal"}>
|
||||
<ComposerAttachments
|
||||
attachments={props.controller.attachments()}
|
||||
uploads={props.controller.uploads()}
|
||||
comments={props.controller.comments()}
|
||||
activeCommentID={state.activeContextID}
|
||||
removeLabel={i18n.t("ui.promptInput.removeAttachment")}
|
||||
onAttachmentClick={props.controller.openAttachment}
|
||||
onAttachmentRemove={(attachment) => props.controller.removeAttachment(attachment.id)}
|
||||
onUploadCancel={(upload) => props.controller.cancelUpload(upload.id)}
|
||||
onCommentClick={(comment) => props.controller.toggleContext(comment.key)}
|
||||
onCommentRemove={(comment) => props.controller.removeContext(comment.key)}
|
||||
/>
|
||||
@@ -192,9 +197,9 @@ export function ComposerEditor(props: ComposerEditorProps) {
|
||||
onInput={(event) => {
|
||||
const cursor = composerCursor(event.currentTarget)
|
||||
const prompt = parseComposerEditor(event.currentTarget)
|
||||
const images = props.controller.parts().filter((part) => part.type === "image")
|
||||
const attachments = props.controller.parts().filter(isAttachment)
|
||||
localInput = true
|
||||
props.controller.onInput(prompt.map((part) => part.content).join(""), [...prompt, ...images], cursor)
|
||||
props.controller.onInput(prompt.map((part) => part.content).join(""), [...prompt, ...attachments], cursor)
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (!view.draftOnly && props.controller.onKeyDown(event)) return
|
||||
@@ -349,7 +354,7 @@ function renderComposerEditor(editor: HTMLDivElement, prompt: ComposerPrompt) {
|
||||
const active = document.activeElement === editor
|
||||
editor.replaceChildren(
|
||||
...prompt.flatMap<Node>((part) => {
|
||||
if (part.type === "image") return []
|
||||
if (isAttachment(part)) return []
|
||||
if (part.type === "text") return [document.createTextNode(part.content)]
|
||||
const mention = document.createElement("span")
|
||||
mentionParts.set(mention, part)
|
||||
@@ -476,17 +481,22 @@ function composerCursor(editor: HTMLDivElement) {
|
||||
|
||||
export function ComposerAttachments(props: {
|
||||
attachments: ComposerAttachment[]
|
||||
uploads?: Upload[]
|
||||
comments?: ComposerComment[]
|
||||
activeCommentID?: string
|
||||
removeLabel: string
|
||||
onAttachmentClick?: (attachment: ComposerAttachment) => void
|
||||
onAttachmentRemove: (attachment: ComposerAttachment) => void
|
||||
onUploadCancel?: (upload: Upload) => void
|
||||
onCommentClick?: (comment: ComposerComment) => void
|
||||
onCommentRemove?: (comment: ComposerComment) => void
|
||||
}) {
|
||||
const i18n = useI18n()
|
||||
const percent = (upload: Upload) => (upload.size === 0 ? 100 : Math.floor((upload.loaded / upload.size) * 100))
|
||||
return (
|
||||
<Show when={props.attachments.length > 0 || (props.comments?.length ?? 0) > 0}>
|
||||
<Show
|
||||
when={props.attachments.length > 0 || (props.uploads?.length ?? 0) > 0 || (props.comments?.length ?? 0) > 0}
|
||||
>
|
||||
<div data-component="composer-attachments" data-slot="composer-attachments" class="relative">
|
||||
<div
|
||||
data-slot="composer-attachments-scroll"
|
||||
@@ -523,22 +533,34 @@ export function ComposerAttachments(props: {
|
||||
<For each={props.attachments}>
|
||||
{(attachment) => (
|
||||
<div class="relative group shrink-0">
|
||||
<Tooltip value={attachment.filename} placement="top" contentClass="break-all">
|
||||
<Tooltip
|
||||
value={attachment.type === "path" ? attachment.path : attachment.filename}
|
||||
placement="top"
|
||||
contentClass="break-all"
|
||||
>
|
||||
<Show
|
||||
when={attachment.mime.startsWith("image/")}
|
||||
when={attachment.type === "image" && attachment.mime.startsWith("image/") ? attachment : undefined}
|
||||
fallback={
|
||||
<AttachmentCard title={attachment.filename}>
|
||||
{typeLabel(attachment.filename, attachment.mime, i18n.t("ui.common.file"))}
|
||||
</AttachmentCard>
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={attachment.blob.url}
|
||||
alt={attachment.filename}
|
||||
class="w-[58px] h-[46px] rounded-[6px] object-cover"
|
||||
onClick={() => props.onAttachmentClick?.(attachment)}
|
||||
/>
|
||||
<div class="absolute inset-0 rounded-[6px] shadow-[inset_0_0_0_0.5px_var(--v2-border-border-base)] pointer-events-none" />
|
||||
{(image) => {
|
||||
// Restored drafts and history carry image ids only; bytes load when shown.
|
||||
const [url] = createResource(() => image().blob, resolveBlobUrl)
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
src={url() ?? ""}
|
||||
alt={attachment.filename}
|
||||
class="w-[58px] h-[46px] rounded-[6px] object-cover"
|
||||
onClick={() => props.onAttachmentClick?.(attachment)}
|
||||
/>
|
||||
<div class="absolute inset-0 rounded-[6px] shadow-[inset_0_0_0_0.5px_var(--v2-border-border-base)] pointer-events-none" />
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
</Tooltip>
|
||||
<button
|
||||
@@ -552,6 +574,28 @@ export function ComposerAttachments(props: {
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<For each={props.uploads ?? []}>
|
||||
{(upload) => (
|
||||
<div class="relative group shrink-0" data-slot="composer-upload">
|
||||
<Tooltip value={upload.filename} placement="top" contentClass="break-all">
|
||||
<AttachmentCard title={upload.filename}>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<ProgressCircle percentage={percent(upload)} />
|
||||
{i18n.t("ui.promptInput.uploading", { percent: percent(upload) })}
|
||||
</span>
|
||||
</AttachmentCard>
|
||||
</Tooltip>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onUploadCancel?.(upload)}
|
||||
class="absolute -top-1 -end-1 size-4 rounded-full bg-v2-icon-icon-muted outline-solid outline-1 outline-v2-icon-icon-contrast flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
aria-label={i18n.t("ui.promptInput.cancelUpload")}
|
||||
>
|
||||
<Icon name="outline-xmark" class="text-v2-icon-icon-contrast" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div
|
||||
data-slot="composer-attachments-fade-left"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { shouldHandlePasteAsAttachment } from "./interaction"
|
||||
|
||||
test("leaves paste to the browser when web clipboard data is unavailable", () => {
|
||||
expect(shouldHandlePasteAsAttachment(clipboard(), false)).toBe(false)
|
||||
})
|
||||
|
||||
test("uses native image reading only when the clipboard has no text", () => {
|
||||
expect(shouldHandlePasteAsAttachment(clipboard(), true)).toBe(true)
|
||||
expect(shouldHandlePasteAsAttachment(clipboard(["text/plain"]), true)).toBe(false)
|
||||
})
|
||||
|
||||
test("handles clipboard files as attachments", () => {
|
||||
expect(shouldHandlePasteAsAttachment(clipboard([], [{ kind: "file" }]), false)).toBe(true)
|
||||
})
|
||||
|
||||
function clipboard(types: string[] = [], items: Array<{ kind: string }> = []) {
|
||||
return { types, items } as unknown as DataTransfer
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { createEffect, type Accessor } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { useFilteredList } from "@opencode/ui/hooks"
|
||||
import { createComposerAttachments, type ComposerAttachmentConfig } from "../attachments/attachments"
|
||||
import type { Upload } from "../attachments/uploads"
|
||||
import { createComposerEditorActions, type ComposerStateStoreInput } from "./actions"
|
||||
import type {
|
||||
ComposerAttachment,
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
type ComposerInteractionCommand,
|
||||
type ComposerInteractionEvent,
|
||||
} from "../suggestions/machine"
|
||||
import { clonePrompt, promptLength } from "../prompt-parts"
|
||||
import { clonePrompt, isAttachment, promptLength } from "../prompt-parts"
|
||||
import type { ComposerQueue } from "../adapter"
|
||||
|
||||
export type ComposerSelectControl = {
|
||||
@@ -74,7 +75,7 @@ export function createComposerEditor(input: {
|
||||
const draft = createComposerEditorActions(input.store)
|
||||
const [state, setState] = input.state ?? createComposerEditorState(draft.state.mode)
|
||||
function addPart(part: ComposerPersistedState["prompt"][number]) {
|
||||
if (part.type === "image") return false
|
||||
if (isAttachment(part)) return false
|
||||
if (part.type === "file" || part.type === "agent") {
|
||||
draft.addMention(part)
|
||||
return true
|
||||
@@ -169,7 +170,7 @@ export function createComposerEditor(input: {
|
||||
if (!action || state.popover.type !== "command-menu") result.commands.forEach(execute)
|
||||
if (action && event.item.kind === "command" && state.popover.type !== "command-menu") {
|
||||
draft.setPrompt(
|
||||
draft.state.prompt.filter((part): part is ComposerAttachment => part.type === "image"),
|
||||
draft.state.prompt.filter(isAttachment),
|
||||
0,
|
||||
)
|
||||
}
|
||||
@@ -315,7 +316,13 @@ export function createComposerEditor(input: {
|
||||
return draft.state.context.items.filter((item) => !!item.comment?.trim())
|
||||
},
|
||||
attachments(): ComposerAttachment[] {
|
||||
return draft.state.prompt.filter((part): part is ComposerAttachment => part.type === "image")
|
||||
return draft.state.prompt.filter(isAttachment)
|
||||
},
|
||||
uploads(): Upload[] {
|
||||
return attachments?.pending() ?? []
|
||||
},
|
||||
cancelUpload(id: string) {
|
||||
attachments?.cancel(id)
|
||||
},
|
||||
toggleContext(id: string) {
|
||||
dispatch({ type: "context.active", id })
|
||||
@@ -336,11 +343,12 @@ export function createComposerEditor(input: {
|
||||
canSubmit() {
|
||||
if (input.view.submit.available?.() === false) return false
|
||||
if (input.view.draftOnly) return false
|
||||
if (attachments?.pending().length) return false
|
||||
const persisted = draft.state
|
||||
if (state.mode === "shell") {
|
||||
return persisted.prompt.some((part) => "content" in part && !!part.content.trim())
|
||||
}
|
||||
if (persisted.prompt.some((part) => part.type === "image")) return true
|
||||
if (persisted.prompt.some(isAttachment)) return true
|
||||
if (persisted.context.items.some((item) => !!item.comment?.trim())) return true
|
||||
return persisted.prompt.some((part) => "content" in part && !!part.content.trim())
|
||||
},
|
||||
@@ -369,6 +377,7 @@ export function createComposerEditor(input: {
|
||||
submit(options?: { alternate?: boolean }) {
|
||||
if (input.view.submit.available?.() === false) return
|
||||
if (input.view.draftOnly) return
|
||||
if (attachments?.pending().length) return
|
||||
input.view.submit.onSubmit(options)
|
||||
dispatch({ type: "popover.close" })
|
||||
},
|
||||
@@ -384,20 +393,20 @@ export function createComposerEditor(input: {
|
||||
},
|
||||
onPaste(event: ClipboardEvent) {
|
||||
const clipboard = event.clipboardData
|
||||
if (
|
||||
attachments &&
|
||||
(Array.from(clipboard?.items ?? []).some((item) => item.kind === "file") || !clipboard?.getData("text/plain"))
|
||||
) {
|
||||
const text = clipboard?.getData("text/plain")
|
||||
if (attachments && shouldHandlePasteAsAttachment(clipboard, !!input.attachments?.readClipboardImage)) {
|
||||
void attachments.handlePaste(event)
|
||||
return
|
||||
}
|
||||
const text = clipboard?.getData("text/plain").replace(/\r\n?/g, "\n")
|
||||
if (!text) return
|
||||
event.preventDefault()
|
||||
// insertText emits input events per line, repeatedly parsing and saving the draft.
|
||||
// Escaped HTML inserts multiline text once and preserves native selection and undo.
|
||||
const multiline = text.includes("\n")
|
||||
const value = multiline ? text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">") : text
|
||||
const normalized = text.replace(/\r\n?/g, "\n")
|
||||
const multiline = normalized.includes("\n")
|
||||
const value = multiline
|
||||
? normalized.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
||||
: normalized
|
||||
if (
|
||||
typeof document.execCommand === "function" &&
|
||||
document.execCommand(multiline ? "insertHTML" : "insertText", false, value)
|
||||
@@ -408,13 +417,13 @@ export function createComposerEditor(input: {
|
||||
if (!(target instanceof HTMLElement) || !selection?.rangeCount || !target.contains(selection.anchorNode)) return
|
||||
const range = selection.getRangeAt(0)
|
||||
range.deleteContents()
|
||||
const node = document.createTextNode(text)
|
||||
const node = document.createTextNode(normalized)
|
||||
range.insertNode(node)
|
||||
range.setStartAfter(node)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
target.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertFromPaste", data: text }))
|
||||
target.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertFromPaste", data: normalized }))
|
||||
},
|
||||
onDragEnter(event: DragEvent) {
|
||||
event.preventDefault()
|
||||
@@ -450,6 +459,12 @@ export function createComposerEditor(input: {
|
||||
|
||||
export type ComposerEditorModel = ReturnType<typeof createComposerEditor>
|
||||
|
||||
export function shouldHandlePasteAsAttachment(clipboard: DataTransfer | null, readClipboardImage: boolean) {
|
||||
if (Array.from(clipboard?.items ?? []).some((item) => item.kind === "file")) return true
|
||||
if (Array.from(clipboard?.types ?? []).some((type) => type.startsWith("text/"))) return false
|
||||
return readClipboardImage
|
||||
}
|
||||
|
||||
function canNavigateHistory(direction: "up" | "down", text: string, cursor: number, inHistory: boolean) {
|
||||
const position = Math.max(0, Math.min(cursor, text.length))
|
||||
if (inHistory) return position === 0 || position === text.length
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Prompt } from "@/composer/state"
|
||||
import type { SelectedLineRange } from "@/workspaces/files/model"
|
||||
import { clonePrompt } from "../prompt-parts"
|
||||
import { clonePrompt, isAttachment } from "../prompt-parts"
|
||||
import type { PromptHistoryComment, PromptHistoryEntry } from "../schema"
|
||||
|
||||
export type { PromptHistoryComment, PromptHistoryEntry } from "../schema"
|
||||
@@ -35,9 +35,9 @@ export function prependHistoryEntry(
|
||||
.map((part) => ("content" in part ? part.content : ""))
|
||||
.join("")
|
||||
.trim()
|
||||
const hasImages = prompt.some((part) => part.type === "image")
|
||||
const hasAttachments = prompt.some(isAttachment)
|
||||
const hasComments = comments.some((comment) => !!comment.comment.trim())
|
||||
if (!text && !hasImages && !hasComments) return entries
|
||||
if (!text && !hasAttachments && !hasComments) return entries
|
||||
|
||||
const entry = {
|
||||
prompt: clonePrompt(prompt),
|
||||
@@ -86,7 +86,7 @@ function isPromptEqual(entryA: PromptHistoryStoredEntry, entryB: PromptHistorySt
|
||||
if (partA.type === "skill") {
|
||||
if (partB.type !== "skill" || partA.id !== partB.id || partA.name !== partB.name) return false
|
||||
}
|
||||
if (partA.type === "image" && partA.id !== (partB.type === "image" ? partB.id : "")) return false
|
||||
if (isAttachment(partA) && partA.id !== (isAttachment(partB) ? partB.id : "")) return false
|
||||
}
|
||||
if (entryA.comments.length !== entryB.comments.length) return false
|
||||
for (let i = 0; i < entryA.comments.length; i++) {
|
||||
|
||||
@@ -11,17 +11,19 @@ import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { useLayout } from "@/shell/state/layout"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useWorkspaceLocation } from "@/workspaces/location"
|
||||
import { resolveBlobUrl } from "@/runtime/persistence/drafts"
|
||||
import { useData, useServer } from "@/runtime/server/current"
|
||||
import { createSessionTabs } from "@/session/helpers"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
import { formatServerError } from "@/runtime/server/errors"
|
||||
import { Skill } from "@opencode/schema/skill"
|
||||
import type { ComposerAdapter, ComposerControls, ComposerQueue } from "./adapter"
|
||||
import type { ImageAttachmentPart } from "./state"
|
||||
import { isAttachment } from "./prompt-parts"
|
||||
import type { PromptHistoryComment } from "./history/entry"
|
||||
import { createComposerHistory } from "./history/store"
|
||||
import { composerPlaceholder } from "./placeholder"
|
||||
import { createComposerSubmit } from "./submit"
|
||||
import { useAttachmentDestination } from "./attachments/destination"
|
||||
|
||||
export type ComposerModel = ComposerEditorModel & {
|
||||
readonly model: ComposerControls["model"]
|
||||
@@ -72,7 +74,7 @@ export function createComposerModel(adapter: ComposerAdapter, options?: { queue?
|
||||
}, [])
|
||||
})
|
||||
const attachments = createMemo(() =>
|
||||
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
|
||||
prompt.current().filter(isAttachment),
|
||||
)
|
||||
const commentCount = createMemo(() => {
|
||||
if (mode() === "shell") return 0
|
||||
@@ -319,8 +321,12 @@ export function createComposerModel(adapter: ComposerAdapter, options?: { queue?
|
||||
onContextRemove(item) {
|
||||
if (item?.commentID) comments.remove(item.path, item.commentID)
|
||||
},
|
||||
openAttachment: (attachment) =>
|
||||
dialog.show(() => createComponent(ImagePreview, { src: attachment.blob.url, alt: attachment.filename })),
|
||||
openAttachment: (attachment) => {
|
||||
if (attachment.type !== "image") return
|
||||
void resolveBlobUrl(attachment.blob).then((src) => {
|
||||
if (src) dialog.show(() => createComponent(ImagePreview, { src, alt: attachment.filename }))
|
||||
})
|
||||
},
|
||||
openContext(key) {
|
||||
const item = controller.contextItem(key)
|
||||
if (item) openComment(item, adapter.controls(), layout, files, comments)
|
||||
@@ -338,13 +344,15 @@ export function createComposerModel(adapter: ComposerAdapter, options?: { queue?
|
||||
attachments: {
|
||||
picker: platform.openAttachmentPickerDialog,
|
||||
directory: () => sdk().directory,
|
||||
destination: useAttachmentDestination(adapter.controls),
|
||||
isDialogActive: () => !!dialog.active,
|
||||
warn: () =>
|
||||
showToast({
|
||||
title: language.t("prompt.toast.pasteUnsupported.title"),
|
||||
description: language.t("prompt.toast.pasteUnsupported.description"),
|
||||
}),
|
||||
duplicate: () => showToast({ title: language.t("prompt.toast.attachmentDuplicate.title") }),
|
||||
onUploadError: (error) =>
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("prompt.toast.uploadFailed.title"),
|
||||
description: composerErrorMessage(language, error),
|
||||
}),
|
||||
onError: (error) =>
|
||||
showToast({
|
||||
variant: "error",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { Prompt } from "./state"
|
||||
import type { ContentPart, ImageAttachmentPart, PathAttachmentPart, Prompt } from "./state"
|
||||
|
||||
/** Parts that sit beside the text rather than inside it. */
|
||||
export function isAttachment(part: ContentPart): part is ImageAttachmentPart | PathAttachmentPart {
|
||||
return part.type === "image" || part.type === "path"
|
||||
}
|
||||
|
||||
export function clonePrompt(prompt: Prompt): Prompt {
|
||||
return prompt.map((part) =>
|
||||
@@ -17,7 +22,7 @@ export function appendPrompt(prompt: Prompt, following: Prompt): Prompt {
|
||||
...clonePrompt(prompt),
|
||||
{ type: "text", content: "\n\n", start, end: offset },
|
||||
...clonePrompt(following).map((part) =>
|
||||
part.type === "image" ? part : { ...part, start: part.start + offset, end: part.end + offset },
|
||||
isAttachment(part) ? part : { ...part, start: part.start + offset, end: part.end + offset },
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Skill } from "@opencode/schema/skill"
|
||||
import type { Prompt } from "@/composer/state"
|
||||
import type { ImageAttachmentPart, Prompt } from "@/composer/state"
|
||||
import { buildPromptRequest } from "./request"
|
||||
|
||||
function inline(filename: string, mime: string, extra?: Partial<ImageAttachmentPart>) {
|
||||
return { type: "image" as const, id: `img_${filename}`, filename, mime, dataUrl: `data:${mime};base64,AAA`, ...extra }
|
||||
}
|
||||
|
||||
describe("buildPromptRequest", () => {
|
||||
test("builds text, files, and agents from the prompt", () => {
|
||||
const prompt: Prompt = [
|
||||
@@ -21,9 +25,7 @@ describe("buildPromptRequest", () => {
|
||||
const result = buildPromptRequest({
|
||||
prompt,
|
||||
context: [{ key: "ctx:1", type: "file", path: "src/bar.ts", comment: "check this" }],
|
||||
images: [
|
||||
{ type: "image", id: "img_1", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" },
|
||||
],
|
||||
images: [inline("a.png", "image/png")],
|
||||
text: "hello @src/foo.ts @planner",
|
||||
sessionDirectory: "/repo",
|
||||
})
|
||||
@@ -45,16 +47,7 @@ describe("buildPromptRequest", () => {
|
||||
const result = buildPromptRequest({
|
||||
prompt: [{ type: "text", content: "check these", start: 0, end: 11 }],
|
||||
context: [],
|
||||
images: [
|
||||
{ type: "image", id: "img_1", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" },
|
||||
{
|
||||
type: "image",
|
||||
id: "img_2",
|
||||
filename: "b.pdf",
|
||||
mime: "application/pdf",
|
||||
dataUrl: "data:application/pdf;base64,BBB",
|
||||
},
|
||||
],
|
||||
images: [inline("a.png", "image/png"), inline("b.pdf", "application/pdf")],
|
||||
text: "check these",
|
||||
sessionDirectory: "/repo",
|
||||
})
|
||||
@@ -70,14 +63,9 @@ describe("buildPromptRequest", () => {
|
||||
prompt: [],
|
||||
context: [],
|
||||
images: [
|
||||
{
|
||||
type: "image",
|
||||
id: "img_external",
|
||||
filename: "opencode.global.dat",
|
||||
inline("opencode.global.dat", "text/plain", {
|
||||
sourcePath: "C:\\Users\\Luke\\AppData\\Roaming\\ai.opencode.desktop.beta\\opencode.global.dat",
|
||||
mime: "text/plain",
|
||||
dataUrl: "data:text/plain;base64,AAA",
|
||||
},
|
||||
}),
|
||||
],
|
||||
text: "inspect this",
|
||||
sessionDirectory: "C:\\Repos\\sst\\opencode",
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { getFilename } from "@opencode/util/path"
|
||||
import type { FileSelection } from "@/workspaces/files/model"
|
||||
import { encodeFilePath } from "@/workspaces/files/path"
|
||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt, SkillPart } from "@/composer/state"
|
||||
import { formatCommentNote, type PromptComment } from "@/composer/comment-note"
|
||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, PathAttachmentPart, Prompt, SkillPart } from "@/composer/state"
|
||||
import {
|
||||
formatAttachmentReference,
|
||||
formatCommentNote,
|
||||
type PromptAttachmentReference,
|
||||
type PromptComment,
|
||||
} from "@/composer/comment-note"
|
||||
|
||||
// Network fields feed both boundaries; display fields keep desktop-only rendering details in the local echo.
|
||||
type PromptRequest = {
|
||||
@@ -12,6 +17,7 @@ type PromptRequest = {
|
||||
agents: { name: string; mention?: { start: number; end: number; text: string } }[]
|
||||
skills: { id: string; name: string; mention?: { start: number; end: number; text: string } }[]
|
||||
comments: PromptComment[]
|
||||
attachments: PromptAttachmentReference[]
|
||||
}
|
||||
|
||||
type ContextFile = {
|
||||
@@ -56,6 +62,7 @@ const parseCommentMentions = (comment: string) => {
|
||||
const isFileAttachment = (part: Prompt[number]): part is FileAttachmentPart => part.type === "file"
|
||||
const isAgentAttachment = (part: Prompt[number]): part is AgentPart => part.type === "agent"
|
||||
const isSkillAttachment = (part: Prompt[number]): part is SkillPart => part.type === "skill"
|
||||
const isPathAttachment = (part: Prompt[number]): part is PathAttachmentPart => part.type === "path"
|
||||
|
||||
export function buildPromptRequest(input: BuildPromptRequestInput): PromptRequest {
|
||||
const skills = input.prompt.filter(isSkillAttachment).map((attachment) => ({
|
||||
@@ -106,18 +113,27 @@ export function buildPromptRequest(input: BuildPromptRequestInput): PromptReques
|
||||
return [file, ...mentions]
|
||||
})
|
||||
|
||||
const images = input.images.map((attachment) => ({
|
||||
const inline = input.images.map((attachment) => ({
|
||||
uri: attachment.dataUrl,
|
||||
mime: attachment.mime,
|
||||
name: attachment.sourcePath ?? attachment.filename,
|
||||
}))
|
||||
// Like comments, path references reach the model as text and the message UI through metadata.
|
||||
const attachments = input.prompt
|
||||
.filter(isPathAttachment)
|
||||
.map((part) => ({ name: part.filename, mime: part.mime, path: part.path }))
|
||||
|
||||
return {
|
||||
text: [...(input.text.trim() ? [input.text] : []), ...comments.map(formatCommentNote)].join("\n"),
|
||||
text: [
|
||||
...(input.text.trim() ? [input.text] : []),
|
||||
...attachments.map(formatAttachmentReference),
|
||||
...comments.map(formatCommentNote),
|
||||
].join("\n"),
|
||||
displayText: input.text,
|
||||
files: [...files, ...context, ...images],
|
||||
files: [...files, ...context, ...inline],
|
||||
agents,
|
||||
skills,
|
||||
comments,
|
||||
attachments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("composer persistence schemas", () => {
|
||||
).toEqual(value)
|
||||
})
|
||||
|
||||
test("migrates inline images but never encodes dataUrl or unresolved references", () => {
|
||||
test("migrates inline images, keeps store references without a URL, and never encodes dataUrl", () => {
|
||||
const value = Schema.decodeUnknownSync(
|
||||
Persistence.withInitial(ComposerStore, { prompt: DEFAULT_PROMPT, context: { items: [] } }),
|
||||
)({
|
||||
@@ -149,12 +149,16 @@ describe("composer persistence schemas", () => {
|
||||
{ ...image, blob: { id: "missing" }, dataUrl: "data:image/png;base64,YQ==" },
|
||||
],
|
||||
})
|
||||
expect(value.prompt).toHaveLength(3)
|
||||
expect(value.prompt).toHaveLength(6)
|
||||
expect(value.prompt[0]).toEqual({
|
||||
...image,
|
||||
sourcePath: "/image.png",
|
||||
blob: { id: "data:image/png;base64,YQ==", url: "data:image/png;base64,YQ==" },
|
||||
})
|
||||
// Bytes still in the draft store resolve on use; a non-blob URL is discarded in favour of the id.
|
||||
expect(value.prompt[3]).toEqual({ ...image, blob: { id: "missing", url: "" } })
|
||||
expect(value.prompt[4]).toEqual({ ...image, blob: { id: "bad", url: "" } })
|
||||
expect(value.prompt[5]).toEqual({ ...image, blob: { id: "missing", url: "" } })
|
||||
const encoded = Schema.encodeSync(ComposerStore)(value)
|
||||
expect(JSON.stringify(encoded)).not.toContain("dataUrl")
|
||||
expect(
|
||||
|
||||
@@ -61,11 +61,12 @@ const ImageFields = {
|
||||
}
|
||||
const Image = Persistence.struct({
|
||||
...ImageFields,
|
||||
blob: Schema.Struct({ id: Schema.NonEmptyString, url: Schema.String.check(Schema.isPattern(/^(blob:|data:)/)) }),
|
||||
// An empty URL is an image whose bytes are still in the draft store; see `resolveBlobUrl`.
|
||||
blob: Schema.Struct({ id: Schema.NonEmptyString, url: Schema.String.check(Schema.isPattern(/^(blob:|data:|$)/)) }),
|
||||
})
|
||||
|
||||
// Draft storage hydrates content-addressed blobs before this codec runs. Legacy
|
||||
// inline data remains usable, but unresolved references are not renderable.
|
||||
// Draft storage keeps content-addressed blobs in the store until an image is shown or sent; a
|
||||
// reference without a URL resolves through `resolveBlobUrl`. Legacy inline data remains usable.
|
||||
export const ImageAttachmentPart = Schema.Struct({
|
||||
...ImageFields,
|
||||
blob: Persistence.optional(
|
||||
@@ -94,7 +95,24 @@ export const ImageAttachmentPart = Schema.Struct({
|
||||
)
|
||||
export type ImageAttachmentPart = typeof ImageAttachmentPart.Type
|
||||
|
||||
export const ContentPart = Schema.Union([TextPart, FileAttachmentPart, AgentPart, SkillPart, ImageAttachmentPart])
|
||||
// A file the model receives as a path on the server: its bytes never enter the draft store.
|
||||
export const PathAttachmentPart = Persistence.struct({
|
||||
type: Schema.Literal("path"),
|
||||
id: Schema.String,
|
||||
filename: Schema.String,
|
||||
mime: Schema.String,
|
||||
path: Schema.String,
|
||||
})
|
||||
export type PathAttachmentPart = typeof PathAttachmentPart.Type
|
||||
|
||||
export const ContentPart = Schema.Union([
|
||||
TextPart,
|
||||
FileAttachmentPart,
|
||||
AgentPart,
|
||||
SkillPart,
|
||||
ImageAttachmentPart,
|
||||
PathAttachmentPart,
|
||||
])
|
||||
export type ContentPart = typeof ContentPart.Type
|
||||
export const Prompt = Persistence.array(ContentPart)
|
||||
export type Prompt = typeof Prompt.Type
|
||||
|
||||
@@ -88,6 +88,15 @@ describe("prompt state initialization", () => {
|
||||
start: 5,
|
||||
end: 12,
|
||||
},
|
||||
// A reference without a usable URL keeps its id; the bytes resolve from the draft store on use.
|
||||
{
|
||||
type: "image",
|
||||
id: "missing-blob",
|
||||
filename: "missing.png",
|
||||
mime: "image/png",
|
||||
blob: { id: "content-hash-without-a-url", url: "" },
|
||||
},
|
||||
{ type: "image", id: "invalid-url", filename: "invalid.png", mime: "image/png", blob: { id: "hash", url: "" } },
|
||||
{
|
||||
type: "image",
|
||||
id: "legacy",
|
||||
|
||||
@@ -23,6 +23,7 @@ export type {
|
||||
FileAttachmentPart,
|
||||
FileContextItem,
|
||||
ImageAttachmentPart,
|
||||
PathAttachmentPart,
|
||||
Prompt,
|
||||
PromptModel,
|
||||
SkillPart,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComposerState, ContextItem, Prompt } from "./state"
|
||||
import { appendPrompt, clonePrompt } from "./prompt-parts"
|
||||
import { appendPrompt, clonePrompt, isAttachment } from "./prompt-parts"
|
||||
|
||||
export type ComposerStateTarget = ReturnType<ComposerState["capture"]>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function createComposerSubmission(input: {
|
||||
if (initial !== target) {
|
||||
initial.reset()
|
||||
// A preparing session may already have an unsent follow-up in its promoted composer.
|
||||
if (preserveDraft && target.current().some((part) => part.type === "image" || part.content.length > 0))
|
||||
if (preserveDraft && target.current().some((part) => isAttachment(part) || part.content.length > 0))
|
||||
following = clonePrompt(target.current())
|
||||
}
|
||||
if (!following) target.reset()
|
||||
|
||||
@@ -8,7 +8,8 @@ import type { ComposerAdapter, ComposerDelivery, ComposerSelection, ComposerSess
|
||||
import { createComposerSubmission } from "./submission-state"
|
||||
import { buildPromptRequest } from "./request"
|
||||
import { setCursorPosition } from "./editor/dom"
|
||||
import { blobDataUrl } from "@/runtime/persistence/drafts"
|
||||
import { blobDataUrl, resolveBlobUrl } from "@/runtime/persistence/drafts"
|
||||
import { isAttachment } from "./prompt-parts"
|
||||
import type { ModelSelection } from "@/providers/models/selection"
|
||||
|
||||
const submitting = new WeakSet<object>()
|
||||
@@ -58,12 +59,22 @@ export function createComposerSubmit(input: ComposerSubmitInput) {
|
||||
selection: item.selection ? { ...item.selection } : undefined,
|
||||
})),
|
||||
})
|
||||
const value = readSubmission(input, submission.prompt, submission.context, options?.alternate ?? false)
|
||||
if (!value) {
|
||||
const read = readSubmission(input, submission.prompt, submission.context, options?.alternate ?? false)
|
||||
if (!read) {
|
||||
if (input.adapter.working() && input.adapter.kind === "active-session") void input.adapter.interrupt()
|
||||
return
|
||||
}
|
||||
if (submitting.has(input.adapter.state)) return
|
||||
// Images restored from a draft or history carry ids only; the optimistic message shows their URLs.
|
||||
const value = {
|
||||
...read,
|
||||
images: await Promise.all(
|
||||
read.images.map(async (image) => ({
|
||||
...image,
|
||||
blob: { ...image.blob, url: (await resolveBlobUrl(image.blob)) ?? image.blob.url },
|
||||
})),
|
||||
),
|
||||
}
|
||||
submitting.add(input.adapter.state)
|
||||
const comments = input.comments.capture()
|
||||
// Capture command intent before starting a session in a worktree whose catalog has not loaded.
|
||||
@@ -151,6 +162,9 @@ function handoffMessage(value: ComposerSubmission): SessionMessageUser {
|
||||
})),
|
||||
metadata: {
|
||||
displayText: value.text,
|
||||
attachments: value.prompt.flatMap((part) =>
|
||||
part.type === "path" ? [{ name: part.filename, mime: part.mime, path: part.path }] : [],
|
||||
),
|
||||
comments: value.context.flatMap((item) =>
|
||||
item.comment?.trim()
|
||||
? [
|
||||
@@ -185,7 +199,7 @@ function readSubmission(
|
||||
if (mode === "shell" && !text.trim()) return
|
||||
const images = prompt.filter((part): part is ImageAttachmentPart => part.type === "image")
|
||||
const comments = context.filter((item) => !!item.comment?.trim()).length
|
||||
if (!text.trim() && images.length === 0 && comments === 0) return
|
||||
if (!text.trim() && !prompt.some(isAttachment) && comments === 0) return
|
||||
|
||||
const controls = input.adapter.controls()
|
||||
const model = controls.model.selection.current()
|
||||
@@ -358,6 +372,7 @@ async function sendPrompt(
|
||||
metadata: {
|
||||
displayText: request.displayText,
|
||||
comments: request.comments,
|
||||
attachments: request.attachments,
|
||||
agent: value.selection.agent,
|
||||
model: {
|
||||
...value.selection.model,
|
||||
@@ -372,19 +387,15 @@ async function sendPrompt(
|
||||
|
||||
async function buildSubmissionRequest(session: ComposerSession, value: ComposerSubmission) {
|
||||
const images = await Promise.all(
|
||||
value.images.map(async (attachment) => ({
|
||||
...attachment,
|
||||
dataUrl: await blobDataUrl(attachment.blob, attachment.mime),
|
||||
})),
|
||||
value.images.map(async (attachment) => ({ ...attachment, dataUrl: await blobDataUrl(attachment.blob, attachment.mime) })),
|
||||
)
|
||||
const request = buildPromptRequest({
|
||||
return buildPromptRequest({
|
||||
prompt: value.prompt,
|
||||
context: value.context,
|
||||
images,
|
||||
text: value.text,
|
||||
sessionDirectory: session.directory,
|
||||
})
|
||||
return request
|
||||
}
|
||||
|
||||
function failSubmission(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ComposerHistoryEntry, ComposerPersistedState, ComposerSuggestion } from "../types"
|
||||
import { isAttachment } from "../prompt-parts"
|
||||
|
||||
export type ComposerInteractionState = {
|
||||
mode: "normal" | "shell"
|
||||
@@ -236,7 +237,7 @@ function populated(persisted: ComposerPersistedState) {
|
||||
return (
|
||||
!!promptText(persisted).trim() ||
|
||||
persisted.context.items.length > 0 ||
|
||||
persisted.prompt.some((part) => part.type === "file" || part.type === "image")
|
||||
persisted.prompt.some((part) => part.type === "file" || isAttachment(part))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import type { AgentPart, ComposerStore, FileAttachmentPart, ImageAttachmentPart, Prompt, SkillPart } from "./state"
|
||||
import type {
|
||||
AgentPart,
|
||||
ComposerStore,
|
||||
FileAttachmentPart,
|
||||
ImageAttachmentPart,
|
||||
PathAttachmentPart,
|
||||
Prompt,
|
||||
SkillPart,
|
||||
} from "./state"
|
||||
|
||||
export type ComposerFilePart = FileAttachmentPart
|
||||
export type ComposerAgentPart = AgentPart
|
||||
export type ComposerSkillPart = SkillPart
|
||||
export type ComposerAttachment = ImageAttachmentPart
|
||||
export type ComposerAttachment = ImageAttachmentPart | PathAttachmentPart
|
||||
export type ComposerPrompt = Prompt
|
||||
export type ComposerComment = ComposerStore["context"]["items"][number]
|
||||
export type ComposerPersistedState = ComposerStore
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { AppBaseProviders, AppInterface, preloadRoute } from "./app"
|
||||
export { ACCEPTED_FILE_EXTENSIONS } from "./runtime/platform/file-picker"
|
||||
export { useCommand } from "./shell/commands/command"
|
||||
export { currentRoute, type LayoutRoute, useCurrentRoute } from "./shell/state/layout"
|
||||
export { loadLocaleDict, normalizeLocale, type Locale, useLanguage } from "./runtime/i18n/language"
|
||||
@@ -15,6 +14,7 @@ export type {
|
||||
BrowserPaneTarget,
|
||||
} from "./runtime/platform/browser-pane"
|
||||
export { ServerConnection, useServers } from "./runtime/server/registry"
|
||||
export { useGlobal } from "./runtime/server/runtime"
|
||||
export { useTabs } from "./shell/tabs/tabs"
|
||||
export { createDraftStore } from "./runtime/persistence/drafts"
|
||||
export { createNamespaceStorage, type NamespaceStorage } from "./runtime/persistence/namespace"
|
||||
|
||||
@@ -42,8 +42,14 @@
|
||||
container-name: session-chat;
|
||||
}
|
||||
|
||||
[data-component="new-session"] {
|
||||
container-type: inline-size;
|
||||
container-name: new-session;
|
||||
}
|
||||
|
||||
[data-slot="session-chat-panel"]
|
||||
:is([data-timeline-virtual-content], [data-component="session-composer-dock"] > div) {
|
||||
:is([data-timeline-virtual-content], [data-component="session-composer-dock"] > div),
|
||||
[data-slot="new-session-content"] {
|
||||
translate: var(--session-summary-resize-translate, var(--session-summary-translate, none));
|
||||
transition: translate 240ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
@@ -53,7 +59,8 @@
|
||||
}
|
||||
|
||||
[data-slot="session-chat-panel"][data-summary-resizing="true"]
|
||||
:is([data-timeline-virtual-content], [data-component="session-composer-dock"] > div) {
|
||||
:is([data-timeline-virtual-content], [data-component="session-composer-dock"] > div),
|
||||
[data-component="new-session"][data-summary-resizing="true"] [data-slot="new-session-content"] {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -70,6 +77,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Shift only while centered content overlaps the 280px summary cards and their 16px edge inset:
|
||||
880px + 2 * (280px + 16px) = 1472px. Center it in the remaining space, reserving 320px. */
|
||||
@container new-session (min-width: 1200px) and (width < 1472px) {
|
||||
[data-component="new-session"][data-summary-open="true"] [data-slot="new-session-content"] {
|
||||
--session-summary-translate: -160px;
|
||||
|
||||
&:dir(rtl) {
|
||||
--session-summary-translate: 160px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="session-chat-panel"][data-scrollbar-hidden="true"]
|
||||
[data-slot="session-timeline-scroll"]
|
||||
> .scroll-view__thumb {
|
||||
|
||||
@@ -48,7 +48,8 @@ export function createNewSessionComposerAdapter(props: {
|
||||
submitted: props.submitted,
|
||||
async start(selection, submission, message) {
|
||||
const draftID = props.draftID
|
||||
const projectDirectory = location().directory
|
||||
const currentDirectory = location().directory
|
||||
const projectDirectory = data.location.info({ directory: currentDirectory })?.project.canonical ?? currentDirectory
|
||||
const worktree = props.worktree()
|
||||
const branch = props.branch()
|
||||
const mcp = props.mcp.capture()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { Tooltip } from "@opencode/ui/tooltip"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { Show, Suspense, createMemo, createSignal, lazy } from "solid-js"
|
||||
import { Show, Suspense, createMemo, createSignal, lazy, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { Schema } from "effect"
|
||||
import createPresence from "solid-presence"
|
||||
import { Composer } from "@/composer/composer"
|
||||
@@ -48,7 +50,21 @@ export function NewSessionView(props: {
|
||||
workspace: NewSessionWorkspaceController
|
||||
mcp: DraftMcpControls
|
||||
}) {
|
||||
const [store, setStore] = createStore({ summary: false })
|
||||
const [store, setStore] = createStore({
|
||||
summary: false,
|
||||
content: undefined as HTMLDivElement | undefined,
|
||||
summaryResizeTranslate: undefined as string | undefined,
|
||||
})
|
||||
const finishWindowResize = debounce(() => setStore("summaryResizeTranslate", undefined), 150)
|
||||
onMount(() => {
|
||||
makeEventListener(window, "resize", () => {
|
||||
if (store.summaryResizeTranslate === undefined) {
|
||||
// Freeze the painted offset, including an in-flight slide, until resizing settles.
|
||||
setStore("summaryResizeTranslate", store.content ? getComputedStyle(store.content).translate : "none")
|
||||
}
|
||||
finishWindowResize()
|
||||
})
|
||||
})
|
||||
const [onboarding, setOnboarding, , onboardingReady] = persisted(
|
||||
Persist.global("workspace-onboarding"),
|
||||
WorkspaceOnboardingSchema,
|
||||
@@ -63,6 +79,9 @@ export function NewSessionView(props: {
|
||||
<div class="@container relative flex flex-col min-h-0 h-full flex-1">
|
||||
<div
|
||||
data-component="new-session"
|
||||
data-summary-open={store.summary}
|
||||
data-summary-resizing={store.summaryResizeTranslate !== undefined}
|
||||
style={{ "--session-summary-resize-translate": store.summaryResizeTranslate }}
|
||||
class="relative flex-1 min-h-0 overflow-hidden rounded-[10px] bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]"
|
||||
>
|
||||
<ComposerDropzone
|
||||
@@ -89,7 +108,11 @@ export function NewSessionView(props: {
|
||||
</SummaryPopover>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 top-[25.375%] flex justify-center px-6">
|
||||
<div class={NEW_SESSION_CONTENT_WIDTH}>
|
||||
<div
|
||||
ref={(element) => setStore("content", element)}
|
||||
data-slot="new-session-content"
|
||||
class={NEW_SESSION_CONTENT_WIDTH}
|
||||
>
|
||||
<NewSessionWordmark />
|
||||
<div class="mt-8 flex flex-col gap-8">
|
||||
<Composer model={props.composer} />
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
[data-component="new-session-wordmark"] {
|
||||
--icon-strong-base: var(--icon-base);
|
||||
--wordmark-shimmer-opacity: 0.45;
|
||||
|
||||
.wordmark-shimmer {
|
||||
--icon-base: white;
|
||||
--icon-strong-base: white;
|
||||
--icon-weak-base: transparent;
|
||||
|
||||
/* Blend above the base logo's opacity so the sweep only adds light in either theme. */
|
||||
color: white;
|
||||
mix-blend-mode: screen;
|
||||
@@ -13,6 +18,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-color-scheme="light"] [data-component="new-session-wordmark"] {
|
||||
--icon-weak-base: color-mix(in srgb, var(--icon-base) 30%, transparent);
|
||||
}
|
||||
|
||||
[data-color-scheme="dark"] [data-component="new-session-wordmark"] {
|
||||
/* Preserve the previous dark highlight strength: 0.16 Ã 0.7 Ã 0.6. */
|
||||
--wordmark-shimmer-opacity: 0.0672;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Wordmark } from "@opencode/ui/wordmark"
|
||||
import { Logo } from "@opencode/ui/logo"
|
||||
import "./wordmark.css"
|
||||
|
||||
export function NewSessionWordmark() {
|
||||
@@ -8,9 +8,9 @@ export function NewSessionWordmark() {
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none mx-auto w-full max-w-[720px] text-v2-background-bg-inverse"
|
||||
>
|
||||
<div data-slot="wordmark-reveal" class="relative">
|
||||
<Wordmark fade={false} class="block h-auto w-full opacity-60 [[data-color-scheme=dark]_&]:opacity-50" />
|
||||
<Wordmark fade={false} muted={false} class="wordmark-shimmer absolute inset-0 h-auto w-full" />
|
||||
<div data-slot="wordmark-reveal" class="relative mx-auto w-4/5">
|
||||
<Logo class="block aspect-[720/129] w-full opacity-[0.16]" />
|
||||
<Logo class="wordmark-shimmer absolute inset-0 aspect-[720/129] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ClaudeáŖ GPTáŖ Gemini áĨá áááŊáá á¨áᎠá¨á°áĩá°áĢá¨á áá´ááŊ",
|
||||
"dialog.provider.opencode.tagline": "áŗáá á¨á°ááģáš áá´ááŊ",
|
||||
"dialog.provider.opencodeGo.tagline": "áá
á°á ááĒ áááá á°á",
|
||||
"dialog.provider.anthropic.note": "á¨ááĨáŗ á¨Claude áá´ááŊáŖ áᎠáĨá áááĩá á¨ááŽ",
|
||||
"dialog.provider.copilot.note": "AI áá´ááŊ á GitHub Copilot] á áŠá áŽáĩ áááĩá¨á áĨáá",
|
||||
"dialog.provider.openai.note": "GPT áá´ááŊ áááŖá áĨá áŊááŗ ááá á á ááá AI á°ááŖáĢáĩ",
|
||||
"dialog.provider.google.note": "Gemini áááŖááŖ á¨á°ááአááážáŊ áá´ááŊ",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "á¨70+ á áá á á
áĢáĸááŊá áááá¨áą",
|
||||
"dialog.provider.viewAll": "á°á¨ááĒ á á
áĢáĸááŊá á áŗá",
|
||||
"provider.connect.title": "{{provider}} á ááá",
|
||||
"provider.connect.title.anthropicProMax": "á Claude Pro/Max áááĄ",
|
||||
"provider.connect.selectMethod": "á¨áááĸáĢ áá´á á{{provider}} áá¨áĨáĸ",
|
||||
"provider.connect.method.apiKey": "API ááá",
|
||||
"provider.connect.method.browser": "á áŗáŊ",
|
||||
@@ -371,8 +369,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ááŖáĒá á áĩáááĩ",
|
||||
"prompt.action.send": "áá",
|
||||
"prompt.action.stop": "á áá",
|
||||
"prompt.toast.pasteUnsupported.title": "á¨ááá°áá ááŖáĒ",
|
||||
"prompt.toast.pasteUnsupported.description": "ááĩááŊáŖ áá˛á¤ááŊ ááá á¨áŊáá ááááŊ áĨáģ áĨáá
áá ááŖáአááŊáááĸ",
|
||||
"prompt.toast.attachmentDuplicate.title": "áá
ááá á áĩááĩá á°á°á
áá",
|
||||
"prompt.toast.modelAgentRequired.title": "ááĒá áĨá áá´á ááá¨áĄ",
|
||||
"prompt.toast.modelAgentRequired.description": "ááŽáááĩ á¨áááá á ááĩ ááĒá áĨá áá´á ááá¨áĄáĸ",
|
||||
|
||||
@@ -198,7 +198,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ŲŲ
اذØŦ Ų
ØŽØĒØ§ØąØŠ ØĒØĒØļŲ
Ų Claude Ų GPT Ų Gemini ŲØ§ŲŲ
Ø˛ŲØ¯",
|
||||
"dialog.provider.opencode.tagline": "ŲŲ
اذØŦ Ų
ŲØĢŲŲØŠ ŲŲ
ØØŗŲØŠ",
|
||||
"dialog.provider.opencodeGo.tagline": "اشØĒØąØ§Ų Ų
ŲØŽŲØļ Ø§ŲØĒŲŲŲØŠ ŲŲØŦŲ
ŲØš",
|
||||
"dialog.provider.anthropic.note": "ŲØĩŲŲ Ų
Ø¨Ø§Ø´Øą ØĨŲŲ ŲŲ
اذØŦ ClaudeØ Ø¨Ų
ا ŲŲŲØ§ Pro Ų Max",
|
||||
"dialog.provider.copilot.note": "ŲŲ
اذØŦ Ø°ŲØ§ØĄ اØĩØˇŲØ§ØšŲ ŲŲŲ
ØŗØ§ØšØ¯ØŠ ŲŲ Ø§ŲØ¨ØąŲ
ØŦØŠ ØšØ¨Øą GitHub Copilot",
|
||||
"dialog.provider.openai.note": "ŲŲ
اذØŦ GPT ŲŲ
ŲØ§Ų
Ø§ŲØ°ŲØ§ØĄ Ø§ŲØ§ØĩØˇŲØ§ØšŲ Ø§ŲØšØ§Ų
ØŠ Ø§ŲØŗØąŲؚ؊ ŲØ§ŲŲ
ØĒŲØ¯Ų
ØŠ",
|
||||
"dialog.provider.google.note": "ŲŲ
اذØŦ Gemini ŲØ§ØŗØĒØŦاباØĒ ØŗØąŲØšØŠ ŲŲ
ŲØ¸Ų
ØŠ",
|
||||
@@ -215,7 +214,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ØšØąØļ ØŖŲØĢØą Ų
Ų 70 Ų
ŲŲØąŲا ØĨØļاŲŲŲØ§",
|
||||
"dialog.provider.viewAll": "ØšØąØļ اŲŲ
Ø˛ŲØ¯ Ų
Ų Ø§ŲŲ
ŲŲØąŲŲ",
|
||||
"provider.connect.title": "Ø§ŲØ§ØĒØĩØ§Ų Ø¨Ų {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ØĒØŗØŦŲŲ Ø§ŲØ¯ØŽŲŲ Ø¨Ø§ØŗØĒ؎داŲ
Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "ØØ¯Ø¯ ØˇØąŲŲØŠ ØĒØŗØŦŲŲ Ø§ŲØ¯ØŽŲŲ ŲŲ {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Ų
ŲØĒØ§Ø API",
|
||||
"provider.connect.method.browser": "اŲŲ
ØĒØĩŲØ",
|
||||
@@ -380,9 +378,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ØĨØ˛Ø§ŲØŠ Ø§ŲŲ
ØąŲŲ",
|
||||
"prompt.action.send": "ØĨØąØŗØ§Ų",
|
||||
"prompt.action.stop": "ØĨŲŲØ§Ų",
|
||||
"prompt.toast.pasteUnsupported.title": "Ų
ØąŲŲ ØēŲØą Ų
دؚŲŲ
",
|
||||
"prompt.toast.attachmentDuplicate.title": "ØĒŲ
ØĒØŲ
ŲŲ ŲØ°Ø§ اŲŲ
ŲŲ Ø¨Ø§ŲŲØšŲ",
|
||||
"prompt.toast.pasteUnsupported.description": "ŲŲ
ŲŲ ØĨØąŲØ§Ų Ø§ŲØĩŲØą ØŖŲ Ų
ŲŲØ§ØĒ PDF ØŖŲ Ø§ŲŲ
ŲŲØ§ØĒ اŲŲØĩŲØŠ ŲŲØˇ ŲŲØ§.",
|
||||
"prompt.toast.modelAgentRequired.title": "ØØ¯Ø¯ ŲŲŲŲØ§Ų ŲŲŲ
ŲØ°ØŦاŲ",
|
||||
"prompt.toast.modelAgentRequired.description": "ا؎ØĒØą ŲŲŲŲØ§Ų ŲŲŲ
ŲØ°ØŦØ§Ų ŲØ¨Ų ØĨØąØŗØ§Ų Ø§ŲŲ
ŲØŦŲ.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "ŲØ´Ų ØĨŲØ´Ø§ØĄ Ø´ØŦØąØŠ Ø§ŲØšŲ
Ų",
|
||||
|
||||
@@ -194,7 +194,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini vÉ daha çoxu daxil olmaqla seçilmiÅ modellÉr",
|
||||
"dialog.provider.opencode.tagline": "EtibarlÄą optimallaÅdÄąrÄąlmÄąÅ modellÉr",
|
||||
"dialog.provider.opencodeGo.tagline": "HamÄą ÃŧçÃŧn aÅaÄÄą qiymÉtli abunÉlik",
|
||||
"dialog.provider.anthropic.note": "Pro vÉ Max daxil olmaqla Claude modellÉrinÉ birbaÅa giriÅ",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot vasitÉsilÉ kodlaÅdÄąrma yardÄąmÄą ÃŧçÃŧn AI modellÉri",
|
||||
"dialog.provider.openai.note": "SÃŧrÉtli vÉ bacarÄąqlÄą Ãŧmumi AI tapÅÄąrÄąqlarÄą ÃŧçÃŧn GPT modellÉri",
|
||||
"dialog.provider.google.note": "SÃŧrÉtli, strukturlaÅdÄąrÄąlmÄąÅ cavablar ÃŧçÃŧn Gemini modellÉri",
|
||||
@@ -211,7 +210,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Daha 70+ provayderÉ baxÄąn",
|
||||
"dialog.provider.viewAll": "Daha çox provayder gÃļstÉr",
|
||||
"provider.connect.title": "{{provider}} provayderini qoÅ",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max ilÉ daxil ol",
|
||||
"provider.connect.selectMethod": "{{provider}} ÃŧçÃŧn giriÅ Ãŧsulunu seçin.",
|
||||
"provider.connect.method.apiKey": "API açarÄą",
|
||||
"provider.connect.method.browser": "Brauzer",
|
||||
@@ -378,8 +376,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ÆlavÉni sil",
|
||||
"prompt.action.send": "GÃļndÉr",
|
||||
"prompt.action.stop": "DayandÄąr",
|
||||
"prompt.toast.pasteUnsupported.title": "DÉstÉklÉnmÉyÉn ÉlavÉ",
|
||||
"prompt.toast.pasteUnsupported.description": "Buraya yalnÄąz ÅÉkillÉr, PDF-lÉr vÉ ya mÉtn fayllarÄą ÉlavÉ edilÉ bilÉr.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Bu fayl artÄąq yÃŧklÉnib",
|
||||
"prompt.toast.modelAgentRequired.title": "Agent vÉ model seçin",
|
||||
"prompt.toast.modelAgentRequired.description": "Prompt gÃļndÉrmÉzdÉn ÉvvÉl agent vÉ model seçin.",
|
||||
|
||||
@@ -194,7 +194,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ĐОдйŅаĐŊи ĐŧОдĐĩĐģи, вĐēĐģŅŅиŅĐĩĐģĐŊĐž Claude, GPT, Gemini и Đ´ŅŅĐŗĐ¸",
|
||||
"dialog.provider.opencode.tagline": "ĐадĐĩĐļĐ´ĐŊи ĐžĐŋŅиĐŧиСиŅаĐŊи ĐŧОдĐĩĐģи",
|
||||
"dialog.provider.opencodeGo.tagline": "ĐйОĐŊаĐŧĐĩĐŊŅ ĐŊа ĐŊиŅĐēа ŅĐĩĐŊа Са вŅĐĩĐēи",
|
||||
"dialog.provider.anthropic.note": "ĐиŅĐĩĐēŅĐĩĐŊ Đ´ĐžŅŅŅĐŋ Đ´Đž Claude ĐŧОдĐĩĐģи, вĐēĐģŅŅиŅĐĩĐģĐŊĐž Pro и Max",
|
||||
"dialog.provider.copilot.note": "AI ĐŧОдĐĩĐģи Са ĐŋĐžĐŧĐžŅ ĐŋŅи ĐēОдиŅаĐŊĐĩ ŅŅĐĩС GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT ĐŧОдĐĩĐģи Са ĐąŅŅСи, ŅĐŋĐžŅОйĐŊи ОйŅи СадаŅи Ņ Đ¸ĐˇĐēŅŅŅвĐĩĐŊ иĐŊŅĐĩĐģĐĩĐēŅ",
|
||||
"dialog.provider.google.note": "Gemini ĐŧОдĐĩĐģи Са ĐąŅŅСи, ŅŅŅŅĐēŅŅŅиŅаĐŊи ĐžŅĐŗĐžĐ˛ĐžŅи",
|
||||
@@ -211,7 +210,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ĐиĐļŅĐĩ ĐžŅĐĩ 70+ Đ´ĐžŅŅавŅиŅи",
|
||||
"dialog.provider.viewAll": "ĐĐžĐēаСваĐŊĐĩ ĐŊа ĐžŅĐĩ Đ´ĐžŅŅавŅиŅи",
|
||||
"provider.connect.title": "ХвŅŅĐļĐĩŅĐĩ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ĐĐģĐĩСŅĐĩ Ņ Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "ĐСйĐĩŅĐĩŅĐĩ ĐŧĐĩŅОд Са вĐģиСаĐŊĐĩ Са {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API ĐēĐģŅŅ",
|
||||
"provider.connect.method.browser": "ĐŅаŅСŅŅ",
|
||||
@@ -378,8 +376,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ĐŅĐĩĐŧаŅ
ваĐŊĐĩ ĐŊа ĐŋŅиĐēаŅĐĩĐŊĐ¸Ņ ŅаКĐģ",
|
||||
"prompt.action.send": "ĐСĐŋŅаŅĐĩŅĐĩ",
|
||||
"prompt.action.stop": "ĐĄĐŋŅĐĩŅĐĩ",
|
||||
"prompt.toast.pasteUnsupported.title": "ĐĐĩĐŋОддŅŅĐļаĐŊ ĐŋŅиĐēаŅĐĩĐŊ ŅаКĐģ",
|
||||
"prompt.toast.pasteUnsupported.description": "ĐĸŅĐē ĐŧĐžĐŗĐ°Ņ Đ´Đ° ŅĐĩ ĐŋŅиĐēаŅĐ˛Đ°Ņ ŅаĐŧĐž иСОйŅаĐļĐĩĐŊиŅ, PDF иĐģи ŅĐĩĐēŅŅОви ŅаКĐģОвĐĩ.",
|
||||
"prompt.toast.attachmentDuplicate.title": "ĐĸОСи ŅаКĐģ вĐĩŅĐĩ Đĩ ĐēаŅĐĩĐŊ",
|
||||
"prompt.toast.modelAgentRequired.title": "ĐСйĐĩŅĐĩŅĐĩ Đ°ĐŗĐĩĐŊŅ Đ¸ ĐŧОдĐĩĐģ",
|
||||
"prompt.toast.modelAgentRequired.description": "ĐСйĐĩŅĐĩŅĐĩ Đ°ĐŗĐĩĐŊŅ Đ¸ ĐŧОдĐĩĐģ, ĐŋŅĐĩди да иСĐŋŅаŅиŅĐĩ ĐŋОдĐēаĐŊа.",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini āĻāĻŦāĻ āĻāϰāĻ āĻ
āύā§āĻ āĻāĻŋāĻā§ āϏāĻš āĻāĻŋāĻāϰā§āĻ āĻāϰāĻž āĻŽāĻĄā§āϞ",
|
||||
"dialog.provider.opencode.tagline": "āύāĻŋāϰā§āĻāϰāϝā§āĻā§āϝ āĻ
āĻĒā§āĻāĻŋāĻŽāĻžāĻāĻ āĻāϰāĻž āĻŽāĻĄā§āϞ",
|
||||
"dialog.provider.opencodeGo.tagline": "āϏāĻŦāĻžāϰ āĻāύā§āϝ āĻāĻŽ āĻāϰāĻā§ āϏāĻžāĻŦāϏā§āĻā§āϰāĻŋāĻĒāĻļāύ",
|
||||
"dialog.provider.anthropic.note": "āĻĒā§āϰ⧠āĻāĻŦāĻ āĻŽā§āϝāĻžāĻā§āϏ āϏāĻš Claude āĻŽāĻĄā§āϞāĻā§āϞāĻŋāϤ⧠āϏāϰāĻžāϏāϰāĻŋ āĻ
ā§āϝāĻžāĻā§āϏā§āϏ",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot āĻāϰ āĻŽāĻžāϧā§āϝāĻŽā§ āĻā§āĻĄāĻŋāĻ āϏāĻšāĻžāϝāĻŧāϤāĻžāϰ āĻāύā§āϝ AI āĻŽāĻĄā§āϞ",
|
||||
"dialog.provider.openai.note": "āĻĻā§āϰā§āϤ, āϏāĻā§āώāĻŽ āϏāĻžāϧāĻžāϰāĻŖ AI āĻāĻžāϰā§āϝāĻā§āϞāĻŋāϰ āĻāύā§āϝ GPT āĻŽāĻĄā§āϞ",
|
||||
"dialog.provider.google.note": "āĻĻā§āϰā§āϤ, āĻāĻžāĻ āĻžāĻŽā§āĻāϤ āĻĒā§āϰāϤāĻŋāĻā§āϰāĻŋāϝāĻŧāĻžāϰ āĻāύā§āϝ Gemini āĻŽāĻĄā§āϞ",
|
||||
@@ -209,7 +208,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "āĻāϰāĻ 70+ āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āĻĻā§āĻā§āύ",
|
||||
"dialog.provider.viewAll": "āĻāϰ⧠āĻĒā§āϰāĻĻāĻžāύāĻāĻžāϰ⧠āĻĻā§āĻāĻžāύ",
|
||||
"provider.connect.title": "{{provider}} āϏāĻāϝā§āĻ āĻāϰā§āύ",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max āĻĻāĻŋāϝāĻŧā§ āϞāĻāĻāύ āĻāϰā§āύ",
|
||||
"provider.connect.selectMethod": "{{provider}} āĻāϰ āĻāύā§āϝ āϞāĻāĻāύ āĻĒāĻĻā§āϧāϤāĻŋ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύāĨ¤",
|
||||
"provider.connect.method.apiKey": "API āĻā§",
|
||||
"provider.connect.method.browser": "āĻŦā§āϰāĻžāĻāĻāĻžāϰ",
|
||||
@@ -375,8 +373,6 @@ export const dict: Record<string, string> = {
|
||||
"prompt.attachment.remove": "āϏāĻāϝā§āĻā§āϤāĻŋ āϏāϰāĻžāύ",
|
||||
"prompt.action.send": "āĻĒāĻžāĻ āĻžāύ",
|
||||
"prompt.action.stop": "āĻĨāĻžāĻŽā§",
|
||||
"prompt.toast.pasteUnsupported.title": "āĻ
āϏāĻŽāϰā§āĻĨāĻŋāϤ āϏāĻāϝā§āĻā§āϤāĻŋ",
|
||||
"prompt.toast.pasteUnsupported.description": "āĻāĻāĻžāύ⧠āĻļā§āϧā§āĻŽāĻžāϤā§āϰ āĻāĻŦāĻŋ, āĻĒāĻŋāĻĄāĻŋāĻāĻĢ āĻŦāĻž āĻā§āĻā§āϏāĻ āĻĢāĻžāĻāϞ āϏāĻāϝā§āĻā§āϤ āĻāϰāĻž āϝāĻžāĻŦā§āĨ¤",
|
||||
"prompt.toast.attachmentDuplicate.title": "āĻāĻ āĻĢāĻžāĻāϞ āĻāϤāĻŋāĻŽāϧā§āϝ⧠āĻāĻĒāϞā§āĻĄ āĻāϰāĻž āĻšāϝāĻŧā§āĻā§",
|
||||
"prompt.toast.modelAgentRequired.title": "āĻāĻāĻāĻŋ āĻāĻā§āύā§āĻ āĻāĻŦāĻ āĻŽāĻĄā§āϞ āύāĻŋāϰā§āĻŦāĻžāĻāύ āĻāϰā§āύ",
|
||||
"prompt.toast.modelAgentRequired.description": "āĻĒā§āϰāĻŽā§āĻĒāĻ āĻĒāĻžāĻ āĻžāύā§āϰ āĻāĻā§ āĻāĻāĻāĻŋ āĻāĻā§āύā§āĻ āĻāĻŦāĻ āĻŽāĻĄā§āϞ āĻŦā§āĻā§ āύāĻŋāύāĨ¤",
|
||||
|
||||
@@ -200,7 +200,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Modelos selecionados incluindo Claude, GPT, Gemini e mais",
|
||||
"dialog.provider.opencode.tagline": "Modelos otimizados e confiÃĄveis",
|
||||
"dialog.provider.opencodeGo.tagline": "Assinatura de baixo custo para todos",
|
||||
"dialog.provider.anthropic.note": "Acesso direto aos modelos Claude, incluindo Pro e Max",
|
||||
"dialog.provider.copilot.note": "Modelos de IA para auxÃlio à programaÃ§ÃŖo pelo GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Modelos GPT rÃĄpidos e avançados para tarefas gerais de IA",
|
||||
"dialog.provider.google.note": "Modelos Gemini para respostas rÃĄpidas e estruturadas",
|
||||
@@ -217,7 +216,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Ver mais de 70 provedores",
|
||||
"dialog.provider.viewAll": "Ver mais provedores",
|
||||
"provider.connect.title": "Conectar {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Entrar com Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Selecionar mÊtodo de login para {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Chave de API",
|
||||
"provider.connect.method.browser": "Navegador",
|
||||
@@ -382,9 +380,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Remover anexo",
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Parar",
|
||||
"prompt.toast.pasteUnsupported.title": "Anexo nÃŖo suportado",
|
||||
"prompt.toast.attachmentDuplicate.title": "Este arquivo jÃĄ foi enviado",
|
||||
"prompt.toast.pasteUnsupported.description": "Apenas imagens, PDFs ou arquivos de texto podem ser anexados aqui.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo",
|
||||
"prompt.toast.modelAgentRequired.description": "Escolha um agente e modelo antes de enviar um prompt.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Falha ao criar worktree",
|
||||
|
||||
@@ -208,7 +208,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kurirani modeli ukljuÄujuÄi Claude, GPT, Gemini i druge",
|
||||
"dialog.provider.opencode.tagline": "Pouzdani optimizovani modeli",
|
||||
"dialog.provider.opencodeGo.tagline": "Povoljna pretplata za sve",
|
||||
"dialog.provider.anthropic.note": "Direktan pristup Claude modelima, ukljuÄujuÄi Pro i Max",
|
||||
"dialog.provider.copilot.note": "AI modeli za pomoÄ pri kodiranju putem GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opÅĄte AI zadatke",
|
||||
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
|
||||
@@ -229,7 +228,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "PrikaÅži viÅĄe provajdera",
|
||||
|
||||
"provider.connect.title": "PoveÅži {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Prijavi se putem Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Odaberi naÄin prijave za {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API kljuÄ",
|
||||
"provider.connect.method.browser": "Preglednik",
|
||||
@@ -403,9 +401,7 @@ export const dict = {
|
||||
"prompt.action.send": "PoÅĄalji",
|
||||
"prompt.action.stop": "Zaustavi",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "NepodrÅžan prilog",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ova datoteka je veÄ uÄitana",
|
||||
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu priloÅžiti samo slike, PDF-ovi ili tekstualne datoteke.",
|
||||
"prompt.toast.modelAgentRequired.title": "Odaberi agenta i model",
|
||||
"prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "NeuspjeÅĄno kreiranje worktree-a",
|
||||
|
||||
@@ -193,7 +193,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Models seleccionats que inclouen Claude, GPT, Gemini i mÊs",
|
||||
"dialog.provider.opencode.tagline": "Models optimitzats fiables",
|
||||
"dialog.provider.opencodeGo.tagline": "SubscripciÃŗ de baix cost per a tothom",
|
||||
"dialog.provider.anthropic.note": "AccÊs directe a Claude models, inclosos Pro i Max",
|
||||
"dialog.provider.copilot.note": "Models d'IA per a l'assistència de codificaciÃŗ mitjançant GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT models per a tasques d'IA generals rà pides i capaces",
|
||||
"dialog.provider.google.note": "Gemini models per a respostes rà pides i estructurades",
|
||||
@@ -210,7 +209,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Consulta mÊs de 70 proveïdors mÊs",
|
||||
"dialog.provider.viewAll": "Mostra mÊs proveïdors",
|
||||
"provider.connect.title": "Connecta {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Inicieu sessiÃŗ amb Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Seleccioneu el mètode d'inici de sessiÃŗ per a {{provider}}.",
|
||||
"provider.connect.method.apiKey": "tecla API.",
|
||||
"provider.connect.method.browser": "Navegador",
|
||||
@@ -377,8 +375,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Elimina el fitxer adjunt",
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Atureu-vos",
|
||||
"prompt.toast.pasteUnsupported.title": "Fitxer adjunt no compatible",
|
||||
"prompt.toast.pasteUnsupported.description": "Aquà nomÊs es poden adjuntar imatges, PDFs o fitxers de text.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Aquest fitxer ja s'ha penjat",
|
||||
"prompt.toast.modelAgentRequired.title": "Seleccioneu un agent i un model",
|
||||
"prompt.toast.modelAgentRequired.description": "Trieu un agent i un model abans d'enviar una sol¡licitud.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "VybranÊ modely vÄetnÄ Claude, GPT, Gemini a dalÅĄÃch",
|
||||
"dialog.provider.opencode.tagline": "SpolehlivÊ optimalizovanÊ modely",
|
||||
"dialog.provider.opencodeGo.tagline": "NÃzkonÃĄkladovÊ pÅedplatnÊ pro kaÅždÊho",
|
||||
"dialog.provider.anthropic.note": "PÅÃmÃŊ pÅÃstup k modelům Claude, vÄetnÄ modelů Pro a Max",
|
||||
"dialog.provider.copilot.note": "Modely umÄlÊ inteligence pro pomoc s kÃŗdovÃĄnÃm prostÅednictvÃm GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modely pro rychlÊ a schopnÊ obecnÊ Ãēlohy umÄlÊ inteligence",
|
||||
"dialog.provider.google.note": "Gemini modely pro rychlÊ, strukturovanÊ odpovÄdi",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Zobrazit vÃce neÅž 70 dalÅĄÃch poskytovatelů",
|
||||
"dialog.provider.viewAll": "Zobrazit dalÅĄÃ poskytovatele",
|
||||
"provider.connect.title": "PÅipojit {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "PÅihlaste se pomocà Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Vyberte způsob pÅihlÃĄÅĄenà pro {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API klÃÄ",
|
||||
"provider.connect.method.browser": "ProhlÃÅžeÄ",
|
||||
@@ -375,8 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "OdstraÅte pÅÃlohu",
|
||||
"prompt.action.send": "Odeslat",
|
||||
"prompt.action.stop": "PÅestaÅ",
|
||||
"prompt.toast.pasteUnsupported.title": "NepodporovanÃĄ pÅÃloha",
|
||||
"prompt.toast.pasteUnsupported.description": "Zde lze pÅipojit pouze obrÃĄzky, PDFs nebo textovÊ soubory.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Tento soubor jiÅž byl nahrÃĄn",
|
||||
"prompt.toast.modelAgentRequired.title": "Vyberte agenta a model",
|
||||
"prompt.toast.modelAgentRequired.description": "PÅed odeslÃĄnÃm vÃŊzvy vyberte zÃĄstupce a model.",
|
||||
|
||||
@@ -107,7 +107,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Udvalgte modeller inklusive Claude, GPT, Gemini og flere",
|
||||
"dialog.provider.opencode.tagline": "PÃĨlidelige optimerede modeller",
|
||||
"dialog.provider.opencodeGo.tagline": "Billigt abonnement for alle",
|
||||
"dialog.provider.anthropic.note": "Direkte adgang til Claude-modeller, inklusive Pro og Max",
|
||||
"dialog.provider.copilot.note": "AI-modeller til kodningsassistance via GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT-modeller til hurtige, kompetente generelle AI-opgaver",
|
||||
"dialog.provider.google.note": "Gemini-modeller til hurtige, strukturerede svar",
|
||||
@@ -128,7 +127,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "Vis flere udbydere",
|
||||
|
||||
"provider.connect.title": "Forbind {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Log ind med Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "VÃĻlg loginmetode for {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API-nøgle",
|
||||
"provider.connect.method.browser": "Browser",
|
||||
@@ -300,9 +298,7 @@ export const dict = {
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stop",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Ikke understøttet vedhÃĻftning",
|
||||
"prompt.toast.attachmentDuplicate.title": "Denne fil er allerede uploadet",
|
||||
"prompt.toast.pasteUnsupported.description": "Kun billeder, PDF'er eller tekstfiler kan vedhÃĻftes her.",
|
||||
"prompt.toast.modelAgentRequired.title": "VÃĻlg en agent og model",
|
||||
"prompt.toast.modelAgentRequired.description": "VÃĻlg en agent og model før du sender en forespørgsel.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Kunne ikke oprette worktree",
|
||||
|
||||
@@ -103,7 +103,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kuratierte Modelle inklusive Claude, GPT, Gemini und mehr",
|
||||
"dialog.provider.opencode.tagline": "Zuverlässige, optimierte Modelle",
|
||||
"dialog.provider.opencodeGo.tagline": "KostengÃŧnstiges Abo fÃŧr alle",
|
||||
"dialog.provider.anthropic.note": "Direkter Zugriff auf Claude-Modelle, einschlieÃlich Pro und Max",
|
||||
"dialog.provider.copilot.note": "KI-Modelle zur ProgrammierunterstÃŧtzung Ãŧber GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT-Modelle fÃŧr schnelle, leistungsfähige allgemeine KI-Aufgaben",
|
||||
"dialog.provider.google.note": "Gemini-Modelle fÃŧr schnelle, strukturierte Antworten",
|
||||
@@ -120,7 +119,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Ãber 70 weitere Anbieter anzeigen",
|
||||
"dialog.provider.viewAll": "Mehr Anbieter anzeigen",
|
||||
"provider.connect.title": "{{provider}} verbinden",
|
||||
"provider.connect.title.anthropicProMax": "Mit Claude Pro/Max anmelden",
|
||||
"provider.connect.selectMethod": "Anmeldemethode fÃŧr {{provider}} auswählen.",
|
||||
"provider.connect.method.apiKey": "API-SchlÃŧssel",
|
||||
"provider.connect.method.browser": "Browser",
|
||||
@@ -286,9 +284,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Anhang entfernen",
|
||||
"prompt.action.send": "Senden",
|
||||
"prompt.action.stop": "Stoppen",
|
||||
"prompt.toast.pasteUnsupported.title": "Nicht unterstÃŧtzter Anhang",
|
||||
"prompt.toast.attachmentDuplicate.title": "Diese Datei wurde bereits hochgeladen",
|
||||
"prompt.toast.pasteUnsupported.description": "Hier kÃļnnen nur Bilder, PDFs oder Textdateien angehängt werden.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell",
|
||||
"prompt.toast.modelAgentRequired.description":
|
||||
"Wählen Sie einen Agenten und ein Modell, bevor Sie eine Eingabe senden.",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const dict = {
|
||||
"ŪŪ¨ŪŪĒŪŪŦŪŪ° ŪŪĒŪŪŦŪŪ¨ŪŪĻŪŪ¨ŪŪ§ ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪĒŪŪŦ ŪŪŦŪŪŪŪĻŪŪ¨ ClaudeØ GPTØ Gemini ŪŪĻŪŪ¨ ŪŪŦŪŪŦŪŪ¨ŪŪŦŪŪ° ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪ° ŪŪ¨ŪŪŦŪŪŦŪŪŦŪŪŦ",
|
||||
"dialog.provider.opencode.tagline": "ŪŪ¨ŪŪĒŪŪ§ŪŪĒ ŪŪĒŪŪ¨ ŪŪŽŪŪ°ŪŪ¨ŪŪĻŪŪ¨ŪŪ°ŪŪ° ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪŦŪŪŦ",
|
||||
"dialog.provider.opencodeGo.tagline": "ŪŪŦŪŪ°ŪŪŦŪŪ°ŪŪĻŪŪ° ŪŪĒŪŪĻ ŪŪĻŪŪŦŪŪ°ŪŪĻŪŪ¨ ŪŪĻŪŪ°ŪŪ°ŪŪ°ŪŪ¨ŪŪ°ŪŪĻŪŪ° ŪŪ¨ŪŪĒŪŪŦŪŪŦ",
|
||||
"dialog.provider.anthropic.note": "ŪŪ°ŪŪ¯ ŪŪĻŪŪ¨ ŪŪŦŪŪ°ŪŪ° ŪŪ¨ŪŪŦŪŪ ŪŪŽŪŪĻŪŪ° Claude ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪĻŪŪ° ŪŪŠŪŪ§ ŪŪŦŪŪ°ŪŪŦŪŪ° ŪŪ¨ŪŪŪŪŦ ŪŪŦŪŪŦ",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot ŪŪŦŪŪĒŪŪŦŪŪ¨ŪŪŽŪŪ° ŪŪ¯ŪŪ¨ŪŪ°Ū ŪŪŦŪŪŠŪŪŦŪŪ¨ŪŪĒŪŪĻŪŪ° AI ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪ°",
|
||||
"dialog.provider.openai.note": "ŪŪĻŪŪĻŪŪ°Ø ŪŪ§ŪŪ¨ŪŪ° ŪŪ§ŪŪ°ŪŪĒ AI ŪŪ§ŪŪ°ŪŪ°ŪŪĻŪŪĻŪŪ° GPT ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪŦŪŪŦ",
|
||||
"dialog.provider.google.note": "ŪŪĻŪŪĻŪŪ°Ø ŪŪ°ŪŪ°ŪŪĻŪŪ°ŪŪĻŪŪŪ° ŪŪŦŪŪ°ŪŪŽŪŪ°ŪŪ°ŪŪĻŪŪĻŪŪ° Gemini ŪŪŽŪŪŦŪŪ°ŪŪĻŪŪŦŪŪŦ",
|
||||
@@ -212,7 +211,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ŪŪ¨ŪŪĒŪŪĒ 70+ ŪŪ°ŪŪŽŪŪĻŪŪ¨ŪŪĻŪŪĒŪŪ° ŪŪĻŪŪ°ŪŪĻŪŪ§ŪŪŦŪŪŦ",
|
||||
"dialog.provider.viewAll": "ŪŪ¨ŪŪĒŪŪĒ ŪŪ°ŪŪŽŪŪĻŪŪ¨ŪŪĻŪŪĒŪŪ° ŪŪĻŪŪ°ŪŪ§ŪŪŦŪŪŦ",
|
||||
"provider.connect.title": "{{provider}} ŪŪĒŪ
ŪĒŪŪ§ŪŪŦŪŪŦ",
|
||||
"provider.connect.title.anthropicProMax": "Claude ŪŪ°ŪŪ¯/ŪŪŦŪŪ°ŪŪ° ŪŪ¨ŪŪ° ŪŪŽŪŪ¨ŪŪ° ŪŪ§ŪŪŦŪŪŦŪŪŦ",
|
||||
"provider.connect.selectMethod": "{{provider}} ŪŪĻŪŪ° ŪŪŽŪŪ¨ŪŪ° ŪŪŦŪŪ¯ŪŪ° ŪŪŽŪŪ§ŪŪŦŪŪŦ.",
|
||||
"provider.connect.method.apiKey": "API ŪŪŠ ŪŪŦŪŪŦ",
|
||||
"provider.connect.method.browser": "ŪŪ°ŪŪĻŪŪĒŪŪ§ ŪŪŦŪŪŦ",
|
||||
@@ -380,9 +378,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ŪŪŦŪŪŪŪ°ŪŪĻŪŪ°ŪŪ° ŪŪĻŪŪ§ŪŪŦŪŪŦ",
|
||||
"prompt.action.send": "ŪŪŽŪŪĒŪŪĒŪŪ°",
|
||||
"prompt.action.stop": "ŪŪĒŪŪ°ŪŪĒŪŪ°",
|
||||
"prompt.toast.pasteUnsupported.title": "ŪŪĻŪŪ¯ŪŪ° ŪŪĒŪŪĒŪŪ§ ŪŪŦŪŪŪŪ°ŪŪĻŪŪ°ŪŪŦŪŪŦ",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"ŪŪ¨ŪŪĻŪŪĒŪŪĻŪŪ¨ ŪŪŦŪŪŪŪ° ŪŪĒŪŪŦŪŪŪŪŠ ŪŪĻŪŪĻŪŪŦŪŪĻŪŪ¨ ŪŪĻŪŪ°ŪŪŠŪŪĒØ PDFsØ ŪŪĒŪŪĻŪŪĻ ŪŪŦŪŪ°ŪŪ°ŪŪ° ŪŪĻŪŪ¨ŪŪ°ŪŪĻŪŪŦŪŪŦ.",
|
||||
"prompt.toast.attachmentDuplicate.title": "ŪŪ¨ ŪŪĻŪŪ¨ŪŪ° ŪŪ¨ŪŪ§ŪŪĒ ŪŪĻŪŪŠ ŪŪĻŪŪ°ŪŪ¯ŪŪ°ŪŪŽŪŪ°ŪŪĻŪŪŦŪŪŦ",
|
||||
"prompt.toast.modelAgentRequired.title": "ŪŪŪŪŦŪŪ°ŪŪĻŪŪ§ŪŪ¨ ŪŪŽŪŪŦŪŪ°ŪŪŦŪŪ° ŪŪŽŪŪĒŪŪ°",
|
||||
"prompt.toast.modelAgentRequired.description": "ŪŪ°ŪŪŽŪŪ°ŪŪ°ŪŪŦŪŪ° ŪŪŽŪŪĒŪŪĒŪŪĒŪŪŦ ŪŪĒŪŪ¨ŪŪ° ŪŪŪŪŦŪŪ°ŪŪĻŪŪ§ŪŪ¨ ŪŪŽŪŪŦŪŪ°ŪŪŦŪŪ° ŪŪŽŪŪĒŪŪ°.",
|
||||
|
||||
@@ -194,7 +194,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.provider.opencode.note": "Claude āŊāŊāŧ GPT āŊāŊēāŧāŊŖāŊĻāŧ Gemini āŊāŊāŧāŊāŊēāŧāŊŖāŊĻāŧāŊŖāžˇāŊāŧāŊĻāžāŊēāŧ āŊāŊāŊŧāŊāŧāŊĻāžāž˛āŊ˛āŊāŧāŊ āŊāŊāŧāŊĄāŊŧāŊāŧāŊāŊ āŊ˛āŧāŊāŊāŊēāŧāŊāŊāŧ",
|
||||
"dialog.provider.opencode.tagline": "āŊāžŗāŊŧāŧāŊāŊāŊāŧāŊ
āŊāŧāŊāžąāŊ˛āŧāŊĄāŊĸāŧāŊĸāžāžąāŊĻāŧāŊ
āŊāŧāŊāžąāŊ˛āŧāŊāŊāŊēāŧāŊāŊāŧāŊāŊ´āŧ",
|
||||
"dialog.provider.opencodeGo.tagline": "āŊāŊ˛āŧāŊāŊāŧāŊāŊ āŊ˛āŧāŊāŊŧāŊāŧāŊŖāŊ´āŧ āŊāŊŧāŊāŧāŊāŊāŧāŊāŊāŊ āŧāŊāŊ āŊ˛āŧ āŊāŊāŊŧāŧāŊāŊāŊāŊĻāŧāŊ āŊāŊāŧāŊāŊ˛āŧ",
|
||||
"dialog.provider.anthropic.note": "āŊāž˛āŊŧāŧāŊāŊāŧāŊāŊēāŊāŊĻāŊ˛āŧāŊāŊ´āŧāŊĸāžŠāŊ˛āŊĻāŧāŊāŊēāŧ ClaudeāŊāŊāŊēāŧāŊāŊāŧāŊāŊ´āŧāŊŖāŊ´āŧāŊāŊāŧāŊāŊĸāŧāŊāŊ´āŧāŊ āŊāŊ´āŊŖāŧāŊĻāž¤āžąāŊŧāŊāŧ",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot āŊāŊĸāžāžąāŊ´āŊāŧāŊāŊēāŧ āŊāŊŧāŧāŊāŊ˛āŊāŧāŊāž˛āŊŧāŊāŊĻāŧāŊĸāŊāŧāŊāžąāŊ˛āŧāŊāŊŧāŊāŧāŊŖāŊ´āŧ AI āŊāŊāŊēāŧāŊāŊāŧ",
|
||||
"dialog.provider.openai.note": "GPT āŊāŊāžąāŊŧāŊāŊĻāŧāŊāž˛āŊāŊĻāŧāŊāŊāŧāŊŖāžāŊŧāŊāŊĻāŧāŊāž˛āŊ´āŊāŧāŊ
āŊāŧāŊāžąāŊ˛āŧāŊĻāž¤āžąāŊ˛āŊĸāŧāŊāŊāŊāŧāŊ¨āŊēāŧāŊ¨āŊ āŊ˛āŧāŊŖāŊĻāŧāŊ āŊāŊāŧāŊāŊ´āŧāŊāŊ˛āŧāŊāŊŧāŊāŧāŊŖāŊ´āŧāŊāŊāŊēāŧāŊāŊāŧ",
|
||||
"dialog.provider.google.note": "Gemini āŊāŊāžąāŊŧāŊāŊĻāŧāŊāž˛āŊāŊĻāŧāŊāŊāŧāŊĻāžāž˛āŊ˛āŊāŧāŊāŊāŊŧāŊāŧāŊ
āŊāŧāŊāžąāŊ˛āŧāŊŖāŊāŧāŊ āŊāŊēāŊāŊĻāŧāŊāžąāŊ˛āŧāŊāŊāŊēāŧāŊāŊāŧ",
|
||||
@@ -212,7 +211,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70+ āŊāŊāŊŧāŧāŊĻāž¤āž˛āŊŧāŊāŧāŊ āŊāŊāŧāŊāŊ˛āŧāŊāŊ´āŧāŊāŊŖāžāŧ",
|
||||
"dialog.provider.viewAll": "āŊāžąāŊ˛āŊāŧāŊāŊ˛āŧāŊāŊāŊāŧāŊĻāžāŊŧāŊāŧ",
|
||||
"provider.connect.title": "{{provider}}āŊāŊāŊ´āŊāŧ",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max āŊāŊāŧāŊ
āŊ˛āŊāŧāŊāŊĸāŧāŊāŊāŧāŊāŊĻāžāžąāŊŧāŊāŧ",
|
||||
"provider.connect.selectMethod": "{{provider}}āŊāŊ˛āŧāŊāŊŧāŊāŧāŊŖāŊ´āŧ āŊāŊāŧāŊāŊĻāžāžąāŊŧāŊāŧāŊāŊāŊĻāŧāŊŖāŊāŧāŊĻāŊēāŊŖāŧāŊ āŊāŊ´āŧāŊ āŊāŊāŧ",
|
||||
"provider.connect.method.apiKey": "APIāŊŖāžĄāŊēāŧāŊāŊ˛āŊāŧ",
|
||||
"provider.connect.method.browser": "āŊāŊĸāŊ āŊ´āŧāŊāŊĸāŧ",
|
||||
@@ -379,9 +377,6 @@ export const dict: Record<string, string> = {
|
||||
"prompt.attachment.remove": "āŊāŊāŊāŧāŊĻāžĻāž˛āŊāŊĻāŧāŊĸāžŠāŧāŊāŊĻāžāž˛āŊāŧāŊāŊāŊāŧāŧ",
|
||||
"prompt.action.send": "āŊāŊāŊāŧāŊāŊ˛",
|
||||
"prompt.action.stop": "āŊāŊāŊāŧāŊ",
|
||||
"prompt.toast.pasteUnsupported.title": "āŊĸāžāžąāŊāŧāŊĻāžāžąāŊŧāŊĸāŧāŊāŊēāŊāŧāŊāŊ āŊ˛āŧāŊāŊāŊāŧāŊĻāžĻāž˛āŊāŊĻāŧ",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"āŊāŊĸāŧāŊĸāŊ˛āŊĻāŧāŊāŊāŧāŊāŊ˛āŧāŊāŊ˛āŧāŊ¨āŊēāŊāŧ āŊĄāŊāŧāŊāŧ āŊāŊ˛āŊāŧāŊĄāŊ˛āŊāŧāŊĄāŊ˛āŊāŧāŊĻāžŖāŊŧāŊāŧāŊāŊ´āŧāŊĸāžāžąāŊāŊāŧāŊ
āŊ˛āŊāŧ āŊāŊąāŧāŊŖāŊ´āŧāŊāŊāŊāŧāŊĻāžĻāž˛āŊāŊĻāŧāŊ āŊāŊāŧāŊāŊāŊ´āŊāŧ",
|
||||
"prompt.toast.attachmentDuplicate.title": "āŊĄāŊ˛āŊāŧāŊĻāžŖāŊŧāŊāŧāŊ āŊāŊ˛āŧāŊ§āŊēāŧāŊāŧāŊŖāŊĻāŧāŊĻāžāžąāŊēāŊŖāŧāŊāŊāŊ´āŊāŊĻāŧāŊ āŊāŊāŧāŊĄāŊ˛āŧ",
|
||||
"prompt.toast.modelAgentRequired.title": "āŊŖāŊĻāŧāŊāŊāŧāŊāŊāŧāŊāŊāŊēāŧāŊāŊāŧāŊ
āŊ˛āŊāŧāŊĻāŊēāŊŖāŧāŊ āŊāŊ´āŧāŊ āŊāŊāŧ",
|
||||
"prompt.toast.modelAgentRequired.description": "āŊāŊĸāžĄāŧāŊĻāžāŊŧāŊāŧāŊāŧāŊāŊāŊāŧāŊāŊ āŊ˛āŧāŊ§āŊēāŧāŊāŧ āŊŖāŊĻāŧāŊāŊāŧāŊāŊāŧāŊāŊāŊēāŧāŊāŊāŧāŊāŊāŊāŧāŊāŧāŊĸāžāžąāŊāŊĻāŧ",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ÎĪΚÎŧÎĩÎģΡÎŧÎÎŊÎą ÎŧÎŋÎŊĪÎÎģÎą ĪĪ
ÎŧĪÎĩĪΚÎģÎąÎŧβιÎŊÎŋÎŧÎÎŊĪÎŊ ĪĪÎŊ Claude, GPT, Gemini ÎēιΚ ÎŦÎģÎģĪÎŊ",
|
||||
"dialog.provider.opencode.tagline": "ÎΞΚĪĪΚĪĪÎą βÎĩÎģĪΚĪĪÎŋĪÎŋΚΡÎŧÎÎŊÎą ÎŧÎŋÎŊĪÎÎģÎą",
|
||||
"dialog.provider.opencodeGo.tagline": "ÎŖĪ
ÎŊδĪÎŋÎŧÎŽ ĪÎąÎŧΡÎģÎŋĪ ÎēĪĪĪÎŋĪ
Ī ÎŗÎšÎą ĪÎģÎŋĪ
Ī",
|
||||
"dialog.provider.anthropic.note": "ÎÎŧÎĩĪΡ ĪĪĪĪβιĪΡ ĪÎĩ Claude ÎŧÎŋÎŊĪÎÎģÎą, ĪĪ
ÎŧĪÎĩĪΚÎģÎąÎŧβιÎŊÎŋÎŧÎÎŊĪÎŊ ĪĪÎŊ Pro ÎēιΚ Max",
|
||||
"dialog.provider.copilot.note": "ÎÎŋÎŊĪÎÎģÎą AI ÎŗÎšÎą βÎŋΎθÎĩΚι ÎēĪδΚÎēÎŋĪÎŋÎ¯ÎˇĪÎˇĪ ÎŧÎĪĪ GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT ÎŧÎŋÎŊĪÎÎģÎą ÎŗÎšÎą ÎŗĪÎŽÎŗÎŋĪÎĩĪ, ΚÎēÎąÎŊÎĪ ÎĩĪÎŗÎąĪίÎĩĪ ÎŗÎĩÎŊΚÎēÎŽĪ ĪÎĩĪÎŊΡĪÎŽĪ ÎŊÎŋΡÎŧÎŋĪĪÎŊΡĪ",
|
||||
"dialog.provider.google.note": "Gemini ÎŧÎŋÎŊĪÎÎģÎą ÎŗÎšÎą ÎŗĪÎŽÎŗÎŋĪÎĩĪ, δÎŋÎŧΡÎŧÎÎŊÎĩĪ ÎąĪÎąÎŊĪÎŽĪÎĩΚĪ",
|
||||
@@ -209,7 +208,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ÎÎĩίĪÎĩ 70+ ÎąÎēĪÎŧΡ ĪÎąĪĪĪÎŋĪ
Ī",
|
||||
"dialog.provider.viewAll": "ÎÎŧĪÎŦÎŊΚĪΡ ĪÎĩĪΚĪĪĪĪÎĩĪĪÎŊ ĪÎąĪĪĪĪÎŊ",
|
||||
"provider.connect.title": "ÎŖĪÎŊδÎĩĪΡ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ÎŖĪÎŊδÎĩĪΡ ÎŧÎĩ Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "ÎĪΚÎģÎŋÎŗÎŽ ÎŧÎĩθĪδÎŋĪ
ĪĪÎŊδÎĩĪÎˇĪ ÎŗÎšÎą {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API ÎēÎģÎĩΚδί",
|
||||
"provider.connect.method.browser": "Î ĪĪÎŗĪÎąÎŧÎŧÎą ĪÎĩĪÎšÎŽÎŗÎˇĪΡĪ",
|
||||
@@ -376,8 +374,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ÎÎąĪÎŦĪÎŗÎˇĪΡ ĪĪ
ÎŊΡÎŧÎŧÎÎŊÎŋĪ
",
|
||||
"prompt.action.send": "ÎĪÎŋĪĪÎŋÎģÎŽ",
|
||||
"prompt.action.stop": "ÎΚιÎēÎŋĪÎŽ",
|
||||
"prompt.toast.pasteUnsupported.title": "ÎΡ Ī
ĪÎŋĪĪΡĪΚÎļĪÎŧÎĩÎŊÎŋ ĪĪ
ÎŊΡÎŧÎŧÎÎŊÎŋ",
|
||||
"prompt.toast.pasteUnsupported.description": "ÎÎ´Ī ÎĩĪΚĪĪ
ÎŊÎŦĪĪÎŋÎŊĪιΚ ÎŧĪÎŊÎŋ ÎĩΚÎēĪÎŊÎĩĪ, ÎąĪĪÎĩÎ¯Îą PDF ÎŽ ÎąĪĪÎĩÎ¯Îą ÎēÎĩΚÎŧÎÎŊÎŋĪ
.",
|
||||
"prompt.toast.attachmentDuplicate.title": "ÎĪ
ĪĪ ĪÎŋ ÎąĪĪÎĩίÎŋ ÎĪÎĩΚ ΎδΡ ÎŧÎĩĪÎąĪÎŋĪĪĪθÎĩί",
|
||||
"prompt.toast.modelAgentRequired.title": "ÎĪΚÎģÎΞĪÎĩ ÎÎŊÎąÎŊ ĪĪÎŦÎēĪÎŋĪÎą ÎēιΚ ÎŧÎŋÎŊĪÎÎģÎŋ",
|
||||
"prompt.toast.modelAgentRequired.description":
|
||||
|
||||
@@ -74,6 +74,7 @@ export const dict = {
|
||||
"command.category.workspace": "Worktree",
|
||||
"command.category.settings": "Settings",
|
||||
"command.logs.export": "Export logs",
|
||||
"command.debugBar.toggle": "Toggle debug bar",
|
||||
|
||||
"theme.scheme.system": "System",
|
||||
"theme.scheme.light": "Light",
|
||||
@@ -170,7 +171,7 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Curated models including Claude, GPT, Gemini and more",
|
||||
"dialog.provider.opencode.tagline": "Reliable optimized models",
|
||||
"dialog.provider.opencodeGo.tagline": "Low cost subscription for everyone",
|
||||
"dialog.provider.anthropic.note": "Direct access to Claude models, including Pro and Max",
|
||||
"dialog.provider.anthropic.note": "Direct access to Claude models via API key",
|
||||
"dialog.provider.copilot.note": "AI models for coding assistance via GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT models for fast, capable general AI tasks",
|
||||
"dialog.provider.google.note": "Gemini models for fast, structured responses",
|
||||
@@ -191,7 +192,7 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "Show more providers",
|
||||
|
||||
"provider.connect.title": "Connect {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Login with Claude Pro/Max",
|
||||
"provider.connect.title.anthropicProMax": "Login with Anthropic",
|
||||
"provider.connect.selectMethod": "Select login method for {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API key",
|
||||
"provider.connect.method.browser": "Browser",
|
||||
@@ -365,9 +366,10 @@ export const dict = {
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stop",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Unsupported attachment",
|
||||
"prompt.toast.pasteUnsupported.description": "Only images, PDFs, or text files can be attached here.",
|
||||
"prompt.toast.attachmentDuplicate.title": "This file has already been uploaded",
|
||||
"prompt.toast.uploading.percent": "{{percent}}%",
|
||||
"prompt.toast.uploading.cancel": "Cancel upload",
|
||||
"prompt.toast.uploadFailed.title": "Upload failed",
|
||||
"prompt.toast.modelAgentRequired.title": "Select an agent and model",
|
||||
"prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Failed to create worktree",
|
||||
@@ -965,7 +967,16 @@ export const dict = {
|
||||
"sidebar.empty.description": "Open a project to get started",
|
||||
|
||||
"debugBar.ariaLabel": "Development performance diagnostics",
|
||||
"debugBar.providerAriaLabel": "Provider performance diagnostics",
|
||||
"debugBar.na": "n/a",
|
||||
"debugBar.ttft.label": "TTFT",
|
||||
"debugBar.ttft.tip": "Time from provider request dispatch to the first model output.",
|
||||
"debugBar.ttfa.label": "TTFA",
|
||||
"debugBar.ttfa.tip": "Time from provider request dispatch to the first answer text.",
|
||||
"debugBar.tps.label": "TPS",
|
||||
"debugBar.tps.tip": "Output tokens per second after the first model output.",
|
||||
"debugBar.e2e.label": "E2E",
|
||||
"debugBar.e2e.tip": "Time from provider request dispatch until its response stream ended.",
|
||||
"debugBar.nav.label": "NAV",
|
||||
"debugBar.nav.tip":
|
||||
"Last completed route transition touching a session page, measured from router start until the first paint after it settles.",
|
||||
|
||||
@@ -208,7 +208,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "SelecciÃŗn de modelos como Claude, GPT, Gemini y otros",
|
||||
"dialog.provider.opencode.tagline": "Modelos optimizados y fiables",
|
||||
"dialog.provider.opencodeGo.tagline": "SuscripciÃŗn econÃŗmica para todos",
|
||||
"dialog.provider.anthropic.note": "Acceso directo a modelos Claude, incluidos Pro y Max",
|
||||
"dialog.provider.copilot.note": "Modelos de IA para asistencia de codificaciÃŗn a travÊs de GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Modelos GPT para tareas de IA generales rÃĄpidas y capaces",
|
||||
"dialog.provider.google.note": "Modelos Gemini para respuestas rÃĄpidas y estructuradas",
|
||||
@@ -229,7 +228,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "Ver mÃĄs proveedores",
|
||||
|
||||
"provider.connect.title": "Conectar {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Iniciar sesiÃŗn con Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Seleccionar mÊtodo de inicio de sesiÃŗn para {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Clave API",
|
||||
"provider.connect.method.browser": "Navegador",
|
||||
@@ -403,10 +401,7 @@ export const dict = {
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Detener",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Adjunto no compatible",
|
||||
"prompt.toast.attachmentDuplicate.title": "Este archivo ya se ha subido",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"Aquà solo se pueden adjuntar imÃĄgenes, archivos PDF o archivos de texto.",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo",
|
||||
"prompt.toast.modelAgentRequired.description": "Elige un agente y modelo antes de enviar un prompt.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Fallo al crear el ÃĄrbol de trabajo",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kureeritud mudelid, sealhulgas Claude, GPT, Gemini ja palju muud",
|
||||
"dialog.provider.opencode.tagline": "Usaldusväärsed optimeeritud mudelid",
|
||||
"dialog.provider.opencodeGo.tagline": "Madala hinnaga tellimus kÃĩigile",
|
||||
"dialog.provider.anthropic.note": "Otsene juurdepääs Claude mudelile, sealhulgas Pro ja Max",
|
||||
"dialog.provider.copilot.note": "AI mudelid kodeerimisabiks GitHub Copilot kaudu",
|
||||
"dialog.provider.openai.note": "GPT mudelid kiirete ja vÃĩimekate Ãŧldiste AI-Ãŧlesannete jaoks",
|
||||
"dialog.provider.google.note": "Gemini mudelid kiirete ja struktureeritud vastuste jaoks",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Vaadake veel 70+ pakkujat",
|
||||
"dialog.provider.viewAll": "Kuva rohkem teenusepakkujaid",
|
||||
"provider.connect.title": "Ãhenda {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Logi sisse Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Valige {{provider}} jaoks sisselogimisviis.",
|
||||
"provider.connect.method.apiKey": "API klahv",
|
||||
"provider.connect.method.browser": "Brauser",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Eemalda manus",
|
||||
"prompt.action.send": "Saada",
|
||||
"prompt.action.stop": "Peatus",
|
||||
"prompt.toast.pasteUnsupported.title": "Toetamata manus",
|
||||
"prompt.toast.pasteUnsupported.description": "Siia saab lisada ainult pilte, PDFs vÃĩi tekstifaile.",
|
||||
"prompt.toast.attachmentDuplicate.title": "See fail on juba Ãŧles laaditud",
|
||||
"prompt.toast.modelAgentRequired.title": "Valige agent ja mudel",
|
||||
"prompt.toast.modelAgentRequired.description": "Enne viipa saatmist valige agent ja mudel.",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Ų
Ø¯Ų ŲØ§Û Ø§ŲØĒØŽØ§Ø¨ Ø´Ø¯Ų Ø§Ø˛ ØŦŲ
ŲŲ ClaudeØ GPTØ Gemini Ų Ų
ŲØ§ØąØ¯ دÛÚ¯Øą",
|
||||
"dialog.provider.opencode.tagline": "Ų
Ø¯Ų ŲØ§Û بŲÛŲŲ Ø´Ø¯Ų ŲØ§Ø¨Ų اؚØĒŲ
اد",
|
||||
"dialog.provider.opencodeGo.tagline": "اشØĒØąØ§ÚŠ ÚŠŲ
ŲØ˛ÛŲŲ Ø¨ØąØ§Û ŲŲ
Ų",
|
||||
"dialog.provider.anthropic.note": "Ø¯ØŗØĒØąØŗÛ Ų
ØŗØĒŲÛŲ
Ø¨Ų Ų
Ø¯Ų ŲØ§Û ClaudeØ Ø§Ø˛ ØŦŲ
ŲŲ Pro Ų Max",
|
||||
"dialog.provider.copilot.note": "Ų
Ø¯Ų ŲØ§Û AI Ø¨ØąØ§Û ÚŠŲ
ÚŠ Ø¨Ų ÚŠØ¯ŲŲÛØŗÛ Ø§Ø˛ ØˇØąÛŲ GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Ų
Ø¯Ų ŲØ§Û GPT Ø¨ØąØ§Û ÚŠØ§ØąŲØ§Û ØšŲ
ŲŲ
Û ØŗØąÛØš Ų Ø¨Ø§ ŲØ§Ø¨ŲÛØĒ AI",
|
||||
"dialog.provider.google.note": "Ų
Ø¯Ų ŲØ§Û Gemini Ø¨ØąØ§Û ŲžØ§ØŗØŽ ŲØ§Û ØŗØąÛØš Ų ØŗØ§ØŽØĒØ§ØąÛØ§ŲØĒŲ",
|
||||
@@ -209,7 +208,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70+ Ø§ØąØ§ØĻŲâØ¯ŲŲØ¯Ų دÛÚ¯Øą ØąØ§ ببÛŲÛØ¯",
|
||||
"dialog.provider.viewAll": "ŲŲ
Ø§ÛØ´ Ø§ØąØ§ØĻŲ Ø¯ŲŲØ¯Ú¯Ø§Ų Ø¨ÛØ´ØĒØą",
|
||||
"provider.connect.title": "{{provider}} ØąØ§ ŲØĩŲ ÚŠŲÛØ¯",
|
||||
"provider.connect.title.anthropicProMax": "با Claude Pro/Max ŲØ§ØąØ¯ Ø´ŲÛØ¯",
|
||||
"provider.connect.selectMethod": "ØąŲØ´ ŲØąŲد Ø¨ØąØ§Û {{provider}} ØąØ§ Ø§ŲØĒØŽØ§Ø¨ ÚŠŲÛØ¯.",
|
||||
"provider.connect.method.apiKey": "ÚŠŲÛØ¯ API",
|
||||
"provider.connect.method.browser": "Ų
ØąŲØąÚ¯Øą",
|
||||
@@ -375,8 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ØØ°Ų ŲžÛŲØŗØĒ",
|
||||
"prompt.action.send": "Ø§ØąØŗØ§Ų ÚŠŲÛØ¯",
|
||||
"prompt.action.stop": "ØĒŲŲŲ ÚŠŲÛØ¯",
|
||||
"prompt.toast.pasteUnsupported.title": "ŲžÛŲØŗØĒ ŲžØ´ØĒÛØ¨Ø§ŲÛ ŲØ´Ø¯Ų Ø§ØŗØĒ",
|
||||
"prompt.toast.pasteUnsupported.description": "ŲŲØˇ ØĒØĩاŲÛØąØ PDFsØ ÛØ§ ŲØ§ÛŲ ŲØ§Û Ų
ØĒŲÛ ØąØ§ Ų
Û ØĒŲØ§Ų Ø¯Øą اÛŲØŦا ŲžÛŲØŗØĒ ÚŠØąØ¯.",
|
||||
"prompt.toast.attachmentDuplicate.title": "اÛŲ ŲØ§ÛŲ ŲØ¨Ųا ØĸŲžŲŲØ¯ Ø´Ø¯Ų Ø§ØŗØĒ",
|
||||
"prompt.toast.modelAgentRequired.title": "ÛÚŠ ؚاŲ
Ų Ų Ų
Ø¯Ų ØąØ§ Ø§ŲØĒØŽØ§Ø¨ ÚŠŲÛØ¯",
|
||||
"prompt.toast.modelAgentRequired.description": "ŲØ¨Ų Ø§Ø˛ Ø§ØąØŗØ§Ų Ø¯ØąØŽŲØ§ØŗØĒØ ÛÚŠ ؚاŲ
Ų Ų Ų
Ø¯Ų ØąØ§ Ø§ŲØĒØŽØ§Ø¨ ÚŠŲÛØ¯.",
|
||||
|
||||
@@ -99,7 +99,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kuratoituja malleja, kuten Claude, GPT, Gemini ja muita",
|
||||
"dialog.provider.opencode.tagline": "Luotettavat optimoidut mallit",
|
||||
"dialog.provider.opencodeGo.tagline": "Edullinen tilaus kaikille",
|
||||
"dialog.provider.anthropic.note": "Suora pääsy Claude-malleihin, mukaan lukien Pro ja Max",
|
||||
"dialog.provider.copilot.note": "AI-malleja koodausapuun GitHub Copilotin kautta",
|
||||
"dialog.provider.openai.note": "GPT-mallit nopeisiin, suorituskykyisiin yleisiin tekoälytehtäviin",
|
||||
"dialog.provider.google.note": "Gemini-mallit nopeisiin, jäsenneltyihin vastauksiin",
|
||||
@@ -116,7 +115,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Katso yli 70 muuta palveluntarjoajaa",
|
||||
"dialog.provider.viewAll": "Näytä lisää palveluntarjoajia",
|
||||
"provider.connect.title": "Yhdistä {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Kirjaudu sisään Claude Pro/Maxilla",
|
||||
"provider.connect.selectMethod": "Valitse kirjautumistapa palvelulle {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API-avain",
|
||||
"provider.connect.method.browser": "Selain",
|
||||
@@ -282,8 +280,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Poista liite",
|
||||
"prompt.action.send": "Lähetä",
|
||||
"prompt.action.stop": "Pysäytä",
|
||||
"prompt.toast.pasteUnsupported.title": "Liitettä ei tueta",
|
||||
"prompt.toast.pasteUnsupported.description": "Vain kuvia, PDF-tiedostoja tai tekstitiedostoja voi liittää tähän.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Tämä tiedosto on jo ladattu",
|
||||
"prompt.toast.modelAgentRequired.title": "Valitse agentti ja malli",
|
||||
"prompt.toast.modelAgentRequired.description": "Valitse agentti ja malli ennen kehotteen lähettämistä.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kuraterað modell Ãroknað Claude, GPT, Gemini og fleiri",
|
||||
"dialog.provider.opencode.tagline": "ÃlÃtandi optimerað modell",
|
||||
"dialog.provider.opencodeGo.tagline": "LÃĄgkostnaðarhald fyri øll",
|
||||
"dialog.provider.anthropic.note": "Beinleiðis atgongd til Claude modellir, eisini Pro og Max .",
|
||||
"dialog.provider.copilot.note": "AI modellir til koduhjÃĄlp umvegis GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modellir til skjÃŗtar, førar almennar AI uppgÃĄvur",
|
||||
"dialog.provider.google.note": "Gemini modellir til skjÃŗt, strukturerað svar",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "SÃ 70+ fleiri veitarar",
|
||||
"dialog.provider.viewAll": "VÃs fleiri veitarar",
|
||||
"provider.connect.title": "Samband {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Rita inn við Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Vel innritanarhÃĄtt til {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API lykil",
|
||||
"provider.connect.method.browser": "Kagari",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Strika viðheftið",
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Steðga",
|
||||
"prompt.toast.pasteUnsupported.title": "Ãstuðlað viðhefti",
|
||||
"prompt.toast.pasteUnsupported.description": "Bert myndir, PDFs, ella tekstfÃlur kunnu viðheftast her.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Hendan fÃlan er longu løgd upp.",
|
||||
"prompt.toast.modelAgentRequired.title": "Vel agent og modell",
|
||||
"prompt.toast.modelAgentRequired.description": "Vel agent og modell, ÃĄÃ°renn tÃē sendir ein prompt.",
|
||||
|
||||
@@ -200,7 +200,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Modèles sÊlectionnÊs incluant Claude, GPT, Gemini et plus",
|
||||
"dialog.provider.opencode.tagline": "Modèles optimisÊs et fiables",
|
||||
"dialog.provider.opencodeGo.tagline": "Abonnement abordable pour tous",
|
||||
"dialog.provider.anthropic.note": "Connectez-vous avec Claude Pro/Max ou une clÊ API",
|
||||
"dialog.provider.copilot.note": "Connectez-vous avec Copilot ou une clÊ API",
|
||||
"dialog.provider.openai.note": "Connectez-vous avec ChatGPT Pro/Plus ou une clÊ API",
|
||||
"dialog.provider.google.note": "Modèles Gemini pour des rÊponses rapides et structurÊes",
|
||||
@@ -217,7 +216,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Voir plus de 70 autres fournisseurs",
|
||||
"dialog.provider.viewAll": "Voir plus de fournisseurs",
|
||||
"provider.connect.title": "Connecter {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Connexion avec Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "SÊlectionnez la mÊthode de connexion pour {{provider}}.",
|
||||
"provider.connect.method.apiKey": "ClÊ API",
|
||||
"provider.connect.method.browser": "Navigateur",
|
||||
@@ -385,10 +383,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Supprimer la pièce jointe",
|
||||
"prompt.action.send": "Envoyer",
|
||||
"prompt.action.stop": "ArrÃĒter",
|
||||
"prompt.toast.pasteUnsupported.title": "Pièce jointe non prise en charge",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ce fichier a dÊjà ÊtÊ tÊlÊversÊ",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"Seules les images, les PDF ou les fichiers texte peuvent ÃĒtre joints ici.",
|
||||
"prompt.toast.modelAgentRequired.title": "SÊlectionnez un agent et un modèle",
|
||||
"prompt.toast.modelAgentRequired.description": "Choisissez un agent et un modèle avant d'envoyer une invite.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Ãchec de la crÊation de l'arbre de travail",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "×××ר ××××××, ××× Claude, GPT, Gemini ××ĸ××",
|
||||
"dialog.provider.opencode.tagline": "×××××× ×××× ×× ×Š×ĸ××¨× ××פ××××××Ļ××",
|
||||
"dialog.provider.opencodeGo.tagline": "×× ×× ××ĸ×××Ē × ×××× ×××××",
|
||||
"dialog.provider.anthropic.note": "×××Š× ×׊××¨× ×××××× Claude, ×××× Pro ×-Max",
|
||||
"dialog.provider.copilot.note": "××××× AI ×ץ×××ĸ ×××Ē×××Ē ×§×× ××××Ļ×ĸ××Ē GitHub Copilot",
|
||||
"dialog.provider.openai.note": "××××× GPT ××׊××××Ē AI ××××××Ē, ×××ר××Ē ×××Ē×§××××Ē",
|
||||
"dialog.provider.google.note": "××××× Gemini ××Ē×××××Ē ×××ר××Ē ××××× ××Ē",
|
||||
@@ -209,7 +208,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "××Ļ××Ē ×××Ēר ×-70 ץפק×× × ×ץפ××",
|
||||
"dialog.provider.viewAll": "××Ļ× ×ĸ×× ×Ą×¤×§××",
|
||||
"provider.connect.title": "××ר ××Ē {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "××Ē××ר ×ĸ× Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "××ר ׊×××Ē ××Ē××ר××Ē ×ĸ××ר {{provider}}.",
|
||||
"provider.connect.method.apiKey": "×פ×Ē× API",
|
||||
"provider.connect.method.browser": "×פ×פ×",
|
||||
@@ -373,8 +371,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "×ץר ××Ē ××§×××Ĩ ×××Ļ×׍ת",
|
||||
"prompt.action.send": "׊××",
|
||||
"prompt.action.stop": "×ĸ×Ļ×ר",
|
||||
"prompt.toast.pasteUnsupported.title": "×§×××Ĩ ××Ļ×׍ת ×× × ×Ē××",
|
||||
"prompt.toast.pasteUnsupported.description": "× ××Ē× ××Ļ׍ת ××× ×¨×§ ×Ē××× ××Ē, ×§×××Ļ× PDF ×× ×§××Ļ× ×קץ×.",
|
||||
"prompt.toast.attachmentDuplicate.title": "××§×××Ĩ ××× ××ר ×××ĸ××",
|
||||
"prompt.toast.modelAgentRequired.title": "××ר ץ××× ×××××",
|
||||
"prompt.toast.modelAgentRequired.description": "×׊ ××××ר ץ××× ××××× ××¤× × ×Š××××Ē ×¤×¨××פ×.",
|
||||
|
||||
@@ -199,7 +199,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini ā¤ā¤° ā¤
⤧ā¤ŋā¤ ā¤¸ā¤šā¤ŋ⤤ ā¤āĨ⤝āĨ⤰āĨā¤āĨā¤Ą ā¤ŽāĨā¤Ąā¤˛",
|
||||
"dialog.provider.opencode.tagline": "ā¤ĩā¤ŋā¤ļāĨā¤ĩ⤏⤍āĨ⤝ ā¤
⤍āĨā¤āĨ⤞ā¤ŋ⤤ ā¤ŽāĨā¤Ąā¤˛",
|
||||
"dialog.provider.opencodeGo.tagline": "⤏ā¤āĨ ā¤āĨ ⤞ā¤ŋ⤠ā¤ā¤Ž ā¤˛ā¤žā¤ā¤¤ ā¤ĩā¤žā¤˛āĨ ⤏ā¤Ļ⤏āĨā¤¯ā¤¤ā¤ž",
|
||||
"dialog.provider.anthropic.note": "ā¤ĒāĨ⤰āĨ ā¤ā¤° ā¤ŽāĨā¤āĨ⤏ ā¤¸ā¤šā¤ŋ⤤ Claude ā¤ŽāĨā¤Ąā¤˛ ⤤⤠⤏āĨ⤧āĨ ā¤Ēā¤šāĨā¤ā¤",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot ā¤āĨ ā¤Žā¤žā¤§āĨā¤¯ā¤Ž ⤏āĨ ā¤āĨā¤Ąā¤ŋā¤ā¤ ā¤¸ā¤šā¤žā¤¯ā¤¤ā¤ž ā¤āĨ ⤞ā¤ŋ⤠AI ā¤ŽāĨā¤Ąā¤˛",
|
||||
"dialog.provider.openai.note": "⤤āĨā¤ā¤ŧ, ⤏ā¤āĨā¤ˇā¤Ž ā¤¸ā¤žā¤Žā¤žā¤¨āĨ⤝ AI ā¤ā¤žā¤°āĨ⤝āĨ⤠ā¤āĨ ⤞ā¤ŋ⤠GPT ā¤ŽāĨā¤Ąā¤˛",
|
||||
"dialog.provider.google.note": "⤤āĨā¤ā¤ŧ, ⤏ā¤ā¤°ā¤ā¤ŋ⤤ ā¤ĒāĨ⤰⤤ā¤ŋā¤āĨ⤰ā¤ŋā¤¯ā¤žā¤ā¤ ā¤āĨ ⤞ā¤ŋ⤠Gemini ā¤ŽāĨā¤Ąā¤˛",
|
||||
@@ -216,7 +215,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70+ ā¤
⤧ā¤ŋ⤠ā¤ĒāĨ⤰āĨā¤ĩā¤žā¤ā¤Ąā¤° ā¤ĻāĨā¤āĨā¤",
|
||||
"dialog.provider.viewAll": "ā¤ā¤° ā¤ĒāĨ⤰āĨā¤ĩā¤žā¤ā¤Ąā¤° ā¤Ļā¤ŋā¤ā¤žā¤ā¤",
|
||||
"provider.connect.title": "{{provider}} ā¤ā¤¨āĨā¤āĨ⤠ā¤ā¤°āĨā¤",
|
||||
"provider.connect.title.anthropicProMax": "Claude ā¤ĒāĨ⤰āĨ/ā¤ŽāĨā¤āĨ⤏ ā¤āĨ ā¤¸ā¤žā¤Ĩ ⤞āĨā¤ā¤ŋ⤍ ā¤ā¤°āĨā¤",
|
||||
"provider.connect.selectMethod": "{{provider}} ā¤āĨ ⤞ā¤ŋ⤠⤞āĨā¤ā¤ŋ⤍ ā¤ĩā¤ŋ⤧ā¤ŋ ā¤āĨ⤍āĨā¤āĨ¤",
|
||||
"provider.connect.method.apiKey": "API ā¤āĨā¤ā¤āĨ",
|
||||
"provider.connect.method.browser": "ā¤ŦāĨā¤°ā¤žā¤ā¤ā¤ŧ⤰",
|
||||
@@ -382,8 +380,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ā¤
⤍āĨ⤞ā¤āĨā¤¨ā¤ ā¤šā¤ā¤žā¤ā¤",
|
||||
"prompt.action.send": "ā¤āĨā¤āĨā¤",
|
||||
"prompt.action.stop": "⤰āĨā¤āĨā¤",
|
||||
"prompt.toast.pasteUnsupported.title": "ā¤
ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ā¤
⤍āĨ⤞ā¤āĨ⤍ā¤",
|
||||
"prompt.toast.pasteUnsupported.description": "ā¤¯ā¤šā¤žā¤ ā¤āĨā¤ĩ⤞ ā¤ā¤ĩā¤ŋā¤¯ā¤žā¤, PDFs, ā¤¯ā¤ž ā¤āĨā¤āĨ⤏āĨ⤠ā¤Ģā¤ŧā¤žā¤ā¤˛āĨ⤠⤏ā¤ā¤˛ā¤āĨ⤍ ā¤āĨ ā¤ā¤ž ⤏ā¤ā¤¤āĨ ā¤šāĨā¤āĨ¤",
|
||||
"prompt.toast.attachmentDuplicate.title": "ā¤¯ā¤š ā¤Ģā¤ŧā¤žā¤ā¤˛ ā¤Ēā¤šā¤˛āĨ ā¤šāĨ ā¤
ā¤Ē⤞āĨā¤Ą ā¤āĨ ā¤ā¤ž ā¤āĨā¤āĨ ā¤šāĨ",
|
||||
"prompt.toast.modelAgentRequired.title": "ā¤ā¤ ā¤ā¤āĨā¤ā¤ ā¤ā¤° ā¤ŽāĨā¤Ąā¤˛ ā¤āĨ⤍āĨā¤",
|
||||
"prompt.toast.modelAgentRequired.description": "ā¤ĒāĨ⤰āĨā¤ŽāĨā¤ĒāĨ⤠ā¤āĨā¤ā¤¨āĨ ⤏āĨ ā¤Ēā¤šā¤˛āĨ ā¤ā¤ ā¤ā¤āĨā¤ā¤ ā¤ā¤° ā¤ŽāĨā¤Ąā¤˛ ā¤āĨ⤍āĨā¤āĨ¤",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Odabrani modeli ukljuÄujuÄi Claude, GPT, Gemini i druge",
|
||||
"dialog.provider.opencode.tagline": "Pouzdani optimizirani modeli",
|
||||
"dialog.provider.opencodeGo.tagline": "Niska cijena pretplate za sve",
|
||||
"dialog.provider.anthropic.note": "Izravan pristup Claude modelima, ukljuÄujuÄi Pro i Max",
|
||||
"dialog.provider.copilot.note": "AI modeli za pomoÄ kodiranja putem GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opÄe AI zadatke",
|
||||
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
|
||||
@@ -212,7 +211,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Pogledajte viÅĄe od 70 pruÅžatelja usluga",
|
||||
"dialog.provider.viewAll": "PrikaÅži viÅĄe pruÅžatelja usluga",
|
||||
"provider.connect.title": "PoveÅžite {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Prijavite se sa Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Odaberite naÄin prijave za {{provider}}.",
|
||||
"provider.connect.method.apiKey": "KljuÄ API",
|
||||
"provider.connect.method.browser": "preglednik",
|
||||
@@ -379,8 +377,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Ukloni privitak",
|
||||
"prompt.action.send": "Poslati",
|
||||
"prompt.action.stop": "Zaustavi",
|
||||
"prompt.toast.pasteUnsupported.title": "NepodrÅžani privitak",
|
||||
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu priloÅžiti samo slike, PDF-ovi ili tekstualne datoteke.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ova datoteka je veÄ uÄitana",
|
||||
"prompt.toast.modelAgentRequired.title": "Odaberite agenta i model",
|
||||
"prompt.toast.modelAgentRequired.description": "Odaberite agenta i model prije slanja upita.",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ÃsszevÃĄlogatott modellek, kÃļztÃŧk Claude, GPT, Gemini Ês mÊg sok mÃĄs",
|
||||
"dialog.provider.opencode.tagline": "MegbÃzhatÃŗ optimalizÃĄlt modellek",
|
||||
"dialog.provider.opencodeGo.tagline": "OlcsÃŗ elÅfizetÊs mindenki szÃĄmÃĄra",
|
||||
"dialog.provider.anthropic.note": "KÃļzvetlen hozzÃĄfÊrÊs a Claude modellekhez, beleÊrtve a Pro Ês Max modelleket",
|
||||
"dialog.provider.copilot.note": "AI modellek a kÃŗdolÃĄshoz a GitHub Copilot segÃtsÊgÊvel",
|
||||
"dialog.provider.openai.note": "GPT modellek a gyors, ÃĄltalÃĄnos AI-feladatokhoz",
|
||||
"dialog.provider.google.note": "Gemini modellek a gyors, strukturÃĄlt vÃĄlaszokhoz",
|
||||
@@ -212,7 +211,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "TovÃĄbbi 70+ szolgÃĄltatÃŗ megtekintÊse",
|
||||
"dialog.provider.viewAll": "TovÃĄbbi szolgÃĄltatÃŗk megjelenÃtÊse",
|
||||
"provider.connect.title": "Csatlakoztassa a {{provider}}-t",
|
||||
"provider.connect.title.anthropicProMax": "Jelentkezzen be a Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "VÃĄlassza ki a {{provider}} bejelentkezÊsi mÃŗdjÃĄt.",
|
||||
"provider.connect.method.apiKey": "API kulcs",
|
||||
"provider.connect.method.browser": "BÃļngÊszÅ",
|
||||
@@ -379,8 +377,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "TÃĄvolÃtsa el a mellÊkletet",
|
||||
"prompt.action.send": "ElkÃŧld",
|
||||
"prompt.action.stop": "LeÃĄllÃtÃĄs",
|
||||
"prompt.toast.pasteUnsupported.title": "Nem tÃĄmogatott mellÊklet",
|
||||
"prompt.toast.pasteUnsupported.description": "Ide csak kÊpeket, PDF-eket vagy szÃļveges fÃĄjlokat lehet csatolni.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ezt a fÃĄjlt mÃĄr feltÃļltÃļttÊk",
|
||||
"prompt.toast.modelAgentRequired.title": "VÃĄlasszon egy ÃŧgynÃļkÃļt Ês modellt",
|
||||
"prompt.toast.modelAgentRequired.description": "A felszÃŗlÃtÃĄs elkÃŧldÊse elÅtt vÃĄlasszon ÃŧgynÃļkÃļt Ês modellt.",
|
||||
|
||||
@@ -193,7 +193,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Ô¸ÕļÕŋÖÕžÕĄÕŽ Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ, ÕļÕĨÖÕĄÕŧÕĩÕĄÕŦÕ Claude, GPT, Gemini Ö ÕĄÕžÕĨÕŦÕĢÕļ",
|
||||
"dialog.provider.opencode.tagline": "ÕÕŊÕŋÕĄÕ°ÕĨÕŦÕĢ Ö
ÕēÕŋÕĢÕ´ÕĢÕĻÕĄÖÕžÕĄÕŽ Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ",
|
||||
"dialog.provider.opencodeGo.tagline": "ÕÕĄÕŽÖ ÕŖÕļÕ¸Õž ÕĸÕĄÕĒÕĄÕļÕ¸ÖÕ¤ÕĄÕŖÖÕ¸ÖÕŠÕĩÕ¸ÖÕļ ÕĸÕ¸ÕŦÕ¸ÖÕĢ Õ°ÕĄÕ´ÕĄÖ",
|
||||
"dialog.provider.anthropic.note": "ÕÖÕ˛ÕĢÕ˛ Õ´Õ¸ÖÕŋÖ Õ¤ÕĨÕēÕĢ Claude Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ, ÕļÕĨÖÕĄÕŧÕĩÕĄÕŦ Pro Ö Max",
|
||||
"dialog.provider.copilot.note": "AI Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ Õ¯Õ¸Õ¤ÕĄÕžÕ¸ÖÕ´ÕĄÕļ Ö
ÕŖÕļÕ¸ÖÕŠÕĩÕĄÕļ Õ°ÕĄÕ´ÕĄÖÕ GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ ÕĄÖÕĄÕŖ, Õ¸ÖÕļÕĄÕ¯ Õ¨ÕļÕ¤Õ°ÕĄÕļÕ¸ÖÖ AI ÕĄÕŧÕĄÕģÕĄÕ¤ÖÕĄÕļÖÕļÕĨÖÕĢ Õ°ÕĄÕ´ÕĄÖ",
|
||||
"dialog.provider.google.note": "Gemini Õ´Õ¸Õ¤ÕĨÕŦÕļÕĨÖ ÕĄÖÕĄÕŖ, Õ¯ÕĄÕŧÕ¸ÖÖÕžÕĄÕŽÖÕĄÕĩÕĢÕļ ÕēÕĄÕŋÕĄÕŊÕÕĄÕļÕļÕĨÖÕĢ Õ°ÕĄÕ´ÕĄÖ",
|
||||
@@ -210,7 +209,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ÕÕĨÕŊ 70+ ÕĄÕĩÕŦ Õ´ÕĄÕŋÕĄÕ¯ÕĄÖÕĄÖÕļÕĨÖ",
|
||||
"dialog.provider.viewAll": "ÕÕ¸ÖÕĩÖ ÕŋÕĄÕŦ ÕĄÕžÕĨÕŦÕĢ ÕˇÕĄÕŋ Õ´ÕĄÕŋÕĄÕ¯ÕĄÖÕĄÖÕļÕĨÖ",
|
||||
"provider.connect.title": "ÕÕĢÕĄÖÕĨÖ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ÕÕ¸ÖÕŋÖ ÕŖÕ¸ÖÕŽÕĨÖ Claude Pro/Max-Õ¸Õž",
|
||||
"provider.connect.selectMethod": "Ô¸ÕļÕŋÖÕĨÖ Õ´Õ¸ÖÕŋÖÕĢ ÕĨÕ˛ÕĄÕļÕĄÕ¯Õ¨ {{provider}}-ÕĢ Õ°ÕĄÕ´ÕĄÖÖ",
|
||||
"provider.connect.method.apiKey": "API ÕĸÕĄÕļÕĄÕŦÕĢ",
|
||||
"provider.connect.method.browser": "Ô´ÕĢÕŋÕĄÖÕ¯ÕĢÕš",
|
||||
@@ -377,8 +375,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ÕÕĨÕŧÕĄÖÕļÕĨÕŦ Õ°ÕĄÕžÕĨÕŦÕžÕĄÕŽÕ¨",
|
||||
"prompt.action.send": "ÕÖÕ˛ÕĄÖÕ¯ÕĨÕŦ",
|
||||
"prompt.action.stop": "ÔŋÕĄÕļÕŖÕļÕĨÖÕļÕĨÕŦ",
|
||||
"prompt.toast.pasteUnsupported.title": "ÕÕĄÕģÕĄÕ¯ÖÕžÕ¸Õ˛ Õ°ÕĄÕžÕĨÕŦÕžÕĄÕŽ",
|
||||
"prompt.toast.pasteUnsupported.description": "ÔąÕĩÕŊÕŋÕĨÕ˛ Õ¯ÕĄÖÕ¸Õ˛ ÕĨÕļ Õ¯ÖÕžÕĨÕŦ Õ´ÕĢÕĄÕĩÕļ ÕēÕĄÕŋÕ¯ÕĨÖÕļÕĨÖ, PDF Õ¯ÕĄÕ´ ÕŋÕĨÖÕŊÕŋÕĄÕĩÕĢÕļ ÖÕĄÕĩÕŦÕĨÖÖ",
|
||||
"prompt.toast.attachmentDuplicate.title": "ÔąÕĩÕŊ ÖÕĄÕĩÕŦÕļ ÕĄÖÕ¤ÕĨÕļ ÕžÕĨÖÕĸÕĨÕŧÕļÕžÕĨÕŦ Õ§",
|
||||
"prompt.toast.modelAgentRequired.title": "Ô¸ÕļÕŋÖÕĨÖ ÕŖÕ¸ÖÕŽÕĄÕ¯ÕĄÕŦ Ö Õ´Õ¸Õ¤ÕĨÕŦ",
|
||||
"prompt.toast.modelAgentRequired.description": "Ô¸ÕļÕŋÖÕĨÖ ÕŖÕ¸ÖÕŽÕĄÕ¯ÕĄÕŦ Ö Õ´Õ¸Õ¤ÕĨÕŦÕ ÕļÕĄÕÖÕĄÕļ Õ°Õ¸ÖÕˇÕ¸ÖÕ´ Õ¸ÖÕ˛ÕĄÖÕ¯ÕĨÕŦÕ¨Ö",
|
||||
|
||||
@@ -208,7 +208,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Model pilihan, termasuk Claude, GPT, Gemini, dan lainnya",
|
||||
"dialog.provider.opencode.tagline": "Model andal yang dioptimalkan",
|
||||
"dialog.provider.opencodeGo.tagline": "Langganan terjangkau untuk semua orang",
|
||||
"dialog.provider.anthropic.note": "Akses langsung ke model Claude, termasuk Pro dan Max",
|
||||
"dialog.provider.copilot.note": "Model AI untuk bantuan pengodean melalui GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Model GPT untuk tugas AI umum yang cepat dan mumpuni",
|
||||
"dialog.provider.google.note": "Model Gemini untuk respons cepat dan terstruktur",
|
||||
@@ -229,7 +228,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "Tampilkan lebih banyak penyedia",
|
||||
|
||||
"provider.connect.title": "Hubungkan {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Masuk dengan Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Pilih metode masuk untuk {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Kunci API",
|
||||
"provider.connect.method.browser": "Peramban",
|
||||
@@ -403,8 +401,6 @@ export const dict = {
|
||||
"prompt.action.send": "Kirim",
|
||||
"prompt.action.stop": "Hentikan",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Lampiran tidak didukung",
|
||||
"prompt.toast.pasteUnsupported.description": "Hanya gambar, PDF, atau berkas teks yang dapat dilampirkan di sini.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Berkas ini sudah diunggah",
|
||||
"prompt.toast.modelAgentRequired.title": "Pilih agen dan model",
|
||||
"prompt.toast.modelAgentRequired.description": "Pilih agen dan model sebelum mengirim prompt.",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "SÃŊndar gerðir Þar ÃĄ meðal Claude, GPT, Gemini og fleira",
|
||||
"dialog.provider.opencode.tagline": "Ãreiðanleg bjartsÃŊni mÃŗdel",
|
||||
"dialog.provider.opencodeGo.tagline": "LÃĄggjaldaÃĄskrift fyrir alla",
|
||||
"dialog.provider.anthropic.note": "Beinn aðgangur að Claude gerðum, Þar ÃĄ meðal Pro og Max",
|
||||
"dialog.provider.copilot.note": "AI mÃŗdel fyrir kÃŗÃ°aaðstoð à gegnum GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT gerðir fyrir hrÃļð, fÃĻr almenn gervigreind verkefni",
|
||||
"dialog.provider.google.note": "Gemini mÃŗdel fyrir hrÃļð, skipulÃļgð viðbrÃļgð",
|
||||
@@ -212,7 +211,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "SjÃĄÃ°u 70+ ÞjÃŗnustuveitendur à viðbÃŗt",
|
||||
"dialog.provider.viewAll": "SÃŊna fleiri veitendur",
|
||||
"provider.connect.title": "Tengdu {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "SkrÃĄÃ°u Þig inn með Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Veldu innskrÃĄningaraðferð fyrir {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API lykill",
|
||||
"provider.connect.method.browser": "Vafri",
|
||||
@@ -379,8 +377,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "FjarlÃĻgðu viðhengi",
|
||||
"prompt.action.send": "Senda",
|
||||
"prompt.action.stop": "StÃļðva",
|
||||
"prompt.toast.pasteUnsupported.title": "Ãstudd viðhengi",
|
||||
"prompt.toast.pasteUnsupported.description": "Aðeins er hÃĻgt að hengja myndir, PDF-skjÃļl eða textaskrÃĄr hÊr við.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ãessari skrÃĄ hefur Þegar verið hlaðið upp",
|
||||
"prompt.toast.modelAgentRequired.title": "Veldu fulltrÃēa og lÃkan",
|
||||
"prompt.toast.modelAgentRequired.description": "Veldu fulltrÃēa og lÃkan ÃĄÃ°ur en ÞÃē sendir kvaðningu.",
|
||||
|
||||
@@ -100,7 +100,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Modelli selezionati tra cui Claude, GPT, Gemini e altri",
|
||||
"dialog.provider.opencode.tagline": "Modelli affidabili e ottimizzati",
|
||||
"dialog.provider.opencodeGo.tagline": "Abbonamento economico per tutti",
|
||||
"dialog.provider.anthropic.note": "Accesso diretto ai modelli Claude, inclusi Pro e Max",
|
||||
"dialog.provider.copilot.note": "Modelli di IA per assistere nella programmazione tramite GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Modelli GPT veloci e potenti per attività generiche di IA",
|
||||
"dialog.provider.google.note": "Modelli Gemini per risposte rapide e strutturate",
|
||||
@@ -117,7 +116,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Scopri oltre 70 provider",
|
||||
"dialog.provider.viewAll": "Mostra altri provider",
|
||||
"provider.connect.title": "Connetti {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Accedi con Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Seleziona il metodo di accesso per {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Chiave API",
|
||||
"provider.connect.method.browser": "Browser",
|
||||
@@ -284,8 +282,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Rimuovi l'allegato",
|
||||
"prompt.action.send": "Invia",
|
||||
"prompt.action.stop": "Interrompi",
|
||||
"prompt.toast.pasteUnsupported.title": "Allegato non supportato",
|
||||
"prompt.toast.pasteUnsupported.description": "Qui è possibile allegare solo immagini, PDF o file di testo.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Questo file è già stato caricato",
|
||||
"prompt.toast.modelAgentRequired.title": "Seleziona un agente e un modello",
|
||||
"prompt.toast.modelAgentRequired.description": "Scegli un agente e un modello prima di inviare un prompt.",
|
||||
|
||||
@@ -198,7 +198,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, GeminiãĒãŠãåĢãåŗé¸ããããĸããĢ",
|
||||
"dialog.provider.opencode.tagline": "äŋĄé ŧæ§ãŽéĢãæéŠåãĸããĢ",
|
||||
"dialog.provider.opencodeGo.tagline": "ããšãĻãŽäēēãĢäŊ䞥æ ŧãŽãĩããšã¯ãĒããˇã§ãŗ",
|
||||
"dialog.provider.anthropic.note": "ProãMaxãåĢãClaudeãĸããĢãĢį´æĨãĸã¯ãģãš",
|
||||
"dialog.provider.copilot.note": "GitHub CopilotãéããĻãŗãŧããŖãŗã°ãæ¯æ´ããAIãĸããĢ",
|
||||
"dialog.provider.openai.note": "éĢéã§éĢæ§čŊãĒæąį¨AIãŋãšã¯åããŽGPTãĸããĢ",
|
||||
"dialog.provider.google.note": "éĢéã§æ§é åãããåŋįãŽãããŽGeminiãĸããĢ",
|
||||
@@ -215,7 +214,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ããŽäģ70äģĨä¸ãŽãããã¤ããŧãčĻã",
|
||||
"dialog.provider.viewAll": "ãããĢãããã¤ããŧã襨į¤ē",
|
||||
"provider.connect.title": "{{provider}}ãæĨįļ",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Maxã§ãã°ã¤ãŗ",
|
||||
"provider.connect.selectMethod": "{{provider}}ãŽãã°ã¤ãŗæšæŗã鏿ããĻãã ããã",
|
||||
"provider.connect.method.apiKey": "APIããŧ",
|
||||
"provider.connect.method.browser": "ããŠãĻãļ",
|
||||
@@ -379,9 +377,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "æˇģäģããĄã¤ãĢãåé¤",
|
||||
"prompt.action.send": "éäŋĄ",
|
||||
"prompt.action.stop": "åæĸ",
|
||||
"prompt.toast.pasteUnsupported.title": "ãĩããŧããããĻããĒãæˇģäģããĄã¤ãĢ",
|
||||
"prompt.toast.attachmentDuplicate.title": "ããŽããĄã¤ãĢã¯ãã§ãĢãĸããããŧããããĻããžã",
|
||||
"prompt.toast.pasteUnsupported.description": "įģåãPDFããžãã¯ãããšãããĄã¤ãĢãŽãŋæˇģäģã§ããžãã",
|
||||
"prompt.toast.modelAgentRequired.title": "ã¨ãŧã¸ã§ãŗãã¨ãĸããĢã鏿",
|
||||
"prompt.toast.modelAgentRequired.description": "ãããŗãããéäŋĄããåãĢã¨ãŧã¸ã§ãŗãã¨ãĸããĢã鏿ããĻãã ããã",
|
||||
"prompt.toast.worktreeCreateFailed.title": "ã¯ãŧã¯ããĒãŧãŽäŊæãĢå¤ąæããžãã",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ááŖá áá áááŖáá áááááááá, ááá á¨áá áᥠClaude, GPT, Gemini áá áĄáŽáá",
|
||||
"dialog.provider.opencode.tagline": "áĄáááá áááĸáááááááŖáá áááááááá",
|
||||
"dialog.provider.opencodeGo.tagline": "áááááá¤ááĄáááá áááááŦáá á á§áááááĄááááĄ",
|
||||
"dialog.provider.anthropic.note": "ááá ááááá á áŦááááá Claude ááááááá, ááá á¨áá áᥠPro áá Max",
|
||||
"dialog.provider.copilot.note": "AI áááááááá ááááá áááᥠáááŽááá ááááĄáááᥠGitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT áááááááá áĄáŦá áá¤á, áááááá AI ááááĒáááááᥠá¨ááĄááĄá áŖáááááá",
|
||||
"dialog.provider.google.note": "Gemini áááááááá áĄáŦá áá¤á, áĄáĸá áŖáĨáĸáŖá áá áááŖáá áááĄáŖáŽááááĄááááĄ",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ááŽáááá 70+ áĄáŽáá áá ááááááá á",
|
||||
"dialog.provider.viewAll": "ááŠáááá áááĸá áá ááááááá á",
|
||||
"provider.connect.title": "áááááá¨áá ááá {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "á¨ááĄááá Claude Pro/Max-áá",
|
||||
"provider.connect.selectMethod": "ááá áŠááá á¨ááĄáááᥠáááááá {{provider}}-ááĄááááĄ.",
|
||||
"provider.connect.method.apiKey": "API áááĄááĻááá",
|
||||
"provider.connect.method.browser": "áá ááŖááá á",
|
||||
@@ -375,8 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ááááá ááᥠáŦáá¨áá",
|
||||
"prompt.action.send": "áááááááá",
|
||||
"prompt.action.stop": "á¨ááŠáá ááá",
|
||||
"prompt.toast.pasteUnsupported.title": "ááŽáá áááŖááá ááá ááááá áá",
|
||||
"prompt.toast.pasteUnsupported.description": "ááĨ á¨áááĢáááá áááá ááᥠááŽáááá áĄáŖá ááááá, PDF áá áĸááĨáĄáĸáŖá á á¤áááááá.",
|
||||
"prompt.toast.attachmentDuplicate.title": "áᥠá¤áááá áŖááá ááĸááá ááŖááá",
|
||||
"prompt.toast.modelAgentRequired.title": "ááá áŠááá áááááĸá áá áááááá",
|
||||
"prompt.toast.modelAgentRequired.description": "ááá áŠááá áááááĸá áá áááááá ááááŽááááᥠááááááááááá.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "áááŧááááááááļááááážááážáááŊáááļá Claude, GPT, Gemini ááˇáá
ááážáááá",
|
||||
"dialog.provider.opencode.tagline": "áááŧááááááááļáááááááĸááááĸáļá
ááģáá
áˇáááááļáá",
|
||||
"dialog.provider.opencodeGo.tagline": "ááļáááļááááááááļááááááļáááĸáááááļááááááļ",
|
||||
"dialog.provider.anthropic.note": "ááļáá
áŧááááážáááááááļáááá
ááļáááááŧááá Claude ááŊáááļáá Pro ááˇá Max",
|
||||
"dialog.provider.copilot.note": "áááŧááá AI áááááļááááááŊáááļááááááááŧáááļáááá GitHub Copilot",
|
||||
"dialog.provider.openai.note": "áááŧááá GPT áááááļááááˇá
áá
ááļá AI ááŧáá
áááááļáááááŋáááŋá ááˇáááļááááááááļá",
|
||||
"dialog.provider.google.note": "áááŧááá Gemini áááááļááááļááááážááááá áá ááˇáááļááá
ááļáááááááá",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "áážááĸááááááááááááļ 70+ áááá",
|
||||
"dialog.provider.viewAll": "áááá áļááĸááááááááááááļáááááá",
|
||||
"provider.connect.title": "ááááļáá {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "á
áŧááááááááž Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "áááážááážáááˇáá¸ááļáááááá
áŧááááááļáá {{provider}} á",
|
||||
"provider.connect.method.apiKey": "áá API",
|
||||
"provider.connect.method.browser": "ááááááˇáá¸ááģááá",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ááģáá¯áááļáááááļááá
áá",
|
||||
"prompt.action.send": "ááááž",
|
||||
"prompt.action.stop": "ááá",
|
||||
"prompt.toast.pasteUnsupported.title": "á¯áááļáááááļáááááááˇáááļáááá",
|
||||
"prompt.toast.pasteUnsupported.description": "ááļáááááŧáááļá PDF áŦá¯áááļááĸááááááááģáááááááááĸáļá
ááááļáááááá¸áááááļáá",
|
||||
"prompt.toast.attachmentDuplicate.title": "á¯áááļááááááááŧáááļáááááģááĄážáááŊá
á ážá",
|
||||
"prompt.toast.modelAgentRequired.title": "áááážááážáááááļááááļá ááˇááááŧááá",
|
||||
"prompt.toast.modelAgentRequired.description": "áááážááážáááááļááááļá ááˇááááŧáááááģáááááááážááááĸáááááá
áŧáá",
|
||||
|
||||
@@ -95,7 +95,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini ëąė íŦí¨í ėė ë ëǍë¸",
|
||||
"dialog.provider.opencode.tagline": "ė ëĸ°í ė ėë ėĩė í ëǍë¸",
|
||||
"dialog.provider.opencodeGo.tagline": "ëǍëëĨŧ ėí ė ë ´í ęĩŦë
",
|
||||
"dialog.provider.anthropic.note": "Pro ë° MaxëĨŧ íŦí¨í Claude ëǍë¸ė ė§ė ėĄė¸ė¤",
|
||||
"dialog.provider.copilot.note": "GitHub Copilotė íĩí ėŊëŠ ė§ėėŠ AI ëǍë¸",
|
||||
"dialog.provider.openai.note": "ëš ëĨ´ęŗ ę°ë Ĩí ë˛ėŠ AI ėė
ė ėí GPT ëǍë¸",
|
||||
"dialog.provider.google.note": "ëš ëĨ´ęŗ ęĩŦėĄ°íë ėëĩė ėí Gemini ëǍë¸",
|
||||
@@ -112,7 +111,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70ę° ė´ėė ęŗĩę¸ė ë ëŗ´ę¸°",
|
||||
"dialog.provider.viewAll": "ë ë§ė ęŗĩę¸ė ëŗ´ę¸°",
|
||||
"provider.connect.title": "{{provider}} ė°ę˛°",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/MaxëĄ ëĄęˇ¸ė¸",
|
||||
"provider.connect.selectMethod": "{{provider}} ëĄęˇ¸ė¸ ë°Šë˛ ė í",
|
||||
"provider.connect.method.apiKey": "API í¤",
|
||||
"provider.connect.method.browser": "ë¸ëŧė°ė ",
|
||||
@@ -268,9 +266,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "랍ëļ íėŧ ė ęą°",
|
||||
"prompt.action.send": "ė ėĄ",
|
||||
"prompt.action.stop": "ė¤ė§",
|
||||
"prompt.toast.pasteUnsupported.title": "ė§ėëė§ ėë 랍ëļ íėŧ",
|
||||
"prompt.toast.attachmentDuplicate.title": "ė´ íėŧė ė´ë¯¸ ė
ëĄëëėėĩëë¤",
|
||||
"prompt.toast.pasteUnsupported.description": "ė´ë¯¸ė§, PDF ëë í
ė¤í¸ íėŧë§ ė˛¨ëļí ė ėėĩëë¤.",
|
||||
"prompt.toast.modelAgentRequired.title": "ėė´ė í¸ ë° ëĒ¨ë¸ ė í",
|
||||
"prompt.toast.modelAgentRequired.description": "íëĄŦíí¸ëĨŧ ëŗ´ë´ę¸° ė ė ėė´ė í¸ė ëǍë¸ė ė ííė¸ė.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "ėė
í¸ëĻŦ ėėą ė¤í¨",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "āģāēāēāēāēĩāģāēāēąāēāēĒāēąāēāēĄāē˛āēĨāē§āēĄāēāēąāē Claude, GPT, Gemini āģāēĨāē°āēāēˇāģāēāģ",
|
||||
"dialog.provider.opencode.tagline": "āēŽāēšāēāģāēāēāēāē˛āēāģāēāēĩāģāēĄāēāē°āēĒāē´āēāēāē´āēāē˛āēāēāēĩāģāģāēāēˇāģāēāēāēˇāģāēāģ",
|
||||
"dialog.provider.opencodeGo.tagline": "āēāē˛āēāēĒāē°āģāēąāēāģāēāģāēĨāē˛āēāē˛āēāģāģāē˛āēĒāēŗāēĨāēąāēāēāē¸āēāēāēģāē",
|
||||
"dialog.provider.anthropic.note": "āģāēāēģāģāē˛āģāēāē´āēāēāēģāē§āģāēāē Claude āģāēāēāēāēģāē, āēĨāē§āēĄāēāēąāē Pro āģāēĨāē° Max",
|
||||
"dialog.provider.copilot.note": "āēŽāēšāēāģāēāē AI āēĒāģāē˛āēĨāēąāēāēāē˛āēāēāģāē§āēāģāēĢāēŧāēˇāēāēāē˛āēāģāēāēģāģāē˛āēĨāē°āēĢāēąāēāēāģāē˛āē GitHub Copilot",
|
||||
"dialog.provider.openai.note": "āģāēāē GPT āēĒāģāē˛āēĨāēąāēāē§āēŊāēāēāē˛āē AI āēāēģāģāē§āģāēāēāēĩāģāģāē§, āēĄāēĩāēāē§āē˛āēĄāēĒāē˛āēĄāē˛āē",
|
||||
"dialog.provider.google.note": "āģāēāē Gemini āēĒāģāē˛āēĨāēąāēāēāē˛āēāēāēāēāēĒāē°āēĢāēāēāēāģāē§, āēĄāēĩāģāēāēāēĒāģāē˛āē",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "āģāēāē´āģāē 70+ āēāēšāģāģāēĢāģāēāģāēĨāē´āēāē˛āēāģāēāēĩāģāēĄāģāēāēĩāēĄ",
|
||||
"dialog.provider.viewAll": "āēĒāē°āģāēāēāēāēšāģāģāēĢāģāēāģāēĨāē´āēāē˛āēāģāēāēĩāģāēĄāģāēāēĩāēĄ",
|
||||
"provider.connect.title": "āģāēāēˇāģāēāēĄāēāģāģ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "āģāēāēģāģāē˛āēĒāēšāģāēĨāē°āēāēģāēāēāģāē§āē Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "āģāēĨāēˇāēāēāē§āē´āēāēĩāēāē˛āēāģāēāēģāģāē˛āēĒāēšāģāēĨāē°āēāēģāēāēĒāģāē˛āēĨāēąāē {{provider}}.",
|
||||
"provider.connect.method.apiKey": "āēĨāē°āēĢāēąāē API",
|
||||
"provider.connect.method.browser": "āēāēģāē§āēāģāēāēāģāē§āēąāē",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "āģāēāēģāē˛āģāēāēĨāģāģāēāēāēāēāē",
|
||||
"prompt.action.send": "āēĒāēģāģāē",
|
||||
"prompt.action.stop": "āēĸāē¸āē",
|
||||
"prompt.toast.pasteUnsupported.title": "āēāģāģāēŽāēāēāēŽāēąāēāģāēāēĨāģāģāēāē",
|
||||
"prompt.toast.pasteUnsupported.description": "āēāēŊāēāģāēāģāēŽāēšāēāēāē˛āē, PDFs, āēĢāēŧāēˇāģāēāēĨāģāēāģāģāēāē§āē˛āēĄāēĒāē˛āēĄāē˛āēāēāē´āēāēĸāēšāģāēāēĩāģ.",
|
||||
"prompt.toast.attachmentDuplicate.title": "āģāēāēĨāģāēāēĩāģāģāēāģāēāēˇāēāēāēąāēāģāēĢāēĨāēāģāēāēāģāēāēāģāēĨāģāē§",
|
||||
"prompt.toast.modelAgentRequired.title": "āģāēĨāēˇāēāēāēāēģāē§āģāēāē āģāēĨāē°āēāēģāē§āģāēāē",
|
||||
"prompt.toast.modelAgentRequired.description": "āģāēĨāēˇāēāēāēāēģāē§āģāēāē āģāēĨāē°āēāēģāē§āģāēāēāēāģāēāēāēĒāēģāģāē prompt.",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Kuruojami modeliai, įskaitant Claude, GPT, Gemini ir kt.",
|
||||
"dialog.provider.opencode.tagline": "Patikimi optimizuoti modeliai",
|
||||
"dialog.provider.opencodeGo.tagline": "MaÅžos kainos abonementas visiems",
|
||||
"dialog.provider.anthropic.note": "TiesioginÄ prieiga prie Claude modeliÅŗ, įskaitant Pro ir Max",
|
||||
"dialog.provider.copilot.note": "AI modeliai, skirti kodavimo pagalbai per GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modeliai, skirti greitoms, bendroms AI uÅžduotims atlikti",
|
||||
"dialog.provider.google.note": "Gemini modeliai greitiems, struktÅĢriniams atsakymams",
|
||||
@@ -212,7 +211,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ÅŊr. daugiau nei 70 tiekÄjÅŗ",
|
||||
"dialog.provider.viewAll": "Rodyti daugiau teikÄjÅŗ",
|
||||
"provider.connect.title": "Prijunkite {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Prisijunkite naudodami Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Pasirinkite {{provider}} prisijungimo bÅĢdÄ
.",
|
||||
"provider.connect.method.apiKey": "API raktas",
|
||||
"provider.connect.method.browser": "NarÅĄyklÄ",
|
||||
@@ -380,8 +378,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "PaÅĄalinti priedÄ
",
|
||||
"prompt.action.send": "SiÅŗsti",
|
||||
"prompt.action.stop": "Stabdyti",
|
||||
"prompt.toast.pasteUnsupported.title": "Nepalaikomas priedas",
|
||||
"prompt.toast.pasteUnsupported.description": "Äia galima pridÄti tik vaizdus, ââPDF arba tekstinius failus.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Šis failas jau buvo įkeltas",
|
||||
"prompt.toast.modelAgentRequired.title": "Pasirinkite agentÄ
ir modelį",
|
||||
"prompt.toast.modelAgentRequired.description": "PrieÅĄ siÅŗsdami raginimÄ
, pasirinkite agentÄ
ir modelį.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "AtlasÄĢti modeÄŧi, tostarp Claude, GPT, Gemini un citi",
|
||||
"dialog.provider.opencode.tagline": "Uzticami optimizÄti modeÄŧi",
|
||||
"dialog.provider.opencodeGo.tagline": "Zemas cenas abonements visiem",
|
||||
"dialog.provider.anthropic.note": "TieÅĄa piekÄŧuve Claude modeÄŧiem, tostarp Pro un Max",
|
||||
"dialog.provider.copilot.note": "AI modeÄŧi programmÄÅĄanas atbalstam ar GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modeÄŧi Ätriem, spÄjÄĢgiem vispÄrÄjiem AI uzdevumiem",
|
||||
"dialog.provider.google.note": "Gemini modeÄŧi ÄtrÄm, strukturÄtÄm atbildÄm",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "SkatÄĢt vÄl 70+ nodroÅĄinÄtÄjus",
|
||||
"dialog.provider.viewAll": "RÄdÄĢt vairÄk nodroÅĄinÄtÄju",
|
||||
"provider.connect.title": "Savienot {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Pieteikties ar Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "IzvÄlieties pieteikÅĄanÄs metodi pakalpojumam {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API atslÄga",
|
||||
"provider.connect.method.browser": "PÄrlÅĢks",
|
||||
@@ -375,8 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "NoÅemt pielikumu",
|
||||
"prompt.action.send": "SÅĢtÄĢt",
|
||||
"prompt.action.stop": "ApturÄt",
|
||||
"prompt.toast.pasteUnsupported.title": "NeatbalstÄĢts pielikums",
|
||||
"prompt.toast.pasteUnsupported.description": "Å eit var pievienot tikai attÄlus, PDF vai teksta failus.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Å is fails jau ir augÅĄupielÄdÄts",
|
||||
"prompt.toast.modelAgentRequired.title": "IzvÄlieties aÄŖentu un modeli",
|
||||
"prompt.toast.modelAgentRequired.description": "Pirms nosÅĢtÄĢÅĄanas izvÄlieties aÄŖentu un modeli.",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ĐŅŅиŅаĐŊи ĐŧОдĐĩĐģи вĐēĐģŅŅŅваŅŅи Claude, GPT, Gemini и ĐŋОвĐĩŅĐĩ",
|
||||
"dialog.provider.opencode.tagline": "ĐĄĐ¸ĐŗŅŅĐŊи ĐžĐŋŅиĐŧиСиŅаĐŊи ĐŧОдĐĩĐģи",
|
||||
"dialog.provider.opencodeGo.tagline": "ĐиŅĐēа ŅĐĩĐŊа ĐŋŅĐĩŅĐŋĐģаŅа Са ŅĐĩĐēĐžĐŗĐž",
|
||||
"dialog.provider.anthropic.note": "ĐиŅĐĩĐēŅĐĩĐŊ ĐŋŅиŅŅаĐŋ Đ´Đž Claude ĐŧОдĐĩĐģи, вĐēĐģŅŅŅваŅŅи ĐŗĐ¸ Pro и Max",
|
||||
"dialog.provider.copilot.note": "ĐОдĐĩĐģи ŅĐž вĐĩŅŅаŅĐēа иĐŊŅĐĩĐģĐ¸ĐŗĐĩĐŊŅиŅа Са ĐŋĐžĐŧĐžŅ ĐˇĐ° ĐēОдиŅаŅĐĩ ĐŋŅĐĩĐēŅ GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT ĐŧОдĐĩĐģи Са ĐąŅСи, ŅĐŋĐžŅОйĐŊи ĐžĐŋŅŅи СадаŅи ŅĐž вĐĩŅŅаŅĐēа иĐŊŅĐĩĐģĐ¸ĐŗĐĩĐŊŅиŅа",
|
||||
"dialog.provider.google.note": "Gemini ĐŧОдĐĩĐģи Са ĐąŅСи, ŅŅŅŅĐēŅŅŅиŅаĐŊи ĐžĐ´ĐŗĐžĐ˛ĐžŅи",
|
||||
@@ -209,7 +208,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ĐидĐĩŅĐĩ ĐŋОвĐĩŅĐĩ Од 70 ĐŋŅОваŅĐ´ĐĩŅи",
|
||||
"dialog.provider.viewAll": "ĐŅиĐēаĐļи ĐŋОвĐĩŅĐĩ ĐŋŅОваŅĐ´ĐĩŅи",
|
||||
"provider.connect.title": "ĐОвŅСĐĩŅĐĩ ŅĐĩ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ĐаŅавĐĩŅĐĩ ŅĐĩ ŅĐž Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "ĐСйĐĩŅĐĩŅĐĩ ĐŧĐĩŅОд Са ĐŊаŅавŅваŅĐĩ Са {{provider}}.",
|
||||
"provider.connect.method.apiKey": "ĐēĐģŅŅ API",
|
||||
"provider.connect.method.browser": "ĐŅĐĩĐģиŅŅŅваŅ",
|
||||
@@ -376,8 +374,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ĐŅŅŅŅаĐŊĐĩŅĐĩ ĐŗĐž ĐŋŅиĐģĐžĐŗĐžŅ",
|
||||
"prompt.action.send": "ĐŅĐŋŅаŅи",
|
||||
"prompt.action.stop": "ĐĄŅĐžĐŋ",
|
||||
"prompt.toast.pasteUnsupported.title": "ĐĐĩĐŋОддŅĐļаĐŊ ĐŋŅиĐģĐžĐŗ",
|
||||
"prompt.toast.pasteUnsupported.description": "ĐвдĐĩ ĐŧĐžĐļĐĩ да ŅĐĩ ĐŋŅиĐēаŅĐ°Ņ ŅаĐŧĐž ŅĐģиĐēи, PDFs иĐģи ŅĐĩĐēŅŅŅаĐģĐŊи даŅĐžŅĐĩĐēи.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Đваа даŅĐžŅĐĩĐēа Đĩ вĐĩŅĐĩ ĐŋĐžŅŅавĐĩĐŊа",
|
||||
"prompt.toast.modelAgentRequired.title": "ĐСйĐĩŅĐĩŅĐĩ Đ°ĐŗĐĩĐŊŅ Đ¸ ĐŧОдĐĩĐģ",
|
||||
"prompt.toast.modelAgentRequired.description": "ĐСйĐĩŅĐĩŅĐĩ Đ°ĐŗĐĩĐŊŅ Đ¸ ĐŧОдĐĩĐģ ĐŋŅĐĩĐ´ да иŅĐŋŅаŅиŅĐĩ иСвĐĩŅŅŅваŅĐĩ.",
|
||||
|
||||
@@ -194,7 +194,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini йОĐģĐžĐŊ ĐąŅŅад ĐˇĐ°ĐŗĐ˛Đ°ŅŅŅĐ´ŅĐŗ ŅĐžĐŊĐŗĐžĐŊ ŅаĐģĐŗĐ°ŅŅŅĐģŅаĐŊ",
|
||||
"dialog.provider.opencode.tagline": "ĐаКдваŅŅаК ĐžĐŊОвŅŅОК ĐˇĐ°ĐŗĐ˛Đ°ŅŅŅĐ´",
|
||||
"dialog.provider.opencodeGo.tagline": "ĐĨŌ¯ĐŊ ĐąŌ¯ŅŅ ĐˇĐžŅиŅĐģŅаĐŊ Ņ
ŅĐŧĐ´ ĶŠŅŅĶŠĐŗŅŅĐš СаŅ
иаĐģĐŗĐ°",
|
||||
"dialog.provider.anthropic.note": "Pro йОĐģĐžĐŊ Max СŅŅŅĐŗ Claude ĐˇĐ°ĐŗĐ˛Đ°ŅŅ ŅŅŅĐ´ Ņ
аĐŊдаŅ
",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot-ŅŅŅ Đ´Đ°ĐŧĐļŅŅĐģаĐŊ ĐēОдĐģĐžŅ
Од ŅŅŅĐģаŅ
Ņ
иКĐŧŅĐģ ĐžŅŅĐŊ ŅŅ
ааĐŊŅ ĐˇĐ°ĐŗĐ˛Đ°ŅŅŅĐ´",
|
||||
"dialog.provider.openai.note": "GPT AI-иКĐŊ Ņ
ŅŅдаĐŊ, ŅадваŅŅаК ĐĩŅĶŠĐŊŅ
иК аĐļĐģŅŅдад СОŅиŅĐģŅаĐŊ ĐˇĐ°ĐŗĐ˛Đ°ŅŅŅĐ´",
|
||||
"dialog.provider.google.note": "Gemini Ņ
ŅŅдаĐŊ, ĐąŌ¯ŅŅŅŅŅĐš Ņ
аŅиŅĐģŅ ĶŠĐŗĶŠŅ
ĐˇĐ°ĐŗĐ˛Đ°ŅŅŅĐ´",
|
||||
@@ -211,7 +210,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Ķ¨ĶŠŅ 70 ĐŗĐ°ŅŅĐš Ō¯ĐšĐģŅиĐģĐŗŅŅ Ō¯ĐˇŌ¯Ō¯ĐģŅĐŗŅĐ¸ĐšĐŗ Ņ
аŅĐŊа ŅŅ",
|
||||
"dialog.provider.viewAll": "ĐĐģŌ¯Ō¯ ĐžĐģĐžĐŊ Ō¯ĐšĐģŅиĐģĐŗŅŅ Ō¯ĐˇŌ¯Ō¯ĐģŅĐŗŅĐ¸ĐšĐŗ Ņ
аŅŅŅĐģаŅ
",
|
||||
"provider.connect.title": "ĐĨĐžĐģйОŅ
{{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max-Đ°Đ°Ņ ĐŊŅвŅŅŅĐŊŅ Ō¯Ō¯",
|
||||
"provider.connect.selectMethod": "{{provider}}-Đ´ ĐŊŅвŅŅŅŅ
аŅĐŗŅĐŗ ŅĐžĐŊĐŗĐžĐŊĐž ŅŅ.",
|
||||
"provider.connect.method.apiKey": "API ŅŌ¯ĐģŅ
Ō¯Ō¯Ņ",
|
||||
"provider.connect.method.browser": "ĐĨĶŠŅĶŠŅ",
|
||||
@@ -378,8 +376,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "ĐĨавŅŅаĐģŅŅĐŗ ŅŅŅĐŗĐ°ĐŊа ŅŅ",
|
||||
"prompt.action.send": "ĐĐģĐŗŅŅŅ
",
|
||||
"prompt.action.stop": "ĐĐžĐŗŅ",
|
||||
"prompt.toast.pasteUnsupported.title": "ĐŅĐŧĐļĐ¸ĐŗĐ´ŅŅĐŗŌ¯Đš Ņ
авŅŅаĐģŅ",
|
||||
"prompt.toast.pasteUnsupported.description": "ĐĐŊĐ´ ĐˇĶŠĐ˛Ņ
ĶŠĐŊ СŅŅĐ°Đŗ, PDFŅ, ŅŅвŅĐģ ŅĐĩĐēŅŅ ŅаКĐģŅĐŗ Ņ
авŅаŅĐŗĐ°Ņ
йОĐģĐžĐŧĐļŅОК.",
|
||||
"prompt.toast.attachmentDuplicate.title": "ĐĐŊŅ ŅаКĐģŅĐŗ аĐģŅ Ņ
ŅдиКĐŊ йаКŅŅŅŅĐģŅаĐŊ йаКĐŊа",
|
||||
"prompt.toast.modelAgentRequired.title": "ĐĐŗĐĩĐŊŅ ĐąĐžĐģĐžĐŊ ĐˇĐ°ĐŗĐ˛Đ°ŅŅĐŗ ŅĐžĐŊĐŗĐžĐŊĐž ŅŅ",
|
||||
"prompt.toast.modelAgentRequired.description": "ĐŅĐžĐŧĐŋŅ Đ¸ĐģĐŗŅŅŅ
ŅŅŅŅŅ ĶŠĐŧĐŊĶŠ Đ°ĐŗĐĩĐŊŅ ĐąĐžĐģĐžĐŊ ĐˇĐ°ĐŗĐ˛Đ°ŅŅĐŗ ŅĐžĐŊĐŗĐžĐŊĐž ŅŅ.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Model terpilih termasuk Claude, GPT, Gemini dan banyak lagi",
|
||||
"dialog.provider.opencode.tagline": "Model dioptimumkan yang boleh dipercayai",
|
||||
"dialog.provider.opencodeGo.tagline": "Langganan kos rendah untuk semua",
|
||||
"dialog.provider.anthropic.note": "Akses terus ke model Claude, termasuk Pro dan Max",
|
||||
"dialog.provider.copilot.note": "Model AI untuk bantuan pengekodan melalui GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Model GPT untuk tugasan AI umum yang pantas dan berkeupayaan",
|
||||
"dialog.provider.google.note": "Model Gemini untuk respons pantas dan berstruktur",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Lihat 70+ lagi penyedia",
|
||||
"dialog.provider.viewAll": "Tunjukkan lebih banyak penyedia",
|
||||
"provider.connect.title": "Sambungkan {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Log masuk dengan Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Pilih kaedah log masuk untuk {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Kunci API",
|
||||
"provider.connect.method.browser": "Pelayar",
|
||||
@@ -375,8 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Buang lampiran",
|
||||
"prompt.action.send": "Hantar",
|
||||
"prompt.action.stop": "Henti",
|
||||
"prompt.toast.pasteUnsupported.title": "Lampiran tidak disokong",
|
||||
"prompt.toast.pasteUnsupported.description": "Hanya imej, PDF, atau fail teks boleh dilampirkan di sini.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Fail ini telah dimuat naik",
|
||||
"prompt.toast.modelAgentRequired.title": "Pilih ejen dan model",
|
||||
"prompt.toast.modelAgentRequired.description": "Pilih ejen dan model sebelum menghantar prompt.",
|
||||
|
||||
@@ -193,7 +193,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Claudeá GPTá Gemini áážááˇáē áĄááŧáŦá¸áĄááŦááģáŦḠáĄááĢáĄáááē ááŊáąá¸ááģááēááŦá¸ááąáŦ ááąáŦáēáááēááģáŦá¸",
|
||||
"dialog.provider.opencode.tagline": "áá¯áļááŧááēá
áááēááģáááąáŦ áĄááąáŦááēá¸áá¯áļá¸ááŧááēáááēááŦá¸ááąáŦ ááąáŦáēáááēááģáŦá¸",
|
||||
"dialog.provider.opencodeGo.tagline": "áá°ááá¯ááēá¸áĄááŊááē áá¯ááēááģá
ááááēáááēááŦááąáŦ á
áŦáááēá¸ááŊááēá¸áážá¯",
|
||||
"dialog.provider.anthropic.note": "Pro áážááˇáē Max áĄááĢáĄáááē Claude ááąáŦáēáááēááģáŦá¸ááá¯áˇ ááá¯ááēááá¯ááēáááēááąáŦááēááŊááˇáē",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot áážáá
áēáááˇáē áá¯ááēááąá¸áááēá¸áĄáá°áĄááŽáĄááŊááē AI ááąáŦáēáááēááģáŦá¸",
|
||||
"dialog.provider.openai.note": "ááģááēááŧááēááŧáŽá¸ áá¯ááēááąáŦááēááá¯ááēááąáŦ áĄááŊáąááŊáą AI áá¯ááēááąáŦááēááģááēááģáŦá¸áĄááŊááē GPT ááąáŦáēáááēááģáŦá¸",
|
||||
"dialog.provider.google.note": "ááŧááēáááēááŧáŽá¸ ááŊá˛áˇá
ááēá¸áááēááąáŦááēááŦá¸ááąáŦ áá¯áļáˇááŧááēáážá¯ááģáŦá¸áĄááŊááē Gemini ááąáŦáēáááēááģáŦá¸",
|
||||
@@ -210,7 +209,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ááąáŦááēáááēááļáˇááá¯á¸ááąá¸áá° 70+ ááá¯ááŧááˇáēááĢá",
|
||||
"dialog.provider.viewAll": "ááąáŦááēáááēáááēááąáŦááēáážá¯ááąá¸áá°ááģáŦá¸ááᯠááŧááĢá",
|
||||
"provider.connect.title": "{{provider}} ááᯠááģáááēáááēááĢá",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max ááŧááˇáē áááēááąáŦááēááĢá",
|
||||
"provider.connect.selectMethod": "{{provider}} áĄááŊááē áĄááąáŦááˇáēáááēáááēá¸áááēá¸ááᯠááŊáąá¸ááĢá",
|
||||
"provider.connect.method.apiKey": "API ááąáŦáˇ",
|
||||
"provider.connect.method.browser": "áááąáŦááēááŦ",
|
||||
@@ -378,9 +376,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "áá°á¸ááŊá˛ááĢááá¯ááēááᯠáááēáážáŦá¸ááĢá",
|
||||
"prompt.action.send": "ááá¯áˇááĢá",
|
||||
"prompt.action.stop": "áááēááĢá",
|
||||
"prompt.toast.pasteUnsupported.title": "áá°á¸ááŊá˛ááĢááá¯ááēááᯠááļáˇááá¯á¸áááŦá¸ááĢá",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"áá¯ááēáá¯áļááģáŦá¸á PDF ááģáŦḠááá¯áˇááá¯ááē á
áŦááŦá¸ááá¯ááēááģáŦá¸ááá¯ááŦ á¤ááąááŦááŊááē áá°á¸ááŊá˛ááá¯ááēááĢáááēá",
|
||||
"prompt.toast.attachmentDuplicate.title": "á¤ááá¯ááēááᯠáĄááēáá¯ááēáá¯ááēááŧáŽá¸ááĢááŧáŽá",
|
||||
"prompt.toast.modelAgentRequired.title": "áĄáąá¸ááģááˇáēáážááˇáē ááąáŦáēáááēááᯠááŊáąá¸ááĢá",
|
||||
"prompt.toast.modelAgentRequired.description": "Prompt áááá¯áˇáᎠáĄáąá¸ááģááˇáēáážááˇáē ááąáŦáēáááēááᯠááŊáąá¸ááĢá",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini ⤰ ā¤Ĩā¤Ē ā¤¸ā¤šā¤ŋ⤤ ā¤āĨ⤝āĨ⤰āĨ⤠ā¤ā¤°ā¤ŋā¤ā¤ā¤ž ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨ",
|
||||
"dialog.provider.opencode.tagline": "ā¤ā¤°ā¤Ē⤰āĨā¤ĻāĨ ā¤
⤍āĨā¤āĨ⤞ā¤ŋ⤤ ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨ",
|
||||
"dialog.provider.opencodeGo.tagline": "⤏ā¤ŦāĨā¤āĨ ā¤˛ā¤žā¤ā¤ŋ ā¤ā¤Ž ā¤˛ā¤žā¤ā¤¤ ⤏ā¤Ļ⤏āĨā¤¯ā¤¤ā¤ž",
|
||||
"dialog.provider.anthropic.note": "ā¤ĒāĨ⤰āĨ ⤰ ā¤ŽāĨā¤¯ā¤žā¤āĨ⤏ ā¤¸ā¤šā¤ŋ⤤ Claude ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨā¤Žā¤ž ā¤ĒāĨ⤰⤤āĨ⤝ā¤āĨ⤎ ā¤Ēā¤šāĨā¤ā¤",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot ā¤Žā¤žā¤°āĨā¤Ģ⤤ ā¤āĨā¤Ąā¤ŋā¤ ā¤¸ā¤šā¤žā¤¯ā¤¤ā¤žā¤ā¤ž ā¤˛ā¤žā¤ā¤ŋ AI ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨ",
|
||||
"dialog.provider.openai.note": "ā¤ā¤ŋā¤āĨ, ⤏ā¤āĨā¤ˇā¤Ž ā¤¸ā¤žā¤Žā¤žā¤¨āĨ⤝ AI ā¤ā¤žā¤°āĨā¤¯ā¤šā¤°āĨā¤āĨ ā¤˛ā¤žā¤ā¤ŋ GPT ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨ",
|
||||
"dialog.provider.google.note": "ā¤ā¤ŋā¤āĨ, ⤏ā¤ā¤°ā¤ā¤ŋ⤤ ā¤ĒāĨ⤰⤤ā¤ŋā¤āĨ⤰ā¤ŋā¤¯ā¤žā¤šā¤°āĨā¤āĨ ā¤˛ā¤žā¤ā¤ŋ Gemini ā¤ŽāĨā¤ĄāĨā¤˛ā¤šā¤°āĨ",
|
||||
@@ -209,7 +208,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70+ ā¤Ĩā¤Ē ā¤ĒāĨ⤰ā¤Ļā¤žā¤¯ā¤ā¤šā¤°āĨ ā¤šāĨ⤰āĨ⤍āĨā¤šāĨ⤏āĨ",
|
||||
"dialog.provider.viewAll": "ā¤Ĩā¤Ē ā¤ĒāĨ⤰ā¤Ļā¤žā¤¯ā¤ā¤šā¤°āĨ ā¤ĻāĨā¤ā¤žā¤ā¤¨āĨā¤šāĨ⤏āĨ",
|
||||
"provider.connect.title": "ā¤ā¤Ąā¤žā¤¨ ā¤ā¤°āĨ⤍āĨā¤šāĨ⤏āĨ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max ⤏ā¤ā¤ ⤞ā¤ā¤ā¤¨ ā¤ā¤°āĨ⤍āĨā¤šāĨ⤏āĨ",
|
||||
"provider.connect.selectMethod": "{{provider}} ā¤ā¤ž ā¤˛ā¤žā¤ā¤ŋ ⤞ā¤ā¤ā¤¨ ā¤ĩā¤ŋ⤧ā¤ŋ ā¤ā¤¯ā¤¨ ā¤ā¤°āĨ⤍āĨā¤šāĨ⤏āĨāĨ¤",
|
||||
"provider.connect.method.apiKey": "API ā¤āĨā¤āĨā¤āĨ",
|
||||
"provider.connect.method.browser": "ā¤ŦāĨā¤°ā¤žā¤ā¤ā¤°",
|
||||
@@ -376,8 +374,6 @@ export const dict: Record<string, string> = {
|
||||
"prompt.attachment.remove": "⤏ā¤ā¤˛ā¤āĨā¤¨ā¤ ā¤šā¤ā¤žā¤ā¤¨āĨā¤šāĨ⤏āĨ",
|
||||
"prompt.action.send": "ā¤Ēā¤ ā¤žā¤ā¤¨āĨā¤šāĨ⤏āĨ",
|
||||
"prompt.action.stop": "⤰āĨā¤āĨ⤍āĨā¤šāĨ⤏āĨ",
|
||||
"prompt.toast.pasteUnsupported.title": "ā¤
ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ⤏ā¤ā¤˛ā¤āĨ⤍ā¤",
|
||||
"prompt.toast.pasteUnsupported.description": "ā¤āĨā¤ĩ⤞ ā¤ā¤ĩā¤ŋā¤šā¤°āĨ, PDF ā¤šā¤°āĨ, ā¤ĩā¤ž ā¤Ēā¤žā¤ ā¤Ģā¤žā¤ā¤˛ā¤šā¤°āĨ ā¤¯ā¤šā¤žā¤ ā¤¸ā¤ā¤˛ā¤āĨ⤍ ā¤ā¤°āĨ⤍ ⤏ā¤ā¤ŋ⤍āĨā¤āĨ¤",
|
||||
"prompt.toast.attachmentDuplicate.title": "⤝āĨ ā¤Ģā¤žā¤ā¤˛ ā¤Ēā¤šā¤ŋ⤞āĨ ⤍āĨ ā¤
ā¤Ē⤞āĨā¤Ą ā¤ā¤°ā¤ŋā¤ā¤āĨ ā¤",
|
||||
"prompt.toast.modelAgentRequired.title": "ā¤ā¤ ā¤ā¤āĨ⤍āĨ⤠⤰ ā¤ŽāĨā¤ĄāĨ⤞ ā¤ā¤¯ā¤¨ ā¤ā¤°āĨ⤍āĨā¤šāĨ⤏āĨ",
|
||||
"prompt.toast.modelAgentRequired.description": "ā¤ĒāĨā¤°ā¤ŽāĨā¤ĒāĨ⤠ā¤Ēā¤ ā¤žā¤ā¤¨āĨ ā¤
ā¤ā¤ŋ ā¤ā¤āĨ⤍āĨ⤠⤰ ā¤ŽāĨā¤ĄāĨ⤞ ā¤ā¤žā¤¨āĨ⤍āĨā¤šāĨ⤏āĨāĨ¤",
|
||||
|
||||
@@ -192,7 +192,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Samengestelde modellen, waaronder Claude, GPT, Gemini en meer",
|
||||
"dialog.provider.opencode.tagline": "Betrouwbare geoptimaliseerde modellen",
|
||||
"dialog.provider.opencodeGo.tagline": "Goedkoop abonnement voor iedereen",
|
||||
"dialog.provider.anthropic.note": "Directe toegang tot Claude-modellen, inclusief Pro en Max",
|
||||
"dialog.provider.copilot.note": "AI-modellen voor codeerondersteuning via GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT-modellen voor snelle, capabele algemene AI-taken",
|
||||
"dialog.provider.google.note": "Gemini-modellen voor snelle, gestructureerde reacties",
|
||||
@@ -209,7 +208,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Bekijk nog 70+ aanbieders",
|
||||
"dialog.provider.viewAll": "Toon meer aanbieders",
|
||||
"provider.connect.title": "Verbinding maken met {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Inloggen met Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Selecteer de aanmeldingsmethode voor {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API-sleutel",
|
||||
"provider.connect.method.browser": "Browser",
|
||||
@@ -375,9 +373,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Bijlage verwijderen",
|
||||
"prompt.action.send": "Verzenden",
|
||||
"prompt.action.stop": "Stop",
|
||||
"prompt.toast.pasteUnsupported.title": "Niet-ondersteunde bijlage",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"Hier kunnen alleen afbeeldingen, pdf's of tekstbestanden worden bijgevoegd.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Dit bestand is al geÃŧpload",
|
||||
"prompt.toast.modelAgentRequired.title": "Selecteer een agent en model",
|
||||
"prompt.toast.modelAgentRequired.description": "Kies een agent en model voordat je een prompt verzendt.",
|
||||
|
||||
@@ -206,7 +206,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Utvalgte modeller inkludert Claude, GPT, Gemini og mer",
|
||||
"dialog.provider.opencode.tagline": "PÃĨlitelige, optimaliserte modeller",
|
||||
"dialog.provider.opencodeGo.tagline": "Rimelig abonnement for alle",
|
||||
"dialog.provider.anthropic.note": "Direkte tilgang til Claude-modeller, inkludert Pro og Max",
|
||||
"dialog.provider.copilot.note": "AI-modeller for kodeassistanse via GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT-modeller for raske, dyktige generelle AI-oppgaver",
|
||||
"dialog.provider.google.note": "Gemini-modeller for raske, strukturerte svar",
|
||||
@@ -227,7 +226,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "Vis flere leverandører",
|
||||
|
||||
"provider.connect.title": "Koble til {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Logg inn med Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Velg innloggingsmetode for {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API-nøkkel",
|
||||
"provider.connect.method.browser": "Nettleser",
|
||||
@@ -393,9 +391,7 @@ export const dict = {
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stopp",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "Ikke støttet vedlegg",
|
||||
"prompt.toast.attachmentDuplicate.title": "Denne filen er allerede lastet opp",
|
||||
"prompt.toast.pasteUnsupported.description": "Kun bilder, PDF-er eller tekstfiler kan legges ved her.",
|
||||
"prompt.toast.modelAgentRequired.title": "Velg en agent og modell",
|
||||
"prompt.toast.modelAgentRequired.description": "Velg en agent og modell før du sender en forespørsel.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Kunne ikke opprette worktree",
|
||||
|
||||
@@ -198,7 +198,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ClaudeØ GPTØ Gemini ØĒÛ ÛŲØą ØŗŲ
ÛØĒ ÚŠÛŲØąÛŲšÚ Ų
اÚŲ",
|
||||
"dialog.provider.opencode.tagline": "ŲØ§Ø¨Ų اؚØĒŲ
اد اØĩŲØ§Ø Ø´Ø¯Û Ų
اÚŲ",
|
||||
"dialog.provider.opencodeGo.tagline": "ÛØą Ø§ÚŠ ŲØĻÛ Ú¯ÚžŲš ŲÛŲ
ØĒ Ø¯Û ØąÚŠŲÛØĒ",
|
||||
"dialog.provider.anthropic.note": "ŲžØąŲ ØĒÛ Ų
ÛÚŠØŗ ØŗŲ
ÛØĒ Claude Ų
اÚŲ ØĒÚŠ Ø¨ØąØ§Û ØąØ§ØŗØĒ ØąØŗØ§ØĻÛ",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot Ø¯Û Ø°ØąÛØšÛ ÚŠŲÚŲÚ¯ Ø¯Û Ų
دد ŲØĻÛ Ø§Û ØĸØĻÛ Ų
اÚŲ",
|
||||
"dialog.provider.openai.note": "ØĒÛØ˛ ØĒÛ ØĩŲØ§ØÛØĒ ŲØ§ŲÛ ØšØ§Ų
AI ÚŠŲ
اÚē ŲØĻÛ GPT Ų
اÚŲ",
|
||||
"dialog.provider.google.note": "ØĒÛØ˛ ØĒÛ Ų
ŲØ¸Ų
ØŦŲØ§Ø¨Ø§Úē ŲØĻÛ Gemini Ų
اÚŲ",
|
||||
@@ -215,7 +214,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "70+ ÛŲØą ŲžØąŲŲØ§ØĻÛÚØą ŲÛÚŠÚžŲ",
|
||||
"dialog.provider.viewAll": "ÛŲØą ŲžØąŲŲØ§ØĻÛÚØą Ųڊڞاؤ",
|
||||
"provider.connect.title": "{{provider}} ŲØ§Ų ØŦÚŲ",
|
||||
"provider.connect.title.anthropicProMax": "Claude ŲžØąŲ/Ų
ÛÚŠØŗ Ø¯Û ŲØ§Ų ŲØ§Ú¯ Ø§Ų ÚŠØąŲ",
|
||||
"provider.connect.selectMethod": "{{provider}} ŲØĻÛ ŲØ§Ú¯ Ø§Ų Ø¯Ø§ ØˇØąÛŲÛ Ų
ŲØĒØŽØ¨ ÚŠØąŲÛ",
|
||||
"provider.connect.method.apiKey": "API ÚŠŲÛØ¯",
|
||||
"provider.connect.method.browser": "Ø¨ØąØ§Ø¤Ø˛Øą",
|
||||
@@ -381,9 +379,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "Ų
ŲØŗŲÚŠÛ ÛŲšØ§ دÛŲ",
|
||||
"prompt.action.send": "Ú¯ÚžŲŲ",
|
||||
"prompt.action.stop": "ØąŲÚŠŲ",
|
||||
"prompt.toast.pasteUnsupported.title": "ØēÛØą ØĒØšØ§ŲŲ ÛØ§ŲØĒÛ Ų
ŲØŗŲÚŠÛ",
|
||||
"prompt.toast.pasteUnsupported.description":
|
||||
"Ø§ÛØĒÚžÛ ØĩØąŲ ØĒØĩŲÛØąØ§ÚēØ ŲžÛ ÚÛ Ø§ÛŲØ ÛØ§ ŲšÛÚŠØŗŲš ŲØ§ØĻŲØ§Úē Ų
ŲØŗŲÚŠ ÚŠÛØĒÛØ§Úē ØŦا ØŗÚŠØ¯ÛØ§Úē ŲÛÚēÛ",
|
||||
"prompt.toast.attachmentDuplicate.title": "اÛÛÛ ŲØ§ØĻŲ ŲžÛŲÛ Ø§Û Ø§Ųž ŲŲÚ ÛŲ ÚÚŠÛ Ø§Û",
|
||||
"prompt.toast.modelAgentRequired.title": "اڊ Ø§ÛØŦŲŲš ØĒÛ Ų
اÚŲ ÚŲŲ",
|
||||
"prompt.toast.modelAgentRequired.description": "ŲžØąØ§Ų
ŲžŲš Ø¨ÚžÛØŦŲ ØĒŲÚē ŲžÛŲØ§Úē Ø§ÚŠ Ø§ÛØŦŲŲš ØĒÛ Ų
اÚŲ Ø¯Ø§ Ø§ŲØĒØŽØ§Ø¨ ÚŠØąŲÛ",
|
||||
|
||||
@@ -199,7 +199,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Starannie dobrane modele, w tym Claude, GPT, Gemini i inne",
|
||||
"dialog.provider.opencode.tagline": "Niezawodne, zoptymalizowane modele",
|
||||
"dialog.provider.opencodeGo.tagline": "Niedroga subskrypcja dla kaÅŧdego",
|
||||
"dialog.provider.anthropic.note": "BezpoÅredni dostÄp do modeli Claude, w tym Pro i Max",
|
||||
"dialog.provider.copilot.note": "Modele AI do pomocy w kodowaniu przez GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Modele GPT do szybkich i wszechstronnych zadaÅ AI",
|
||||
"dialog.provider.google.note": "Modele Gemini do szybkich i ustrukturyzowanych odpowiedzi",
|
||||
@@ -216,7 +215,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Zobacz ponad 70 innych dostawcÃŗw",
|
||||
"dialog.provider.viewAll": "Zobacz wiÄcej dostawcÃŗw",
|
||||
"provider.connect.title": "PoÅÄ
cz {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Zaloguj siÄ za pomocÄ
konta Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Wybierz metodÄ logowania dla {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Klucz API",
|
||||
"provider.connect.method.browser": "PrzeglÄ
darka",
|
||||
@@ -382,9 +380,7 @@ export const dict = {
|
||||
"prompt.attachment.remove": "UsuÅ zaÅÄ
cznik",
|
||||
"prompt.action.send": "WyÅlij",
|
||||
"prompt.action.stop": "Zatrzymaj",
|
||||
"prompt.toast.pasteUnsupported.title": "NieobsÅugiwany zaÅÄ
cznik",
|
||||
"prompt.toast.attachmentDuplicate.title": "Ten plik zostaÅ juÅŧ przesÅany",
|
||||
"prompt.toast.pasteUnsupported.description": "MoÅŧna tutaj zaÅÄ
czaÄ tylko obrazy, pliki PDF lub pliki tekstowe.",
|
||||
"prompt.toast.modelAgentRequired.title": "Wybierz agenta i model",
|
||||
"prompt.toast.modelAgentRequired.description": "Wybierz agenta i model przed wysÅaniem zapytania.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "Nie udaÅo siÄ utworzyÄ drzewa roboczego",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "Modele selectate, inclusiv Claude, GPT, Gemini Či altele",
|
||||
"dialog.provider.opencode.tagline": "Modele optimizate, fiabile",
|
||||
"dialog.provider.opencodeGo.tagline": "Abonament accesibil pentru toČi",
|
||||
"dialog.provider.anthropic.note": "Acces direct la modelele Claude, inclusiv Pro Či Max",
|
||||
"dialog.provider.copilot.note": "Modele AI pentru asistenČÄ la programare prin GitHub Copilot",
|
||||
"dialog.provider.openai.note": "Modele GPT pentru sarcini AI generale rapide Či performante",
|
||||
"dialog.provider.google.note": "Modele Gemini pentru rÄspunsuri rapide Či structurate",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "Vezi peste 70 de furnizori",
|
||||
"dialog.provider.viewAll": "AfiČeazÄ mai mulČi furnizori",
|
||||
"provider.connect.title": "ConecteazÄ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "Autentificare cu Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "SelecteazÄ metoda de autentificare pentru {{provider}}.",
|
||||
"provider.connect.method.apiKey": "Cheie API",
|
||||
"provider.connect.method.browser": "Navigator",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "EliminÄ ataČamentul",
|
||||
"prompt.action.send": "Trimite",
|
||||
"prompt.action.stop": "OpreČte",
|
||||
"prompt.toast.pasteUnsupported.title": "AtaČament neacceptat",
|
||||
"prompt.toast.pasteUnsupported.description": "PoČi ataČa doar imagini, PDF-uri sau fiČiere text aici.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Acest fiČier a fost deja ÃŽncÄrcat",
|
||||
"prompt.toast.modelAgentRequired.title": "SelecteazÄ un agent Či un model",
|
||||
"prompt.toast.modelAgentRequired.description": "Alege un agent Či un model ÃŽnainte de a trimite un prompt.",
|
||||
|
||||
@@ -207,7 +207,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "ĐŅОйŅаĐŊĐŊŅĐĩ ĐŧОдĐĩĐģи, вĐēĐģŅŅĐ°Ņ Claude, GPT, Gemini и Đ´ŅŅĐŗĐ¸Đĩ",
|
||||
"dialog.provider.opencode.tagline": "ĐадŅĐļĐŊŅĐĩ ĐžĐŋŅиĐŧиСиŅОваĐŊĐŊŅĐĩ ĐŧОдĐĩĐģи",
|
||||
"dialog.provider.opencodeGo.tagline": "ĐĐžŅŅŅĐŋĐŊĐ°Ņ ĐŋОдĐŋиŅĐēа Đ´ĐģŅ Đ˛ŅĐĩŅ
",
|
||||
"dialog.provider.anthropic.note": "ĐŅŅĐŧОК Đ´ĐžŅŅŅĐŋ Đē ĐŧОдĐĩĐģŅĐŧ Claude, вĐēĐģŅŅĐ°Ņ Pro и Max",
|
||||
"dialog.provider.copilot.note": "ĐĐ-ĐŧОдĐĩĐģи Đ´ĐģŅ ĐŋĐžĐŧĐžŅи в ĐēОдиŅОваĐŊии ŅĐĩŅĐĩС GitHub Copilot",
|
||||
"dialog.provider.openai.note": "ĐОдĐĩĐģи GPT Đ´ĐģŅ ĐąŅŅŅŅŅŅ
и ĐŧĐžŅĐŊŅŅ
ĐˇĐ°Đ´Đ°Ņ ĐžĐąŅĐĩĐŗĐž ĐĐ",
|
||||
"dialog.provider.google.note": "ĐОдĐĩĐģи Gemini Đ´ĐģŅ ĐąŅŅŅŅŅŅ
и ŅŅŅŅĐēŅŅŅиŅОваĐŊĐŊŅŅ
ĐžŅвĐĩŅОв",
|
||||
@@ -228,7 +227,6 @@ export const dict = {
|
||||
"dialog.provider.viewAll": "ĐĐžĐēаСаŅŅ ĐąĐžĐģŅŅĐĩ ĐŋŅОваКдĐĩŅОв",
|
||||
|
||||
"provider.connect.title": "ĐОдĐēĐģŅŅиŅŅ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "ĐОКŅи Ņ ĐŋĐžĐŧĐžŅŅŅ Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "ĐŅĐąĐĩŅиŅĐĩ ŅĐŋĐžŅОй вŅ
Ода Đ´ĐģŅ {{provider}}.",
|
||||
"provider.connect.method.apiKey": "ĐĐģŅŅ API",
|
||||
"provider.connect.method.browser": "ĐŅаŅСĐĩŅ",
|
||||
@@ -401,9 +399,7 @@ export const dict = {
|
||||
"prompt.action.send": "ĐŅĐŋŅавиŅŅ",
|
||||
"prompt.action.stop": "ĐŅŅаĐŊОвиŅŅ",
|
||||
|
||||
"prompt.toast.pasteUnsupported.title": "ĐĐĩĐŋОддĐĩŅĐļиваĐĩĐŧĐžĐĩ вĐģĐžĐļĐĩĐŊиĐĩ",
|
||||
"prompt.toast.attachmentDuplicate.title": "ĐŅĐžŅ ŅаКĐģ ŅĐļĐĩ ĐˇĐ°ĐŗŅŅĐļĐĩĐŊ",
|
||||
"prompt.toast.pasteUnsupported.description": "ĐĐ´ĐĩŅŅ ĐŧĐžĐļĐŊĐž ĐŋŅиĐēŅĐĩĐŋиŅŅ ŅĐžĐģŅĐēĐž иСОйŅаĐļĐĩĐŊиŅ, PDF иĐģи ŅĐĩĐēŅŅОвŅĐĩ ŅаКĐģŅ.",
|
||||
"prompt.toast.modelAgentRequired.title": "ĐŅĐąĐĩŅиŅĐĩ Đ°ĐŗĐĩĐŊŅа и ĐŧОдĐĩĐģŅ",
|
||||
"prompt.toast.modelAgentRequired.description": "ĐŅĐąĐĩŅиŅĐĩ Đ°ĐŗĐĩĐŊŅа и ĐŧОдĐĩĐģŅ ĐŋĐĩŅĐĩĐ´ ĐžŅĐŋŅавĐēОК СаĐŋŅĐžŅа.",
|
||||
"prompt.toast.worktreeCreateFailed.title": "ĐĐĩ ŅдаĐģĐžŅŅ ŅОСдаŅŅ worktree",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.provider.opencode.note": "Claude, GPT, Gemini āˇāˇ āļāˇāļ⎠āļ¯āˇ āļāļāˇāˇ
āˇāˇ āˇāļāˇāˇāļ āļāļāˇāļāˇ",
|
||||
"dialog.provider.opencode.tagline": "āˇāˇāˇāˇāˇāˇāļąāˇāļē āļ´āˇāļģāˇāˇāˇāļ āļ¸āˇāļ¯āˇāļŊāˇ",
|
||||
"dialog.provider.opencodeGo.tagline": "āˇāˇāļ¸ āļāˇāļąāˇāļāˇāļ§āļ¸ āļ
āļŠāˇ āˇāˇāļēāļ¯āļ¸āˇ āļ¯āˇāļēāļāļāˇāˇāļēāļāˇ",
|
||||
"dialog.provider.anthropic.note": "Pro āˇāˇ Max āļāļāˇāˇ
āˇāˇ Claude āļ¸āˇāļ¯āˇāļŊ⎠āˇāˇāļ āˇāˇāļĸ⎠āļ´āˇâāļģāˇāˇāˇāļē",
|
||||
"dialog.provider.copilot.note": "GitHub Copilot āˇāļģāˇāˇ āļāˇāļāˇāļāļģāļĢ āˇāˇāˇāļē āˇāļŗāˇāˇ AI āļāļāˇāļāˇ",
|
||||
"dialog.provider.openai.note": "āˇāˇāļāˇāļāˇ, āˇāˇāļāˇāļēāˇāˇ āļāļ⎠āˇāˇāļ¸āˇāļąāˇâāļē AI āļāˇāļģāˇāļēāļēāļąāˇ āˇāļŗāˇāˇ GPT āļ¸āˇāļ¯āˇāļŊāˇ",
|
||||
"dialog.provider.google.note": "āˇāˇāļāˇāļāˇ, āˇāˇâāļēāˇāˇāļāļ āļ´āˇâāļģāļāˇāļ āˇāļģ āˇāļŗāˇāˇ Gemini āļāļāˇāļāˇ",
|
||||
@@ -208,7 +207,6 @@ export const dict: Record<string, string> = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "āļāˇāļ⎠āˇāļ´āļēāļąāˇāļąāļąāˇ 70+ āļļāļŊāļąāˇāļą",
|
||||
"dialog.provider.viewAll": "āļāˇāļ⎠āˇāļ´āļēāļąāˇāļąāļąāˇ āļ´āˇāļąāˇāˇāļąāˇāļą",
|
||||
"provider.connect.title": "{{provider}} āˇāļ¸āˇāļļāļąāˇāļ° āļāļģāļąāˇāļą",
|
||||
"provider.connect.title.anthropicProMax": "Claude Pro/Max āˇāļ¸āļāˇāļąāˇ āļ´āˇāļģāļąāļē āˇāļąāˇāļą",
|
||||
"provider.connect.selectMethod": "{{provider}} āˇāļŗāˇāˇ āļ´āˇāˇāˇāˇāˇāļ¸āˇ āļāˇâāļģāļ¸āļē āļāˇāļģāļąāˇāļą.",
|
||||
"provider.connect.method.apiKey": "API āļēāļāˇāļģ",
|
||||
"provider.connect.method.browser": "āļļāˇāļģāˇāˇāˇāļģāļē",
|
||||
@@ -374,8 +372,6 @@ export const dict: Record<string, string> = {
|
||||
"prompt.attachment.remove": "āļāļ¸āˇāļĢāˇāļ¸ āļāˇāļ⎠āļāļģāļąāˇāļą",
|
||||
"prompt.action.send": "āļēāˇāļąāˇāļą",
|
||||
"prompt.action.stop": "āļąāˇāļāˇāˇāļąāˇāļą",
|
||||
"prompt.toast.pasteUnsupported.title": "āˇāˇāˇāļē āļąāˇāļ¯āļāˇāˇāļą āļāļ¸āˇāļĢāˇāļ¸",
|
||||
"prompt.toast.pasteUnsupported.description": "āļ´āˇāļąāˇāļāˇāļģ, PDF āˇāˇ āļ´āˇāˇ
āļāˇāļąāˇ āļ´āļ¸āļĢāļ⎠āļ¸āˇāˇāˇ āļāļ¸āˇāļĢāˇāļē āˇāˇāļ.",
|
||||
"prompt.toast.attachmentDuplicate.title": "āļ¸āˇāļ¸ āļāˇāļąāˇāˇ āļ¯āˇāļąāļ§āļ¸āļ⎠āļāļŠāˇāļāļ āļāļģ āļāļ",
|
||||
"prompt.toast.modelAgentRequired.title": "āļąāˇāļēāˇāļĸāˇāļāļēāˇāļ⎠āˇāˇ āļāļāˇāļāˇāļēāļ⎠āļāˇāļģāļąāˇāļą",
|
||||
"prompt.toast.modelAgentRequired.description": "āļ´āˇâāļģāˇāļ¸āˇāļ´āˇāļ§āˇ āļāļāļ⎠āļēāˇāˇāˇāļ¸āļ§ āļ´āˇāļģ āļąāˇāļēāˇāļĸāˇāļāļēāˇāļ⎠āˇāˇ āļāļāˇāļāˇāļēāļ⎠āļāˇāļģāļąāˇāļą.",
|
||||
|
||||
@@ -191,7 +191,6 @@ export const dict = {
|
||||
"dialog.provider.opencode.note": "VybranÊ modely vrÃĄtane Claude, GPT, Gemini a ÄalÅĄÃch",
|
||||
"dialog.provider.opencode.tagline": "SpoĞahlivÊ optimalizovanÊ modely",
|
||||
"dialog.provider.opencodeGo.tagline": "NÃzkonÃĄkladovÊ predplatnÊ pre kaÅždÊho",
|
||||
"dialog.provider.anthropic.note": "Priamy prÃstup k modelom Claude vrÃĄtane Pro a Max",
|
||||
"dialog.provider.copilot.note": "AI modely na asistenciu pri kÃŗdovanà cez GitHub Copilot",
|
||||
"dialog.provider.openai.note": "GPT modely na rÃŊchle a vÃŊkonnÊ vÅĄeobecnÊ AI Ãēlohy",
|
||||
"dialog.provider.google.note": "Gemini modely na rÃŊchle, ÅĄtruktÃērovanÊ odpovede",
|
||||
@@ -208,7 +207,6 @@ export const dict = {
|
||||
"dialog.model.unpaid.viewMoreProviders": "ZobraziÅĨ viac ako 70 ÄalÅĄÃch poskytovateÄžov",
|
||||
"dialog.provider.viewAll": "ZobraziÅĨ viac poskytovateÄžov",
|
||||
"provider.connect.title": "PripojiÅĨ {{provider}}",
|
||||
"provider.connect.title.anthropicProMax": "PrihlÃĄsiÅĨ sa cez Claude Pro/Max",
|
||||
"provider.connect.selectMethod": "Vyberte spôsob prihlÃĄsenia pre {{provider}}.",
|
||||
"provider.connect.method.apiKey": "API kÄžÃēÄ",
|
||||
"provider.connect.method.browser": "PrehliadaÄ",
|
||||
@@ -374,8 +372,6 @@ export const dict = {
|
||||
"prompt.attachment.remove": "OdstrÃĄniÅĨ prÃlohu",
|
||||
"prompt.action.send": "OdoslaÅĨ",
|
||||
"prompt.action.stop": "ZastaviÅĨ",
|
||||
"prompt.toast.pasteUnsupported.title": "NepodporovanÃĄ prÃloha",
|
||||
"prompt.toast.pasteUnsupported.description": "PripojiÅĨ moÅžno len obrÃĄzky, PDF alebo textovÊ sÃēbory.",
|
||||
"prompt.toast.attachmentDuplicate.title": "Tento sÃēbor uÅž bol nahranÃŊ",
|
||||
"prompt.toast.modelAgentRequired.title": "Vyberte agenta a model",
|
||||
"prompt.toast.modelAgentRequired.description": "Pred odoslanÃm vÃŊzvy vyberte agenta a model.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user