Compare commits

..

90 Commits

Author SHA1 Message Date
Aiden Cline 9c0fa96ef0 refactor(core): clean up compaction flow 2026-07-09 19:19:08 -05:00
Aiden Cline 2db7ccb453 fix(core): restore resilient compaction (#36163) 2026-07-09 19:13:11 -05:00
Aiden Cline 629b304c48 fix(core): continue after configured permission denial (#36155) 2026-07-09 17:35:25 -05:00
Dax Raad 1004fbb2f5 docs: expand OpenCode 2 beta guides 2026-07-09 18:01:15 -04:00
opencode-agent[bot] a7746379d5 chore: merge dev into v2 (#36144)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: James Long <jlongster@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-07-09 16:32:44 -05:00
Kit Langton a72992e00f feat(core): session.pending.list API with pending-only session_pending storage (#36126) 2026-07-09 17:25:40 -04:00
James Long 99156c10e6 feat(cli): export frontend logs over OTLP (#36152) 2026-07-09 17:19:22 -04:00
Dax Raad f6f0a5c10f docs: expand user guides 2026-07-09 16:57:13 -04:00
Dax Raad e2eed76101 refactor(tui): migrate v2 calls to new client 2026-07-09 16:57:13 -04:00
Kit Langton 9ff7ef3fb0 fix(tui): handle session search fetch failures without crashing (#36149) 2026-07-09 16:38:58 -04:00
Dax Raad 8fe78c8f8d fix(core): restore plugins after failed activation 2026-07-09 16:28:22 -04:00
Harsh Mathur bd0dffd781 fix(llm): preserve nested OpenAI stream errors (#36130)
Co-authored-by: Harsh Mathur <jeevesforharsh@gmail.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-07-09 14:40:28 -05:00
Dax Raad 0ac8f91ed6 fix(core): restore catalog status schema export 2026-07-09 14:20:36 -04:00
Dax Raad 4c80c23e11 fix(tui): reconcile refreshed messages 2026-07-09 14:12:02 -04:00
Dax Raad 33c0cc2bb6 fix: synchronize remote data refreshes 2026-07-09 13:56:28 -04:00
Dax Raad cc686ab8f6 fix(cli): unify server resolution 2026-07-09 13:28:01 -04:00
Kit Langton 6524dfc818 feat(core): resume suspended sessions after service restart (#36105) 2026-07-09 13:19:15 -04:00
Dax Raad c91604d519 chore(opencode): suppress dead v1 model types 2026-07-09 13:15:20 -04:00
Dax Raad a2718177be test(core): align models dev snapshot fixtures 2026-07-09 13:15:20 -04:00
Dax Raad 6fb526741b refactor(tui): rename link concept to pair 2026-07-09 13:15:20 -04:00
Dax Raad 85fdb3bde6 refactor(core): optimize location startup 2026-07-09 13:15:20 -04:00
James Long b642f9c7bc feat(tui): log event stream connections (#35973) 2026-07-09 13:13:34 -04:00
James Long 855a9569b5 feat(simulation): add literal screen text matching (#36085) 2026-07-09 10:50:35 -04:00
Kit Langton aa0e626b12 fix(core): defer permission rejection cleanup (#36067) 2026-07-09 09:49:14 -04:00
Aiden Cline 389c866cac docs(codemode): add interpreter doc (#36026) 2026-07-09 00:42:48 -05:00
Aiden Cline 7feefb697f refactor: remove todo tool (#35989) 2026-07-09 00:13:48 -05:00
Dax Raad d4155f2906 refactor(cli): simplify mini arguments 2026-07-08 23:28:40 -04:00
Dax Raad 67f2393f83 refactor(cli): parse variants from model refs 2026-07-08 23:25:37 -04:00
Dax Raad cccd013801 refactor(cli): centralize server resolution 2026-07-08 22:10:30 -04:00
Dax Raad be7a684791 refactor(cli): unify server endpoint handling 2026-07-08 22:10:30 -04:00
Kit Langton 984cab7938 feat(core): generalize session input inbox (#36005) 2026-07-08 22:07:45 -04:00
Aiden Cline 7eea97184a fix(tui): restore prompt after undo (#35987) 2026-07-08 19:54:29 -05:00
Kit Langton 0590819090 fix(server): absorb healthy event bursts 2026-07-08 20:33:28 -04:00
opencode-agent[bot] 687dbba6a3 chore: merge dev into v2 (#35962)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: runvip <164729189+runvip@users.noreply.github.com>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Julian Coy <julian@ex-machina.co>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: James Long <jlongster@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-07-08 17:58:55 -05:00
Dax Raad 2347f4d7b4 fix(tui): simplify server link title 2026-07-08 18:47:48 -04:00
Dax Raad 7698a5e6ac feat: add server link sharing 2026-07-08 18:42:54 -04:00
Aiden Cline ec8eea7cbe test(codemode): add Test262 array coverage (#35965) 2026-07-08 17:26:11 -05:00
Aiden Cline b44a981eef feat(core): global form support (#35959) 2026-07-08 17:25:34 -05:00
James Long 79415f625a feat(tui): revamp session export (#35971) 2026-07-08 18:14:43 -04:00
Simon Klee 19f42f7102 feat(v2/cli): add console login (#35969) 2026-07-09 00:00:09 +02:00
Aiden Cline 212c9f99ee test(codemode): add Test262 string coverage (#35940) 2026-07-08 15:51:54 -05:00
James Long 99e52303e6 feat(core): discover ecosystem skill directories (#35956) 2026-07-08 16:34:41 -04:00
Dax Raad 88925ec6e6 fix(tui): allow callers without log sink 2026-07-08 16:00:52 -04:00
Dax Raad cedf365674 feat(tui): add structured logging 2026-07-08 16:00:07 -04:00
Kit Langton 5b39972947 fix(core): await initial plugin readiness (#35755) 2026-07-08 15:12:45 -04:00
James Long 09553d46e0 refactor(simulation): remove filesystem layers (#35938) 2026-07-08 12:22:04 -04:00
James Long e1d72fa338 fix(simulation): stabilize screenshot artifacts (#35933) 2026-07-08 11:48:53 -04:00
Dax Raad b3cf749a7f fix(tui): show resolved skill name 2026-07-08 11:46:02 -04:00
Dax Raad dd842f9862 fix(cli): elect one service process 2026-07-08 11:46:02 -04:00
Kit Langton 3dd29094f4 feat(http-recorder): sync recorder v0.3 (#35619) 2026-07-08 11:41:59 -04:00
Aiden Cline 05e8d5be73 fix(core): preserve prompt attachment order (#35921) 2026-07-08 10:06:58 -05:00
Dax Raad 8b634e4a58 refactor(tui): simplify client data state 2026-07-08 10:37:48 -04:00
James Long c6156f171c feat(simulation): stream UI recordings (#35909) 2026-07-08 10:33:11 -04:00
Dax Raad 4d2b06f8cf docs: prohibit bypassing git hooks 2026-07-08 10:11:46 -04:00
𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 bf15c97e4b Revert "fix(tui): paginate session history"
This reverts commit cc29f86cdc.
2026-07-08 13:45:20 +00:00
Shoubhit Dash 3cbcd8d727 feat(llm): complete provider package entrypoints (#35907) 2026-07-08 18:41:46 +05:30
Dax Raad cc29f86cdc fix(tui): paginate session history 2026-07-08 01:34:43 -04:00
Dax Raad 86e3828da6 fix(core): watch location only when vcs is present 2026-07-08 00:38:56 -04:00
James Long 8d00a51766 feat(simulation): support multiple tool calls (#35861) 2026-07-08 00:35:18 -04:00
opencode-agent[bot] ed4f833813 fix(core): preserve provider metadata namespaces (#35817)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-07-07 21:45:58 -05:00
opencode-agent[bot] e96c24ce2e test(core): fix mutable plugin fixture (#35837) 2026-07-07 22:22:12 -04:00
Kit Langton ed6ad272ec refactor(schema): apply session review decisions (#35793) 2026-07-07 22:10:11 -04:00
Kit Langton d27746e5b3 test(core): simplify runner test fixtures (#35832) 2026-07-07 21:58:52 -04:00
Kit Langton eb661e6e6d fix(core): settle unadvertised tool calls and repair session hook test contexts 2026-07-07 21:51:23 -04:00
Dax Raad 3379e44abb refactor(core): rename ApplyPatchTool to PatchTool 2026-07-07 21:16:00 -04:00
Dax e1abf59c59 fix(core): provide PluginHooks in plugin test layer (#35811) 2026-07-07 20:03:41 -04:00
Dax 4f976bcf1a feat(plugin): add session request hook (#35794) 2026-07-07 19:56:47 -04:00
Kit Langton e25a724bc2 test(tui): cover patch diff display aliases (#35810) 2026-07-07 19:44:54 -04:00
Kit Langton 521ea87192 fix(core): retain reasoning delta state (#35758) 2026-07-07 19:44:40 -04:00
Aiden Cline 3100701488 fix(tui): restore prompt context usage (#35795) 2026-07-07 17:49:33 -05:00
Kit Langton 9c54291ae8 fix(tui): restore forms on reconnect
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-07 22:47:13 +00:00
Dax Raad 52ad916ba4 fix(tui): restore permissions on reconnect
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-07 22:44:26 +00:00
Dax Raad 5db320b02d refactor: rename apply_patch tool 2026-07-07 16:27:25 -04:00
Aiden Cline 910e37f6d8 fix: update v2 session usage metrics (#35468) 2026-07-07 14:31:54 -05:00
Kit Langton 81f6e06681 feat: enable background subagents by default 2026-07-07 15:18:46 -04:00
James Long da68e2865e feat(tui): wire move session command (#35203) 2026-07-07 14:49:11 -04:00
Aiden Cline f86e1cc1ff fix(codemode): preserve collection value identity (#35776) 2026-07-07 13:40:55 -05:00
Aiden Cline 947bbf9490 feat(mcp): expose resource catalog API (#35773) 2026-07-07 13:34:29 -05:00
James Long 3e57b43a4a feat(session): support admit-only synthetic messages (#35774) 2026-07-07 14:17:20 -04:00
opencode-agent[bot] cb18a42a47 docs(codemode): clarify implicit return guidance (#35763)
Co-authored-by: Kit Langton <kit.langton@gmail.com>
2026-07-07 12:48:05 -05:00
Aiden Cline 1fb2837fd3 feat(codemode): expand numeric standard library (#35749) 2026-07-07 12:11:57 -05:00
Aiden Cline 66a8d830aa feat(core): support MCP resources (#35656) 2026-07-07 11:55:06 -05:00
opencode-agent[bot] d0733f83bf fix(codemode): return final top-level expression (#35759)
Co-authored-by: Kit Langton <kit.langton@gmail.com>
2026-07-07 11:44:50 -05:00
Kit Langton b2ce195bda fix(core): normalize Location.Ref keys in LocationServiceMap (#35757) 2026-07-07 12:25:29 -04:00
James Long 6c065ca6a5 feat(simulation): render driven UI screenshots and videos (#35739) 2026-07-07 12:15:01 -04:00
Dax Raad dd1c007877 docs 2026-07-07 12:08:45 -04:00
Kit Langton 8deb0e5780 fix(core): preserve SDK plugins across location eviction (#35725) 2026-07-07 11:43:40 -04:00
Aiden Cline f488089179 fix: preserve compaction files and classify Z.AI overflow (#35747)
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: 冯基魁 <56265583+fengjikui@users.noreply.github.com>
2026-07-07 09:54:31 -05:00
Kit Langton e3a39b214f fix(core): lower failed reasoning to text (#35735) 2026-07-07 09:49:17 -04:00
James Long b6a2912bb1 refactor(simulation): type RPC request payloads (#35734) 2026-07-07 09:21:19 -04:00
946 changed files with 48181 additions and 26533 deletions
+4
View File
@@ -1,4 +1,5 @@
adamdotdevin adamdotdevin
arvsrn
Brendonovich Brendonovich
fwang fwang
Hona Hona
@@ -7,11 +8,14 @@ jayair
jlongster jlongster
kitlangton kitlangton
kommander kommander
ludvigrask
MrMushrooooom MrMushrooooom
nexxeln nexxeln
R44VC0RP R44VC0RP
rekram1-node rekram1-node
thdxr thdxr
simonklee simonklee
Slickstef11
usrnk1
vimtor vimtor
starptech starptech
+4 -2
View File
@@ -19,6 +19,8 @@ Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes a
Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`. Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
Never bypass Git hooks. Do not use `--no-verify` or otherwise disable, skip, or circumvent commit or push hooks. If a hook fails, fix the failure or stop and report it to the user.
## Style Guide ## Style Guide
### General Principles ### General Principles
@@ -152,8 +154,8 @@ const table = sqliteTable("session", {
## V2 Session Core ## V2 Session Core
- Keep durable events minimal: record irreducible new facts and do not repeat state derivable by folding the ordered aggregate history. Enrich projections and read models with previous or derived state when consumers need self-contained views. - Keep durable events minimal: record irreducible new facts and do not repeat state derivable by folding the ordered aggregate history. Enrich projections and read models with previous or derived state when consumers need self-contained views.
- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries. - Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_pending` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries, consuming the pending row in the same event transaction; `session_pending` stores only unconsumed work.
- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry. - Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Retry of an already-promoted input reconciles against the projected message and the durable admitted event rather than a retained row.
- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op. - Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
- 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. - 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 step and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop. - Preserve one explicit `llm.stream(request)` call per step and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
+1 -1
View File
@@ -198,7 +198,7 @@ _Avoid_: Response envelope
- `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected **Session History**; it does not include or represent the complete **Model Context**, whose system text, **Instruction Baseline**, tools, and step-local additions remain separate. - `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected **Session History**; it does not include or represent the complete **Model Context**, whose system text, **Instruction Baseline**, tools, and step-local additions remain separate.
- **Open question**: Should a future, separately named operation expose complete **Model Context**, including the instruction baseline, applied instruction metadata, tools, and step-local additions? - **Open question**: Should a future, separately named operation expose complete **Model Context**, including the instruction baseline, applied instruction metadata, tools, and step-local additions?
- `sessions.prompt(...)` exposes `resume?: boolean`. Omitting it preserves durable admission followed by an advisory execution wake; `resume: false` requests durable admit-only behavior. - `sessions.prompt(...)` exposes `resume?: boolean`. Omitting it preserves durable admission followed by an advisory execution wake; `resume: false` requests durable admit-only behavior.
- The public operation remains `sessions.prompt(...)`; `SessionInput.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics. - The public operation remains `sessions.prompt(...)`; `SessionPending.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics.
- `sessions.create(...)` accepts an optional `location`. Omission resolves through the connected OpenCode instance's default or current location; an explicit value selects a known location. Networked and embedded transports use the same handler semantics. - `sessions.create(...)` accepts an optional `location`. Omission resolves through the connected OpenCode instance's default or current location; an explicit value selects a known location. Networked and embedded transports use the same handler semantics.
- `sessions.switchAgent({ sessionID, agent })` is part of the common client alongside `sessions.switchModel(...)`. It affects subsequent Session activity and fails with `SessionNotFoundError` for an unknown Session. - `sessions.switchAgent({ sessionID, agent })` is part of the common client alongside `sessions.switchModel(...)`. It affects subsequent Session activity and fails with `SessionNotFoundError` for an unknown Session.
- The **Embedded OpenCode** Layer delegates to the same scoped creation path; it does not define a second implementation. - The **Embedded OpenCode** Layer delegates to the same scoped creation path; it does not define a second implementation.
+193 -42
View File
@@ -30,8 +30,9 @@
}, },
"packages/app": { "packages/app": {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0", "@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0", "@dnd-kit/dom": "0.5.0",
"@dnd-kit/helpers": "0.5.0", "@dnd-kit/helpers": "0.5.0",
@@ -73,6 +74,7 @@
"shiki": "catalog:", "shiki": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"solid-list": "catalog:", "solid-list": "catalog:",
"solid-presence": "0.2.0",
"tailwindcss": "catalog:", "tailwindcss": "catalog:",
}, },
"devDependencies": { "devDependencies": {
@@ -94,14 +96,13 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@opencode-ai/cli", "name": "@opencode-ai/cli",
"version": "1.17.14", "version": "1.17.18",
"bin": { "bin": {
"opencode2": "./bin/opencode2.cjs", "opencode2": "./bin/opencode2.cjs",
}, },
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/codemode": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
@@ -115,10 +116,12 @@
"effect": "catalog:", "effect": "catalog:",
"fuzzysort": "catalog:", "fuzzysort": "catalog:",
"jsonc-parser": "3.3.1", "jsonc-parser": "3.3.1",
"open": "10.1.2",
"opentui-spinner": "catalog:", "opentui-spinner": "catalog:",
"semver": "catalog:", "semver": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2", "strip-ansi": "7.1.2",
"uqr": "0.1.3",
}, },
"devDependencies": { "devDependencies": {
"@opencode-ai/script": "workspace:*", "@opencode-ai/script": "workspace:*",
@@ -152,7 +155,7 @@
}, },
"packages/codemode": { "packages/codemode": {
"name": "@opencode-ai/codemode", "name": "@opencode-ai/codemode",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"acorn": "8.15.0", "acorn": "8.15.0",
"effect": "catalog:", "effect": "catalog:",
@@ -166,7 +169,7 @@
}, },
"packages/console/app": { "packages/console/app": {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
@@ -202,7 +205,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@aws-sdk/client-sts": "3.782.0", "@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1", "@jsx-email/render": "1.1.1",
@@ -229,7 +232,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "3.0.82", "@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48", "@ai-sdk/openai": "3.0.48",
@@ -251,7 +254,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
@@ -275,7 +278,7 @@
}, },
"packages/console/support": { "packages/console/support": {
"name": "@opencode-ai/console-support", "name": "@opencode-ai/console-support",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-core": "workspace:*",
@@ -295,7 +298,7 @@
}, },
"packages/core": { "packages/core": {
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"version": "1.17.14", "version": "1.17.18",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -319,7 +322,7 @@
"@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/provider-utils": "4.0.23",
"@ai-sdk/togetherai": "2.0.41", "@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39", "@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.82", "@ai-sdk/xai": "3.0.102",
"@aws-sdk/credential-providers": "3.1057.0", "@aws-sdk/credential-providers": "3.1057.0",
"@effect/opentelemetry": "catalog:", "@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
@@ -391,7 +394,7 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@zip.js/zip.js": "2.7.62", "@zip.js/zip.js": "2.7.62",
"effect": "catalog:", "effect": "catalog:",
@@ -451,7 +454,7 @@
}, },
"packages/effect-drizzle-sqlite": { "packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite", "name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
@@ -465,7 +468,7 @@
}, },
"packages/effect-sqlite-node": { "packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node", "name": "@opencode-ai/effect-sqlite-node",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"effect": "catalog:", "effect": "catalog:",
}, },
@@ -477,7 +480,7 @@
}, },
"packages/enterprise": { "packages/enterprise": {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@hono/standard-validator": "catalog:", "@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -509,7 +512,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
@@ -525,12 +528,12 @@
}, },
"packages/http-recorder": { "packages/http-recorder": {
"name": "@opencode-ai/http-recorder", "name": "@opencode-ai/http-recorder",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@effect/platform-node": "4.0.0-beta.83",
"@effect/platform-node-shared": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83",
}, },
"devDependencies": { "devDependencies": {
"@effect/platform-node": "catalog:",
"@tsconfig/node22": "catalog:", "@tsconfig/node22": "catalog:",
"@types/bun": "catalog:", "@types/bun": "catalog:",
"@types/node": "catalog:", "@types/node": "catalog:",
@@ -539,7 +542,7 @@
"typescript": "catalog:", "typescript": "catalog:",
}, },
"peerDependencies": { "peerDependencies": {
"effect": "4.0.0-beta.83", "effect": "catalog:",
}, },
}, },
"packages/httpapi-codegen": { "packages/httpapi-codegen": {
@@ -557,7 +560,7 @@
}, },
"packages/llm": { "packages/llm": {
"name": "@opencode-ai/llm", "name": "@opencode-ai/llm",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14", "@smithy/eventstream-codec": "4.2.14",
@@ -576,7 +579,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.17.14", "version": "1.17.18",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -602,7 +605,7 @@
"@ai-sdk/provider": "3.0.8", "@ai-sdk/provider": "3.0.8",
"@ai-sdk/togetherai": "2.0.41", "@ai-sdk/togetherai": "2.0.41",
"@ai-sdk/vercel": "2.0.39", "@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.82", "@ai-sdk/xai": "3.0.102",
"@aws-sdk/credential-providers": "3.1057.0", "@aws-sdk/credential-providers": "3.1057.0",
"@clack/prompts": "1.0.0-alpha.1", "@clack/prompts": "1.0.0-alpha.1",
"@effect/opentelemetry": "catalog:", "@effect/opentelemetry": "catalog:",
@@ -709,7 +712,7 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@ai-sdk/provider": "3.0.8", "@ai-sdk/provider": "3.0.8",
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
@@ -789,6 +792,8 @@
"effect": "catalog:", "effect": "catalog:",
}, },
"devDependencies": { "devDependencies": {
"@opencode-ai/httpapi-codegen": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
"@tsconfig/bun": "catalog:", "@tsconfig/bun": "catalog:",
"@types/bun": "catalog:", "@types/bun": "catalog:",
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
@@ -796,7 +801,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"cross-spawn": "catalog:", "cross-spawn": "catalog:",
}, },
@@ -811,7 +816,7 @@
}, },
"packages/server": { "packages/server": {
"name": "@opencode-ai/server", "name": "@opencode-ai/server",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -828,7 +833,7 @@
}, },
"packages/session-ui": { "packages/session-ui": {
"name": "@opencode-ai/session-ui", "name": "@opencode-ai/session-ui",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -874,6 +879,8 @@
"name": "@opencode-ai/simulation", "name": "@opencode-ai/simulation",
"version": "1.17.13", "version": "1.17.13",
"dependencies": { "dependencies": {
"@fontsource/adwaita-mono": "5.2.1",
"@napi-rs/canvas": "1.0.2",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/llm": "workspace:*", "@opencode-ai/llm": "workspace:*",
"@opentui/core": "catalog:", "@opentui/core": "catalog:",
@@ -887,7 +894,7 @@
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@@ -900,7 +907,7 @@
}, },
"packages/stats/app": { "packages/stats/app": {
"name": "@opencode-ai/stats-app", "name": "@opencode-ai/stats-app",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
"@opencode-ai/stats-core": "workspace:*", "@opencode-ai/stats-core": "workspace:*",
@@ -933,7 +940,7 @@
}, },
"packages/stats/core": { "packages/stats/core": {
"name": "@opencode-ai/stats-core", "name": "@opencode-ai/stats-core",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@aws-sdk/client-athena": "3.933.0", "@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0", "@planetscale/database": "1.19.0",
@@ -952,7 +959,7 @@
}, },
"packages/stats/server": { "packages/stats/server": {
"name": "@opencode-ai/stats-server", "name": "@opencode-ai/stats-server",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@aws-sdk/client-firehose": "3.933.0", "@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
@@ -993,7 +1000,7 @@
}, },
"packages/tui": { "packages/tui": {
"name": "@opencode-ai/tui", "name": "@opencode-ai/tui",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -1014,6 +1021,7 @@
"remeda": "catalog:", "remeda": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2", "strip-ansi": "7.1.2",
"uqr": "0.1.3",
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "catalog:", "@tsconfig/bun": "catalog:",
@@ -1023,7 +1031,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:", "@pierre/diffs": "catalog:",
@@ -1074,7 +1082,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.17.14", "version": "1.17.18",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
@@ -1117,6 +1125,7 @@
], ],
"patchedDependencies": { "patchedDependencies": {
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
@@ -1126,7 +1135,6 @@
"@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
"@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch",
"@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch",
"pacote@21.5.0": "patches/pacote@21.5.0.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch",
}, },
@@ -1139,6 +1147,7 @@
}, },
"catalog": { "catalog": {
"@cloudflare/workers-types": "4.20251008.0", "@cloudflare/workers-types": "4.20251008.0",
"@corvu/drawer": "0.2.4",
"@effect/opentelemetry": "4.0.0-beta.83", "@effect/opentelemetry": "4.0.0-beta.83",
"@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node": "4.0.0-beta.83",
"@effect/sql-sqlite-bun": "4.0.0-beta.83", "@effect/sql-sqlite-bun": "4.0.0-beta.83",
@@ -1264,7 +1273,7 @@
"@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="],
"@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], "@ai-sdk/xai": ["@ai-sdk/xai@3.0.102", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.56", "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NeQyOR7OCqDMgaLS4uNX/ep/HrwUzzFYLzXQSRoqLy2jsnqxAJhsgltRwAwf+ADjyPBIAKEOestWnIQA+LrLrQ=="],
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
@@ -1548,6 +1557,10 @@
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="], "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
"@corvu/dialog": ["@corvu/dialog@0.2.4", "", { "dependencies": { "@corvu/utils": "~0.4.2", "solid-dismissible": "~0.1.1", "solid-focus-trap": "~0.1.8", "solid-presence": "~0.2.0", "solid-prevent-scroll": "~0.1.10" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA=="],
"@corvu/drawer": ["@corvu/drawer@0.2.4", "", { "dependencies": { "@corvu/dialog": "~0.2.4", "@corvu/utils": "~0.4.2", "@solid-primitives/memo": "^1.4.1", "solid-transition-size": "~0.1.4" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-7jQoGZ8ROB9CmXam2nMY2wEskU3IoFwZQywkF/7vrBc/edGsPv7mOVQ1GN6G+4nd7nrMZ3UtqHAhmRh9V0azlw=="],
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="], "@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
@@ -1612,11 +1625,11 @@
"@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], "@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="],
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="], "@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
@@ -1724,6 +1737,8 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@fontsource/adwaita-mono": ["@fontsource/adwaita-mono@5.2.1", "", {}, "sha512-6+Q1UIvklJ9REijs6kv7YlRNt6yktRj0iW8H69YIugdD9P2h3eIX1AB8/9ICMfpVyVeywlsrCXg82y/LfRrjyg=="],
"@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.2.5", "", {}, "sha512-G09N3GfuT9qj3Ax2FDZvKqZttzM3v+cco2l8uXamhKyXLdmlaUDH5o88/C3vtTHj2oT7yRKsvxz9F+BXbWKMYA=="], "@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.2.5", "", {}, "sha512-G09N3GfuT9qj3Ax2FDZvKqZttzM3v+cco2l8uXamhKyXLdmlaUDH5o88/C3vtTHj2oT7yRKsvxz9F+BXbWKMYA=="],
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
@@ -1984,6 +1999,30 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@napi-rs/canvas": ["@napi-rs/canvas@1.0.2", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "1.0.2", "@napi-rs/canvas-darwin-arm64": "1.0.2", "@napi-rs/canvas-darwin-x64": "1.0.2", "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2", "@napi-rs/canvas-linux-arm64-gnu": "1.0.2", "@napi-rs/canvas-linux-arm64-musl": "1.0.2", "@napi-rs/canvas-linux-riscv64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-gnu": "1.0.2", "@napi-rs/canvas-linux-x64-musl": "1.0.2", "@napi-rs/canvas-win32-arm64-msvc": "1.0.2", "@napi-rs/canvas-win32-x64-msvc": "1.0.2" } }, "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@1.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g=="],
"@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@1.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ=="],
"@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@1.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A=="],
"@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@1.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ=="],
"@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA=="],
"@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@1.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA=="],
"@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@1.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw=="],
"@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g=="],
"@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@1.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q=="],
"@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@1.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ=="],
"@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@1.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
@@ -2278,7 +2317,7 @@
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="], "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.127.0", "", { "os": "win32", "cpu": "x64" }, "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w=="],
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], "@oxc-project/types": ["@oxc-project/types@0.138.0", "", {}, "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA=="],
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="],
@@ -2570,7 +2609,37 @@
"@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="], "@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.4", "", { "os": "android", "cpu": "arm64" }, "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw=="],
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ=="],
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg=="],
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ=="],
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.4", "", { "os": "linux", "cpu": "arm" }, "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA=="],
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w=="],
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng=="],
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg=="],
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ=="],
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw=="],
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ=="],
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.4", "", { "os": "none", "cpu": "arm64" }, "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA=="],
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.4", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg=="],
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA=="],
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="],
@@ -2824,6 +2893,8 @@
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="], "@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
"@solid-primitives/memo": ["@solid-primitives/memo@1.5.1", "", { "dependencies": { "@solid-primitives/scheduled": "^1.5.3", "@solid-primitives/utils": "^6.4.1" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-VDPrkl9epp0tbby9MvsqphGFCYCtDRC5J8FKzTqHbQiG5hhR8n6xv4MfjhTW231IaBxxPHLxS43EE8c5Q23mSQ=="],
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="], "@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="], "@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
@@ -3112,6 +3183,8 @@
"@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], "@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
@@ -3184,6 +3257,12 @@
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="], "@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
"@vercel/cli-config": ["@vercel/cli-config@0.2.0", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-fJRRRB7734BDuXZ89yBEaA2ncYhH7bWX30mk04W80J6VAfQc+4iB8lyzAdaGpFV3/vNlkt9VZt+/uoQoWX6UsQ=="],
"@vercel/cli-exec": ["@vercel/cli-exec@1.0.0", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug=="],
"@vercel/functions": ["@vercel/functions@3.7.5", "", { "dependencies": { "@vercel/oidc": "3.8.0" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*", "ws": ">=8" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity", "ws"] }, "sha512-ESf8BbeDebqRUyMi09JwRbQqpLn4g6fjcVVHPsHB56j2dSqRrSHO4h3X4aaxJf6iQQjzhAtDGI2xCWQ27JE8PA=="],
"@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
@@ -4988,6 +5067,8 @@
"opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="], "opentui-spinner": ["opentui-spinner@0.0.7", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.3.4", "@opentui/react": "^0.3.4", "@opentui/solid": "^0.3.4", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-nPzwAvJG+y9rVEwwHLHqbsMzLnIk2zw+F9LqwA7aYJvpM5gsrKC2rrGi36A+tZpA+1RnWxXeWEgVZMchnaH18Q=="],
"os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="], "oxc-minify": ["oxc-minify@0.96.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.96.0", "@oxc-minify/binding-darwin-arm64": "0.96.0", "@oxc-minify/binding-darwin-x64": "0.96.0", "@oxc-minify/binding-freebsd-x64": "0.96.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.96.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.96.0", "@oxc-minify/binding-linux-arm64-gnu": "0.96.0", "@oxc-minify/binding-linux-arm64-musl": "0.96.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.96.0", "@oxc-minify/binding-linux-s390x-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-gnu": "0.96.0", "@oxc-minify/binding-linux-x64-musl": "0.96.0", "@oxc-minify/binding-wasm32-wasi": "0.96.0", "@oxc-minify/binding-win32-arm64-msvc": "0.96.0", "@oxc-minify/binding-win32-x64-msvc": "0.96.0" } }, "sha512-dXeeGrfPJJ4rMdw+NrqiCRtbzVX2ogq//R0Xns08zql2HjV3Zi2SBJ65saqfDaJzd2bcHqvGWH+M44EQCHPAcA=="],
@@ -5402,6 +5483,8 @@
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
"rolldown": ["rolldown@1.1.4", "", { "dependencies": { "@oxc-project/types": "=0.138.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.4", "@rolldown/binding-darwin-arm64": "1.1.4", "@rolldown/binding-darwin-x64": "1.1.4", "@rolldown/binding-freebsd-x64": "1.1.4", "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", "@rolldown/binding-linux-arm64-gnu": "1.1.4", "@rolldown/binding-linux-arm64-musl": "1.1.4", "@rolldown/binding-linux-ppc64-gnu": "1.1.4", "@rolldown/binding-linux-s390x-gnu": "1.1.4", "@rolldown/binding-linux-x64-gnu": "1.1.4", "@rolldown/binding-linux-x64-musl": "1.1.4", "@rolldown/binding-openharmony-arm64": "1.1.4", "@rolldown/binding-wasm32-wasi": "1.1.4", "@rolldown/binding-win32-arm64-msvc": "1.1.4", "@rolldown/binding-win32-x64-msvc": "1.1.4" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA=="],
"rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="],
"rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
@@ -5528,11 +5611,15 @@
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
"solid-dismissible": ["solid-dismissible@0.1.1", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA=="],
"solid-focus-trap": ["solid-focus-trap@0.1.9", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-LTyNki6GUJPRLXV5uMWPkYClB07SUMubbr2EkAddiR0CJCF/I283txilMU9RURSr/P8EewMfXWu2o3aWrK7A5A=="],
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="], "solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
"solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="], "solid-presence": ["solid-presence@0.2.0", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-YM92o+jvpzX3XGaD4rLYmq/Kc2ZVh47GSCLEufHBFQQIurvZTs8SoGJxO8BJGNDxBKdcS8F3dYhW1SDXp4BNjA=="],
"solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="], "solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="],
@@ -5540,6 +5627,8 @@
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="], "solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
"solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="],
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="], "solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
@@ -5880,6 +5969,8 @@
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uqr": ["uqr@0.1.3", "", {}, "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="], "urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="],
@@ -6022,8 +6113,12 @@
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
"xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="],
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
"xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="],
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
"xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
@@ -6162,7 +6257,11 @@
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@ai-sdk/provider-utils": "4.0.35" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cQrN6OUn/jvsY3OdsU6Wn+ss7vp1iwIcakZKSlSRMnYqShBfyT7Qht+eqmgxs7w9ttrw6FAG6o11AiBs+iEsTA=="],
"@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.13", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-ZPtVYt5QIJzOta1kdUiDuCx4HhFkvNPv/rvmZ2b1iXwybYjJsCnNYR4PAw4kW7rgVfDARvHXcU64efWuqNp6bw=="],
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="],
"@alcalzone/ansi-tokenize/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "@alcalzone/ansi-tokenize/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -6398,6 +6497,8 @@
"@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], "@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
"@kobalte/core/solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="],
"@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
@@ -6620,6 +6721,10 @@
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], "@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
@@ -6640,6 +6745,8 @@
"@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], "@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
@@ -6690,6 +6797,8 @@
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
"@solid-primitives/memo/@solid-primitives/utils": ["@solid-primitives/utils@6.4.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg=="],
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], "@solidjs/start/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
@@ -6748,6 +6857,14 @@
"@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@vercel/cli-exec/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"@vercel/functions/@vercel/oidc": ["@vercel/oidc@3.8.0", "", { "dependencies": { "@vercel/cli-config": "0.2.0", "@vercel/cli-exec": "1.0.0", "jose": "^5.9.6" } }, "sha512-r00laGW6Pv778RoR6M2NxX91ycSj+PBwVo+fOb9Bif+F0IyUKt25zrvBzfEzQpeAzbqOgPZyQibEWDdDFApd+A=="],
"@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@vitest/coverage-v8/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], "@vitest/coverage-v8/@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="],
"@vitest/coverage-v8/magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], "@vitest/coverage-v8/magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="],
@@ -6768,6 +6885,8 @@
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
"ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="],
"ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
@@ -7044,6 +7163,8 @@
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"oxc-parser/@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
"p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], "p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="],
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -7124,6 +7245,8 @@
"socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"solid-transition-size/@corvu/utils": ["@corvu/utils@0.3.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.7" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ZWlyWEE8qV9+CB9OAyo2bTrZGXQN9ZeM+JfYv89zoR+lRACKTDuoOZEdiyL8Uc7U5dUSH1uTqKhTTnaHWb+wZA=="],
"sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], "sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
@@ -7270,6 +7393,8 @@
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -7708,6 +7833,10 @@
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@oxc-parser/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], "@pierre/diffs/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], "@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
@@ -7716,6 +7845,8 @@
"@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
"@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
"@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], "@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
@@ -7786,6 +7917,22 @@
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
"@vercel/cli-exec/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"@vercel/cli-exec/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
"@vercel/cli-exec/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"@vercel/cli-exec/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"@vercel/cli-exec/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"@vercel/cli-exec/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"@vercel/cli-exec/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"@vercel/functions/@vercel/oidc/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], "@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="],
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
@@ -7802,6 +7949,8 @@
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -8352,6 +8501,8 @@
"@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@vercel/cli-exec/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
-1
View File
@@ -495,7 +495,6 @@ async function subscribeSessionEvents() {
console.log("Subscribing to session events...") console.log("Subscribing to session events...")
const TOOL: Record<string, [string, string]> = { const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", "\x1b[33m\x1b[1m"],
bash: ["Bash", "\x1b[31m\x1b[1m"], bash: ["Bash", "\x1b[31m\x1b[1m"],
edit: ["Edit", "\x1b[32m\x1b[1m"], edit: ["Edit", "\x1b[32m\x1b[1m"],
glob: ["Glob", "\x1b[34m\x1b[1m"], glob: ["Glob", "\x1b[34m\x1b[1m"],
+4 -4
View File
@@ -1,8 +1,8 @@
{ {
"nodeModules": { "nodeModules": {
"x86_64-linux": "sha256-KTyd2ISQ4n1E3tm1LMEHtz+rKRZga+SONn5J6H0pheQ=", "x86_64-linux": "sha256-JTtn+wXTXg+yklvIMDLcGFaYhTU6ZrCgKT9JTNEQ3gA=",
"aarch64-linux": "sha256-ZIeaaqRr4JorEmmwFYuitxfYEAtrP8C6IlD+pmFRko0=", "aarch64-linux": "sha256-gXU6zyhvAZrZirkL/PlHdkHtEof/7PVSPCaE34Jnd4U=",
"aarch64-darwin": "sha256-Po8FISoBzUMwJSn6nw243/hLT/gAQCrm5HbwXe2uA0g=", "aarch64-darwin": "sha256-Q0oTG3uzOlD/X2kJingLle529lKFoTpyCW2rHXOZ6iE=",
"x86_64-darwin": "sha256-5TZzrCnvg1z00YP6O9U0SXjL04r+VmF7LVrqQ9BbSn4=" "x86_64-darwin": "sha256-LINvKHxPibTlJeNzfACQx0x+Yj5oROT6Du3I5AtqqXk="
} }
} }
+3 -1
View File
@@ -24,6 +24,7 @@
"prepare": "husky", "prepare": "husky",
"random": "echo 'Random script'", "random": "echo 'Random script'",
"sso": "aws sso login --sso-session=opencode --no-browser", "sso": "aws sso login --sso-session=opencode --no-browser",
"translate:app": "bun run script/translate-app.ts",
"test": "echo 'do not run tests from root' && exit 1" "test": "echo 'do not run tests from root' && exit 1"
}, },
"workspaces": { "workspaces": {
@@ -51,6 +52,7 @@
"@shikijs/stream": "4.2.0", "@shikijs/stream": "4.2.0",
"ulid": "3.0.1", "ulid": "3.0.1",
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",
"@corvu/drawer": "0.2.4",
"@types/luxon": "3.7.1", "@types/luxon": "3.7.1",
"@types/node": "24.12.2", "@types/node": "24.12.2",
"@types/semver": "7.7.1", "@types/semver": "7.7.1",
@@ -152,7 +154,7 @@
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch", "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
"pacote@21.5.0": "patches/pacote@21.5.0.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch",
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
@@ -20,7 +20,7 @@ const profiles = [
{ name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } }, { name: "edit", tool: "edit", input: { filePath: "src/edit.ts" } },
{ {
name: "multi patch", name: "multi patch",
tool: "apply_patch", tool: "patch",
input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] }, input: { files: ["src/a.ts", "src/b.ts", "src/old.ts", "src/moved.ts"] },
}, },
] as const ] as const
@@ -25,7 +25,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
userMessage(), userMessage(),
assistantMessage( assistantMessage(
[ [
toolPart(patchID, "apply_patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }), toolPart(patchID, "patch", "running", { files: [first.filePath] }, { metadata: { files: [first] } }),
textPart(followingID, "Following incremental patch"), textPart(followingID, "Following incremental patch"),
], ],
{ completed: false }, { completed: false },
@@ -49,7 +49,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"running", "running",
{ files: [first.filePath, second.filePath] }, { files: [first.filePath, second.filePath] },
{ metadata: { files: [first, second] } }, { metadata: { files: [first, second] } },
@@ -61,7 +61,7 @@ test("adds patch files incrementally without resetting outer expansion", async (
partUpdated( partUpdated(
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"completed", "completed",
{ files: [first.filePath, second.filePath, third.filePath] }, { files: [first.filePath, second.filePath, third.filePath] },
{ metadata: { files: [first, second, third] } }, { metadata: { files: [first, second, third] } },
@@ -33,11 +33,9 @@ test.describe("timeline tool state stability", () => {
} }
const names = { webfetch: "webfetch", websearch: "websearch", task: "task", skill: "skill", custom: "mcp_probe" } const names = { webfetch: "webfetch", websearch: "websearch", task: "task", skill: "skill", custom: "mcp_probe" }
const questionID = "prt_state_question" const questionID = "prt_state_question"
const todoID = "prt_state_todo"
const initial = [ const initial = [
...ids.map((id) => toolPart(`prt_state_${id}`, names[id], "pending", inputs[id])), ...ids.map((id) => toolPart(`prt_state_${id}`, names[id], "pending", inputs[id])),
toolPart(questionID, "question", "pending", questionInput()), toolPart(questionID, "question", "pending", questionInput()),
toolPart(todoID, "todowrite", "pending", { todos: [{ content: "Hidden", status: "pending" }] }),
textPart("prt_state_following", "Following lightweight tools"), textPart("prt_state_following", "Following lightweight tools"),
] ]
const childID = "ses_timeline_child" const childID = "ses_timeline_child"
@@ -49,7 +47,6 @@ test.describe("timeline tool state stability", () => {
await timeline.send(status("busy"), 120) await timeline.send(status("busy"), 120)
for (const id of ids) await timeline.waitForPart(`prt_state_${id}`) for (const id of ids) await timeline.waitForPart(`prt_state_${id}`)
await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0) await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toHaveCount(0)
await expect(page.locator(`[data-timeline-part-id="${todoID}"]`)).toHaveCount(0)
const regionIDs = [ const regionIDs = [
"prt_state_webfetch", "prt_state_webfetch",
@@ -105,7 +102,6 @@ test.describe("timeline tool state stability", () => {
]), ]),
) )
await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toContainText("Keep it stable") await expect(page.locator(`[data-timeline-part-id="${questionID}"]`)).toContainText("Keep it stable")
await expect(page.locator(`[data-timeline-part-id="${todoID}"]`)).toHaveCount(0)
await expect( await expect(
page.locator(`a[href$="/session/${childID}"]`, { has: page.locator('[data-component="task-tool-card"]') }), page.locator(`a[href$="/session/${childID}"]`, { has: page.locator('[data-component="task-tool-card"]') }),
).toBeVisible() ).toBeVisible()
@@ -295,7 +295,7 @@ function performanceTurn(index: number) {
messageID: assistantID, messageID: assistantID,
type: "tool", type: "tool",
callID: `call_0000_${suffix}_patch`, callID: `call_0000_${suffix}_patch`,
tool: "apply_patch", tool: "patch",
state: { state: {
status: "completed", status: "completed",
input: { patchText: realisticPatch(index) }, input: { patchText: realisticPatch(index) },
@@ -131,7 +131,7 @@ function toolPart(
): MessagePart { ): MessagePart {
const metadata = const metadata =
metadataOverride ?? metadataOverride ??
(tool === "apply_patch" (tool === "patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -219,7 +219,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ...(index % 7 === 0
? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)] ? [toolPart(index, 4, "bash", { command: "bun typecheck", description: "Verify generated output" }, 620)]
@@ -269,7 +269,6 @@ const childMessages = Array.from({ length: 4 }, (_, index) => [
]).flat() ]).flat()
function renderable(part: MessagePart) { function renderable(part: MessagePart) {
if (part.type === "tool" && part.tool === "todowrite") return false
if (part.type === "text") return !!part.text.trim() if (part.type === "text") return !!part.text.trim()
if (part.type === "reasoning") return !!part.text.trim() if (part.type === "reasoning") return !!part.text.trim()
return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch" return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch"
@@ -90,7 +90,7 @@ async function mockServers(page: Page, requests: string[]) {
if (url.pathname === `/session/${current.id}`) return json(route, current) if (url.pathname === `/session/${current.id}`) return json(route, current)
if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404)
if (url.pathname === `/session/${current.id}/message`) return json(route, []) if (url.pathname === `/session/${current.id}/message`) return json(route, [])
if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, []) if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, [])
if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname))
return json(route, []) return json(route, [])
if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname)) if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname))
@@ -0,0 +1,40 @@
import { expect, test } from "@playwright/test"
import { base64Encode } from "@opencode-ai/core/util/encode"
import { mockOpenCodeServer } from "../utils/mock-server"
const draftID = "draft_legacy_new_session"
const directory = "C:/OpenCode/LegacyNewSession"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
test("redirects a draft to the legacy new-session route", async ({ page }) => {
await mockOpenCodeServer(page, {
directory,
project: {
id: "proj_legacy_new_session",
worktree: directory,
vcs: "git",
name: "legacy-new-session",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes: [],
},
provider: { all: [], connected: [], default: {} },
sessions: [],
pageMessages: () => ({ items: [] }),
})
await page.addInitScript(
({ directory, draftID, server }) => {
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } }))
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([{ type: "draft", draftID, server, directory }]),
)
},
{ directory, draftID, server },
)
await page.goto(`/new-session?draftId=${draftID}`)
await expect(page).toHaveURL(`/${base64Encode(directory)}/session`)
await expect(page.locator("header[data-tauri-drag-region]")).toBeVisible()
await expect(page.locator('[data-component="prompt-input"]')).toBeVisible()
})
@@ -65,7 +65,7 @@ async function mockServers(page: Page) {
if (url.pathname === `/session/${current.id}`) return json(route, current) if (url.pathname === `/session/${current.id}`) return json(route, current)
if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404)
if (url.pathname === `/session/${current.id}/message`) return json(route, []) if (url.pathname === `/session/${current.id}/message`) return json(route, [])
if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, []) if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, [])
if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname))
return json(route, []) return json(route, [])
if (["/global/config", "/config", "/provider/auth", "/mcp"].includes(url.pathname)) return json(route, {}) if (["/global/config", "/config", "/provider/auth", "/mcp"].includes(url.pathname)) return json(route, {})
@@ -0,0 +1,146 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { expect, test } from "@playwright/test"
import { mockOpenCodeServer } from "../utils/mock-server"
import { expectSessionTitle } from "../utils/waits"
const directory = "C:/OpenCode/ReviewOpenFile"
const projectID = "proj_review_open_file"
const sessionID = "ses_review_open_file"
const title = "Review open file"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
test.use({ viewport: { width: 1440, height: 900 } })
test("opens and searches project files inline", async ({ page }) => {
const searches: { query: string; dirs?: string; limit?: number }[] = []
await mockOpenCodeServer(page, {
directory,
project: {
id: projectID,
worktree: directory,
vcs: "git",
name: "open-file-project",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes: [],
},
provider: {
all: [
{
id: "opencode",
name: "OpenCode",
models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } },
},
],
connected: ["opencode"],
default: { providerID: "opencode", modelID: "test" },
},
sessions: [
{
id: sessionID,
slug: sessionID,
projectID,
directory,
title,
version: "dev",
time: { created: 1700000000000, updated: 1700000000000 },
},
],
vcsDiff: [fileDiff("src/changed.ts")],
fileList: (path) => {
if (path) return []
return [
fileNode("README.md"),
{ name: "src", path: "src", absolute: `${directory}/src`, type: "directory", ignored: false },
]
},
fileContent: (path) => ({ type: "text", content: `contents:${path}` }),
findFiles: (input) => {
searches.push(input)
return input.query === "nested" ? ["src/nested.ts"] : []
},
pageMessages: () => ({ items: [] }),
})
await page.addInitScript(
({ directory, server, sessionID }) => {
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({
projects: { local: [{ worktree: directory, expanded: true }] },
lastProject: { local: directory },
}),
)
localStorage.setItem(
"opencode.global.dat:layout",
JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }),
)
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify([{ type: "session", server, sessionId: sessionID }]),
)
},
{ directory, server, sessionID },
)
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
await expectSessionTitle(page, title)
const panel = page.locator("#review-panel")
const contextButton = page.getByRole("button", { name: "View context usage" })
await contextButton.click()
await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "")
await panel.getByRole("button", { name: "Open file" }).click()
await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "")
await contextButton.click()
await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "")
await panel.getByRole("button", { name: "Open file" }).click()
const filter = panel.getByRole("combobox", { name: "Filter files" })
await expect(filter).toBeFocused()
await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "")
await expect(panel.getByText("open-file-project", { exact: true })).toBeVisible()
await panel.getByRole("button", { name: "README.md" }).click()
await expect(panel.getByRole("tab", { name: "README.md" })).toHaveAttribute("data-selected", "")
await expect(panel.getByText("contents:README.md", { exact: true })).toBeVisible()
await panel.getByRole("button", { name: "Open file" }).click()
await expect(panel.getByRole("tab", { name: "README.md" })).toHaveCount(0)
await filter.fill("nested")
const result = panel.getByRole("option", { name: /nested\.ts/ })
await expect(result).toBeVisible()
const resultID = await result.getAttribute("id")
expect(resultID).toBeTruthy()
await expect(filter).toHaveAttribute("aria-activedescendant", resultID!)
await filter.press("Enter")
await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "")
await expect(panel.getByText("contents:src/nested.ts", { exact: true })).toBeVisible()
expect(searches).toContainEqual({ query: "nested", dirs: "false", limit: 200 })
await panel.getByRole("button", { name: "Open file" }).click()
await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveCount(1)
await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "")
await page.keyboard.press("Control+w")
await expect(panel.getByRole("tab", { name: "Open file" })).toHaveCount(0)
await expect(panel.getByRole("tab", { name: "nested.ts" })).toHaveAttribute("data-selected", "")
})
function fileNode(path: string) {
return {
name: path,
path,
absolute: `${directory}/${path}`,
type: "file",
ignored: false,
}
}
function fileDiff(file: string) {
return {
file,
before: "before\n",
after: "after\n",
additions: 1,
deletions: 1,
status: "modified",
}
}
@@ -0,0 +1,152 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { expect, test, type Page } from "@playwright/test"
import { mockOpenCodeServer } from "../utils/mock-server"
import { expectSessionTitle } from "../utils/waits"
const directory = "C:/OpenCode/ReviewStatePersistence"
const projectID = "proj_review_state_persistence"
const sessionA = "ses_review_state_a"
const sessionB = "ses_review_state_b"
const titleA = "Alpha review state"
const titleB = "Beta review state"
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
test.use({ viewport: { width: 1440, height: 900 } })
test("restores review mode and selected file per session", async ({ page }) => {
await setup(page)
await page.goto(sessionHref(sessionA))
await expectSessionTitle(page, titleA)
await page.getByRole("button", { name: "Toggle review" }).click()
await selectMode(page, "Git changes", "Branch changes")
await selectFile(page, "beta.ts")
await switchSession(page, titleB)
await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible()
await selectFile(page, "gamma.ts")
await switchSession(page, titleA)
await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible()
await expectSelectedFile(page, "beta.ts")
await selectMode(page, "Branch changes", "Git changes")
await expectSelectedFile(page, "alpha.ts")
await selectMode(page, "Git changes", "Branch changes")
await expectSelectedFile(page, "beta.ts")
await page.reload()
await expectSessionTitle(page, titleA)
await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible()
await expectSelectedFile(page, "beta.ts")
await switchSession(page, titleB)
await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible()
await expectSelectedFile(page, "gamma.ts")
})
async function selectMode(page: Page, current: string, next: string) {
await page.getByRole("button", { name: current }).click()
await page.getByRole("option", { name: next }).click()
}
async function selectFile(page: Page, file: string) {
await page.getByRole("button", { name: file }).click()
await expectSelectedFile(page, file)
}
async function expectSelectedFile(page: Page, file: string) {
await expect(page.locator('[data-slot="session-review-v2-file-name"]')).toHaveText(file)
}
async function switchSession(page: Page, title: string) {
await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click()
await expectSessionTitle(page, title)
}
async function setup(page: Page) {
await mockOpenCodeServer(page, {
directory,
project: {
id: projectID,
worktree: directory,
vcs: "git",
name: "review-state-persistence",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes: [],
},
provider: {
all: [
{
id: "opencode",
name: "OpenCode",
models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } },
},
],
connected: ["opencode"],
default: { providerID: "opencode", modelID: "test" },
},
sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)],
pageMessages: () => ({ items: [] }),
})
await page.route(/\/vcs(?:\?.*)?$/, (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ branch: "feature", default_branch: "dev" }),
}),
)
await page.route("**/vcs/diff**", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(
new URL(route.request().url()).searchParams.get("mode") === "branch"
? [diff("src/alpha.ts"), diff("src/beta.ts")]
: [diff("src/alpha.ts"), diff("src/gamma.ts")],
),
}),
)
await page.addInitScript(
({ directory, server, sessions }) => {
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({
projects: { local: [{ worktree: directory, expanded: true }] },
lastProject: { local: directory },
}),
)
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))),
)
},
{ directory, server, sessions: [sessionA, sessionB] },
)
}
function session(id: string, title: string, created: number) {
return {
id,
slug: id,
projectID,
directory,
title,
version: "dev",
time: { created, updated: created },
}
}
function diff(file: string) {
return {
file,
additions: 1,
deletions: 1,
status: "modified",
patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`,
}
}
function sessionHref(sessionID: string) {
return `/server/${base64Encode(server)}/session/${sessionID}`
}
@@ -9,12 +9,19 @@ const title = "Review terminal stacked"
const branchDiffs = [ const branchDiffs = [
fileDiff(".github/actions/setup-bun/action.yml", 7), fileDiff(".github/actions/setup-bun/action.yml", 7),
...Array.from({ length: 2_739 }, (_, index) => ...Array.from({ length: 2_739 }, (_, index) =>
fileDiff(`src/branch/generated-${String(index).padStart(4, "0")}.ts`, 100), fileDiff(
`src/branch/d${String(Math.floor(index / 100)).padStart(5, "0")}/generated-${String(index).padStart(4, "0")}.ts`,
100,
false,
),
), ),
] ]
test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => { test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => {
test.setTimeout(120_000) test.setTimeout(120_000)
const events: Array<{ directory: string; payload: Record<string, unknown> }> = []
let detailVersion = 1
let detailFailures = 1
await page.setViewportSize({ width: 1400, height: 900 }) await page.setViewportSize({ width: 1400, height: 900 })
await mockOpenCodeServer(page, { await mockOpenCodeServer(page, {
directory, directory,
@@ -48,7 +55,10 @@ test("keeps the review tree and terminal sized when both panels are open", async
time: { created: 1700000000000, updated: 1700000000000 }, time: { created: 1700000000000, updated: 1700000000000 },
}, },
], ],
sessionStatus: { [sessionID]: { type: "idle" } },
pageMessages: () => ({ items: [] }), pageMessages: () => ({ items: [] }),
events: () => events.splice(0, 1),
eventRetry: 16,
}) })
await page.route(/\/vcs(?:\?.*)?$/, (route) => await page.route(/\/vcs(?:\?.*)?$/, (route) =>
route.fulfill({ route.fulfill({
@@ -57,17 +67,25 @@ test("keeps the review tree and terminal sized when both panels are open", async
body: JSON.stringify({ branch: "review-pane-performance", default_branch: "dev" }), body: JSON.stringify({ branch: "review-pane-performance", default_branch: "dev" }),
}), }),
) )
await page.route("**/vcs/diff**", (route) => await page.route("**/vcs/diff**", (route) => {
route.fulfill({ const url = new URL(route.request().url())
const scope = url.searchParams.get("directory")?.replaceAll("\\", "/")
const detail = scope?.endsWith("/src/branch/d00027")
if (detail && detailFailures-- > 0) return route.fulfill({ status: 500, body: "retry detail" })
return route.fulfill({
status: 200, status: 200,
contentType: "application/json", contentType: "application/json",
body: JSON.stringify( body: JSON.stringify(
new URL(route.request().url()).searchParams.get("mode") === "branch" url.searchParams.get("mode") === "branch"
? detail
? branchDiffs ? branchDiffs
.filter((diff) => diff.file.startsWith("src/branch/d00027/"))
.map((diff) => fileDiff(diff.file, diff.additions, true, detailVersion))
: branchDiffs
: Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)), : Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)),
), ),
}), })
) })
await page.route("**/pty", (route) => await page.route("**/pty", (route) =>
route.fulfill({ route.fulfill({
status: 200, status: 200,
@@ -96,7 +114,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
await expect(page.getByRole("tab", { name: "Review 2740" })).toBeVisible() await expect(page.getByRole("tab", { name: "Review 2740" })).toBeVisible()
await page.keyboard.press("Control+Backquote") await page.keyboard.press("Control+Backquote")
await expect(page.locator("#terminal-panel")).toBeVisible() await expect(page.locator("#terminal-panel")).toBeVisible()
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await expectStackGeometry(page) await expectStackGeometry(page)
const treeViewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') const treeViewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport')
@@ -113,41 +131,65 @@ test("keeps the review tree and terminal sized when both panels are open", async
}) })
expect(bottomGap).toBeGreaterThanOrEqual(0) expect(bottomGap).toBeGreaterThanOrEqual(0)
expect(bottomGap).toBeLessThanOrEqual(16) expect(bottomGap).toBeLessThanOrEqual(16)
const lazyDiff = page.waitForRequest((request) => {
const url = new URL(request.url())
return (
url.pathname === "/vcs/diff" &&
url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
)
})
await lastFile.click()
await lazyDiff
const preview = page.locator('[data-slot="session-review-v2-diff-scroll"]')
await expect(preview).toContainText("after-1")
detailVersion = 2
events.push(statusEvent("busy"))
await expect(page.getByRole("button", { name: "Stop" })).toBeVisible()
const refreshedDiff = page.waitForRequest((request) => {
const url = new URL(request.url())
return (
url.pathname === "/vcs/diff" &&
url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
)
})
events.push(statusEvent("idle"))
await refreshedDiff
await expect(preview).toContainText("after-2")
await selectMode(page, "Branch changes", "Git changes") await selectMode(page, "Branch changes", "Git changes")
await expectTree(page, 8, "git-0.ts") await expectTree(page, 8, "git-0.ts")
await page.getByRole("button", { name: "git-0.ts" }).click()
await selectMode(page, "Git changes", "Branch changes") await selectMode(page, "Git changes", "Branch changes")
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
const filter = page.getByRole("searchbox", { name: "Filter files" }) const filter = page.getByRole("searchbox", { name: "Filter files" })
await filter.fill("generated-2738") await filter.fill("generated-2738")
await expectTree(page, 1, "generated-2738.ts") await expectTree(page, 1, "generated-2738.ts")
await filter.fill("") await filter.fill("")
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await page.getByRole("button", { name: "Toggle file tree" }).click() await page.getByRole("button", { name: "Toggle file tree" }).click()
await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveAttribute("aria-hidden", "true") await expect(page.locator('[data-slot="session-review-v2-sidebar"]')).toHaveCount(0)
await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1) await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(0)
await page.getByRole("button", { name: "Toggle file tree" }).click() await page.getByRole("button", { name: "Toggle file tree" }).click()
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await page.keyboard.press("Control+Backquote") await page.keyboard.press("Control+Backquote")
await expect(page.locator("#terminal-panel")).toHaveCount(0) await expect(page.locator("#terminal-panel")).toHaveCount(0)
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await page.keyboard.press("Control+Backquote") await page.keyboard.press("Control+Backquote")
await expect(page.locator("#terminal-panel")).toBeVisible() await expect(page.locator("#terminal-panel")).toBeVisible()
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await page.getByRole("button", { name: "Toggle review" }).click() await page.getByRole("button", { name: "Toggle review" }).click()
await expect(page.locator("#review-panel")).toHaveAttribute("aria-hidden", "true") await expect(page.locator("#review-panel")).toHaveCount(0)
await expect(page.locator('#review-panel [data-component="file-tree-v2"]')).toHaveCount(1)
await page.getByRole("button", { name: "Toggle review" }).click() await page.getByRole("button", { name: "Toggle review" }).click()
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await page.setViewportSize({ width: 1_000, height: 700 }) await page.setViewportSize({ width: 1_000, height: 700 })
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await expectStackGeometry(page) await expectStackGeometry(page)
await page.setViewportSize({ width: 1_000, height: 120 }) await page.setViewportSize({ width: 1_000, height: 120 })
await page.setViewportSize({ width: 1_400, height: 900 }) await page.setViewportSize({ width: 1_400, height: 900 })
await expectTree(page, 2_745, "action.yml") await expectTree(page, 2_773, "action.yml")
await expectStackGeometry(page) await expectStackGeometry(page)
}) })
@@ -201,12 +243,21 @@ function base64Encode(value: string) {
return Buffer.from(value, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") return Buffer.from(value, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
} }
function fileDiff(file: string, additions: number) { function statusEvent(type: "busy" | "idle") {
return {
directory,
payload: { type: "session.status", properties: { sessionID, status: { type } } },
}
}
function fileDiff(file: string, additions: number, loaded = true, version = 1) {
return { return {
file, file,
additions, additions,
deletions: 0, deletions: 0,
status: "modified", status: "modified",
patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`, patch: loaded
? `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after-${version}'\n`
: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}`,
} }
} }
@@ -24,7 +24,7 @@ test("renders a completed single-file patch", async ({ page }) => {
assistantMessage([ assistantMessage([
toolPart( toolPart(
id, id,
"apply_patch", "patch",
"completed", "completed",
{ files: ["src/a.ts"] }, { files: ["src/a.ts"] },
{ {
@@ -35,7 +35,7 @@ test("preserves nested patch file state through outer collapse and reopen", asyn
assistantMessage([ assistantMessage([
toolPart( toolPart(
patchID, patchID,
"apply_patch", "patch",
"completed", "completed",
{ files: files.map((file) => file.filePath) }, { files: files.map((file) => file.filePath) },
{ metadata: { files } }, { metadata: { files } },
@@ -35,7 +35,6 @@ test.describe("session timeline projection", () => {
editPart("prt_edit"), editPart("prt_edit"),
toolPart("prt_write", "write", "completed", { filePath: "src/new.ts", content: "export const stable = true\n" }), toolPart("prt_write", "write", "completed", { filePath: "src/new.ts", content: "export const stable = true\n" }),
patchPart("prt_patch"), patchPart("prt_patch"),
toolPart("prt_todo", "todowrite", "completed", { todos: [{ content: "Hidden", status: "pending" }] }),
toolPart( toolPart(
"prt_question", "prt_question",
"question", "question",
@@ -65,7 +64,6 @@ test.describe("session timeline projection", () => {
]) { ]) {
await expect(page.locator(`[data-timeline-part-id="${id}"]`).first(), id).toBeVisible() await expect(page.locator(`[data-timeline-part-id="${id}"]`).first(), id).toBeVisible()
} }
await expect(page.locator('[data-timeline-part-id="prt_todo"]')).toHaveCount(0)
}) })
test("projects gaps, dividers, assistant parts, and errors together", async ({ page }) => { test("projects gaps, dividers, assistant parts, and errors together", async ({ page }) => {
@@ -246,7 +244,7 @@ function editPart(id: string) {
function patchPart(id: string) { function patchPart(id: string) {
return toolPart( return toolPart(
id, id,
"apply_patch", "patch",
"completed", "completed",
{ files: ["src/a.ts", "src/b.ts"] }, { files: ["src/a.ts", "src/b.ts"] },
{ {
@@ -8,7 +8,7 @@ import {
} from "../performance/timeline-stability/fixture" } from "../performance/timeline-stability/fixture"
test("renders every tool error outcome without leaking hidden tools", async ({ page }) => { test("renders every tool error outcome without leaking hidden tools", async ({ page }) => {
const ordinary = ["bash", "edit", "write", "apply_patch", "webfetch", "websearch", "task", "skill", "mcp_probe"] const ordinary = ["bash", "edit", "write", "patch", "webfetch", "websearch", "task", "skill", "mcp_probe"]
const parts = ordinary.map((tool, index) => const parts = ordinary.map((tool, index) =>
toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }), toolPart(`prt_error_${index}`, tool, "error", errorInput(tool), { error: `${tool} failed visibly` }),
) )
@@ -17,13 +17,11 @@ test("renders every tool error outcome without leaking hidden tools", async ({ p
error: "The user dismissed this question", error: "The user dismissed this question",
}), }),
toolPart("prt_question_error", "question", "error", questionInput(), { error: "Question transport failed" }), toolPart("prt_question_error", "question", "error", questionInput(), { error: "Question transport failed" }),
toolPart("prt_todo_error", "todowrite", "error", { todos: [] }, { error: "Hidden todo failure" }),
) )
await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] }) await setupTimeline(page, { messages: [userMessage(), assistantMessage(parts)] })
await expect(page.locator('[data-kind="tool-error-card"]')).toHaveCount(ordinary.length + 1) await expect(page.locator('[data-kind="tool-error-card"]')).toHaveCount(ordinary.length + 1)
await expect(page.getByText(/dismissed/i)).toBeVisible() await expect(page.getByText(/dismissed/i)).toBeVisible()
await expect(page.locator('[data-timeline-part-id="prt_todo_error"]')).toHaveCount(0)
for (let index = 0; index < ordinary.length; index++) { for (let index = 0; index < ordinary.length; index++) {
await expect(page.locator(`[data-timeline-part-id="prt_error_${index}"]`)).toBeVisible() await expect(page.locator(`[data-timeline-part-id="prt_error_${index}"]`)).toBeVisible()
} }
@@ -90,7 +88,7 @@ function questionInput() {
function errorInput(tool: string) { function errorInput(tool: string) {
if (tool === "bash") return { command: "exit 1" } if (tool === "bash") return { command: "exit 1" }
if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" } if (["edit", "write"].includes(tool)) return { filePath: "src/error.ts", content: "" }
if (tool === "apply_patch") return { files: ["src/error.ts"] } if (tool === "patch") return { files: ["src/error.ts"] }
if (tool === "webfetch") return { url: "https://example.com" } if (tool === "webfetch") return { url: "https://example.com" }
if (tool === "websearch") return { query: "failure" } if (tool === "websearch") return { query: "failure" }
if (tool === "task") return { description: "Fail task", subagent_type: "explore" } if (tool === "task") return { description: "Fail task", subagent_type: "explore" }
@@ -1,186 +0,0 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { expect, test, type Page } from "@playwright/test"
import { mockOpenCodeServer } from "../utils/mock-server"
import { expectSessionTitle } from "../utils/waits"
const directory = "C:/OpenCode/TodoDockNavigation"
const projectID = "proj_todo_dock_navigation"
const sourceID = "ses_todo_dock_source"
const otherID = "ses_todo_dock_other"
const sourceTitle = "Todo dock animation"
const otherTitle = "Separate session"
const activeTodos = [
{ id: "todo-1", content: "Receive todos in the active session", status: "completed", priority: "high" },
{ id: "todo-2", content: "Keep the dock visible across tabs", status: "completed", priority: "high" },
{ id: "todo-3", content: "Close after the final todo", status: "in_progress", priority: "high" },
]
type EventPayload = {
directory: string
payload: Record<string, unknown>
}
test.use({ viewport: { width: 1440, height: 900 }, reducedMotion: "no-preference" })
test("animates todo lifecycle without replaying it across session tabs", async ({ page }) => {
test.setTimeout(90_000)
const events: EventPayload[] = []
const todos: Record<string, typeof activeTodos> = { [sourceID]: [], [otherID]: [] }
await mockOpenCodeServer(page, {
directory,
project: {
id: projectID,
worktree: directory,
vcs: "git",
name: "todo-dock-navigation",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes: [],
},
provider: {
all: [
{
id: "opencode",
name: "OpenCode",
models: {
"claude-opus-4-6": {
id: "claude-opus-4-6",
name: "Claude Opus 4.6",
limit: { context: 200_000 },
},
},
},
],
connected: ["opencode"],
default: { providerID: "opencode", modelID: "claude-opus-4-6" },
},
sessions: [session(sourceID, sourceTitle, 1700000000000), session(otherID, otherTitle, 1700000001000)],
pageMessages: () => ({ items: [] }),
events: () => events.splice(0, 1),
eventRetry: 16,
todos: (sessionID) => todos[sessionID] ?? [],
})
await configurePage(page)
await page.goto(sessionHref(sourceID))
await expectSessionTitle(page, sourceTitle)
const dock = page.locator('[data-component="session-todo-dock"]')
await expect(dock).toHaveCount(0)
events.push(statusEvent(sourceID, "busy"))
await expect(page.getByRole("button", { name: "Stop" })).toBeVisible()
await page.waitForTimeout(700)
const opening = sampleDock(page, 1_000)
todos[sourceID] = activeTodos
events.push(todoEvent(sourceID, activeTodos))
await expect(dock).toBeVisible()
await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1)
expect((await opening).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true)
await switchSession(page, otherID, otherTitle)
await expect(dock).toHaveCount(0)
const returningOpen = sampleDock(page, 700)
await switchSession(page, sourceID, sourceTitle)
const openSamples = (await returningOpen).filter((sample) => sample.present)
expect(openSamples.length).toBeGreaterThan(0)
expect(openSamples[0]!.opacity).toBeGreaterThan(0.98)
expect(openSamples[0]!.height).toBeGreaterThan(70)
await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1)
const completedTodos = activeTodos.map((todo) => ({ ...todo, status: "completed" }))
const closing = sampleDock(page, 1_000)
todos[sourceID] = completedTodos
events.push(todoEvent(sourceID, completedTodos))
await expect(dock).toHaveCount(0)
expect((await closing).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true)
todos[sourceID] = []
events.push(todoEvent(sourceID, []))
await switchSession(page, otherID, otherTitle)
const returningEmpty = sampleDock(page, 700)
await switchSession(page, sourceID, sourceTitle)
await expect(dock).toHaveCount(0)
expect((await returningEmpty).every((sample) => !sample.present)).toBe(true)
})
function session(id: string, title: string, created: number) {
return {
id,
slug: id,
projectID,
directory,
title,
version: "dev",
time: { created, updated: created },
}
}
function statusEvent(sessionID: string, type: "busy" | "idle"): EventPayload {
return {
directory,
payload: { type: "session.status", properties: { sessionID, status: { type } } },
}
}
function todoEvent(sessionID: string, next: typeof activeTodos): EventPayload {
return {
directory,
payload: { type: "todo.updated", properties: { sessionID, todos: next } },
}
}
async function configurePage(page: Page) {
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
await page.addInitScript(
({ directory, dirBase64, server, sessionIDs }) => {
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({
projects: { local: [{ worktree: directory, expanded: true }] },
lastProject: { local: directory },
}),
)
localStorage.setItem(
"opencode.window.browser.dat:tabs",
JSON.stringify(sessionIDs.map((sessionId) => ({ type: "session", server, dirBase64, sessionId }))),
)
},
{ directory, dirBase64: base64Encode(directory), server, sessionIDs: [sourceID, otherID] },
)
}
function sessionHref(sessionID: string) {
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
return `/server/${base64Encode(server)}/session/${sessionID}`
}
async function switchSession(page: Page, sessionID: string, title: string) {
const href = sessionHref(sessionID)
const tab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`).first()
await expect(tab).toBeVisible()
await tab.click()
await expectSessionTitle(page, title)
}
function sampleDock(page: Page, duration: number) {
return page.evaluate(async (duration) => {
const samples: { present: boolean; height: number; opacity: number }[] = []
const start = performance.now()
while (performance.now() - start < duration) {
const dock = document.querySelector<HTMLElement>('[data-component="session-todo-dock"]')
const clip = dock?.parentElement?.parentElement
const label = dock?.querySelector<HTMLElement>('[data-action="session-todo-toggle"] span[aria-label]')
samples.push({
present: !!dock,
height: clip?.getBoundingClientRect().height ?? 0,
opacity: label ? Number.parseFloat(getComputedStyle(label).opacity) : 0,
})
await new Promise(requestAnimationFrame)
}
return samples
}, duration)
}
@@ -63,7 +63,7 @@ async function mockServer(page: Page) {
if (byId) return json(route, byId) if (byId) return json(route, byId)
if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404) if (/^\/session\/[^/]+$/.test(url.pathname)) return json(route, { name: "NotFoundError" }, 404)
if (/^\/session\/[^/]+\/message$/.test(url.pathname)) return json(route, []) if (/^\/session\/[^/]+\/message$/.test(url.pathname)) return json(route, [])
if (/^\/session\/[^/]+\/(children|todo|diff)$/.test(url.pathname)) return json(route, []) if (/^\/session\/[^/]+\/(children|diff)$/.test(url.pathname)) return json(route, [])
if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname)) if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/diff"].includes(url.pathname))
return json(route, []) return json(route, [])
if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname)) if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(url.pathname))
@@ -120,7 +120,7 @@ function toolPart(
outputLength = 160, outputLength = 160,
): MessagePart { ): MessagePart {
const metadata = const metadata =
tool === "apply_patch" tool === "patch"
? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] } ? { files: [patchFile(index, "update"), patchFile(index + 1, index % 2 === 0 ? "add" : "delete")] }
: tool === "edit" || tool === "write" : tool === "edit" || tool === "write"
? { ? {
@@ -199,7 +199,7 @@ function turn(index: number): Message[] {
? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)] ? [toolPart(index, 7, "write", { filePath: `src/generated/write-${index}.ts`, content: code(index, 28) }, 560)]
: []), : []),
...(index % 8 === 0 ...(index % 8 === 0
? [toolPart(index, 8, "apply_patch", { files: [`src/generated/patch-${index}.ts`] }, 620)] ? [toolPart(index, 8, "patch", { files: [`src/generated/patch-${index}.ts`] }, 620)]
: []), : []),
...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []), ...(index % 7 === 0 ? [toolPart(index, 4, "bash", { command: "bun typecheck" }, 620)] : []),
...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []), ...(index % 10 === 0 ? [toolPart(index, 9, "webfetch", { url: "https://example.com/docs/sample" }, 120)] : []),
@@ -229,7 +229,6 @@ const sourceMessages = Array.from({ length: 12 }, (_, index) => [
]).flat() ]).flat()
function renderable(part: MessagePart) { function renderable(part: MessagePart) {
if (part.type === "tool" && part.tool === "todowrite") return false
if (part.type === "text") return !!part.text.trim() if (part.type === "text") return !!part.text.trim()
if (part.type === "reasoning") return !!part.text.trim() if (part.type === "reasoning") return !!part.text.trim()
return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch" return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch"
+11 -4
View File
@@ -17,11 +17,11 @@ export interface MockServerConfig {
onMessage?: (input: { sessionID: string; messageID: string }) => void onMessage?: (input: { sessionID: string; messageID: string }) => void
events?: () => unknown[] events?: () => unknown[]
eventRetry?: number eventRetry?: number
todos?: (sessionID: string) => unknown[]
permissions?: unknown[] | (() => unknown[]) permissions?: unknown[] | (() => unknown[])
questions?: unknown[] | (() => unknown[]) questions?: unknown[] | (() => unknown[])
fileList?: (path: string) => unknown | Promise<unknown> fileList?: (path: string) => unknown | Promise<unknown>
fileContent?: (path: string) => unknown | Promise<unknown> fileContent?: (path: string) => unknown | Promise<unknown>
findFiles?: (input: { query: string; dirs?: string; limit?: number }) => unknown
sessionStatus?: unknown sessionStatus?: unknown
} }
@@ -55,7 +55,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
const path = url.pathname const path = url.pathname
if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry) if (path === "/global/event" || path === "/event") return sse(route, config.events?.(), config.eventRetry)
if (path === "/global/health") return json(route, { healthy: true }) if (path === "/global/health") return json(route, { healthy: true })
if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: false }) if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true })
if (path === "/permission") if (path === "/permission")
return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? [])) return json(route, typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
if (path === "/question") if (path === "/question")
@@ -66,6 +66,15 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
return json(route, await config.fileList(url.searchParams.get("path") ?? "")) return json(route, await config.fileList(url.searchParams.get("path") ?? ""))
if (path === "/file/content" && config.fileContent) if (path === "/file/content" && config.fileContent)
return json(route, await config.fileContent(url.searchParams.get("path") ?? "")) return json(route, await config.fileContent(url.searchParams.get("path") ?? ""))
if (path === "/find/file" && config.findFiles)
return json(
route,
await config.findFiles({
query: url.searchParams.get("query") ?? "",
dirs: url.searchParams.get("dirs") ?? undefined,
limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined,
}),
)
if (path === "/api/reference") if (path === "/api/reference")
return json(route, { return json(route, {
location: { location: {
@@ -96,8 +105,6 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
return json(route, message) return json(route, message)
} }
const todoMatch = path.match(/^\/session\/([^/]+)\/todo$/)
if (todoMatch) return json(route, config.todos?.(todoMatch[1]!) ?? [])
if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, []) if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, [])
const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/) const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/)
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.17.14", "version": "1.17.18",
"description": "", "description": "",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -47,6 +47,7 @@
"vite-plugin-solid": "catalog:" "vite-plugin-solid": "catalog:"
}, },
"dependencies": { "dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0", "@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0", "@dnd-kit/dom": "0.5.0",
"@dnd-kit/helpers": "0.5.0", "@dnd-kit/helpers": "0.5.0",
@@ -88,6 +89,7 @@
"shiki": "catalog:", "shiki": "catalog:",
"solid-js": "catalog:", "solid-js": "catalog:",
"solid-list": "catalog:", "solid-list": "catalog:",
"solid-presence": "0.2.0",
"tailwindcss": "catalog:" "tailwindcss": "catalog:"
} }
} }
+71 -17
View File
@@ -9,9 +9,10 @@ import { Font } from "@opencode-ai/ui/font"
import { Splash } from "@opencode-ai/ui/logo" import { Splash } from "@opencode-ai/ui/logo"
import { ThemeProvider } from "@opencode-ai/ui/theme/context" import { ThemeProvider } from "@opencode-ai/ui/theme/context"
import { MetaProvider } from "@solidjs/meta" import { MetaProvider } from "@solidjs/meta"
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import { Effect } from "effect" import { Effect } from "effect"
import { base64Encode } from "@opencode-ai/core/util/encode"
import { import {
type Component, type Component,
createEffect, createEffect,
@@ -32,7 +33,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma
import { CommentsProvider } from "@/context/comments" import { CommentsProvider } from "@/context/comments"
import { FileProvider } from "@/context/file" import { FileProvider } from "@/context/file"
import { ServerSDKProvider } from "@/context/server-sdk" import { ServerSDKProvider } from "@/context/server-sdk"
import { ServerSyncProvider } from "@/context/server-sync" import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
import { GlobalProvider, useGlobal } from "@/context/global" import { GlobalProvider, useGlobal } from "@/context/global"
import { HighlightsProvider } from "@/context/highlights" import { HighlightsProvider } from "@/context/highlights"
import { LanguageProvider, type Locale, useLanguage } from "@/context/language" import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
@@ -52,9 +53,10 @@ import LegacyLayout from "@/pages/layout"
import NewLayout from "@/pages/layout-new" import NewLayout from "@/pages/layout-new"
import { ErrorPage } from "./pages/error" import { ErrorPage } from "./pages/error"
import { useCheckServerHealth } from "./utils/server-health" import { useCheckServerHealth } from "./utils/server-health"
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route" import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
import { createSessionLineage } from "@/pages/session/session-lineage"
import { SessionPage, TargetSessionRouteContent } from "@/pages/session" import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session"
import { NewHome, LegacyHome } from "@/pages/home" import { NewHome, LegacyHome } from "@/pages/home"
const NewSession = lazy(() => import("@/pages/new-session")) const NewSession = lazy(() => import("@/pages/new-session"))
@@ -88,10 +90,14 @@ const SessionRoute = () => {
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt) tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
}) })
return <SessionPage /> return (
<SessionRouteErrorBoundary sessionID={params.id}>
<SessionPage />
</SessionRouteErrorBoundary>
)
} }
const TargetSessionRoute = () => { function TargetServerRoute(props: ParentProps) {
const params = useParams<{ serverKey: string; id: string }>() const params = useParams<{ serverKey: string; id: string }>()
const global = useGlobal() const global = useGlobal()
const conn = createMemo(() => { const conn = createMemo(() => {
@@ -105,14 +111,47 @@ const TargetSessionRoute = () => {
// re-resolves reactively instead); both rely on this key for server changes. // re-resolves reactively instead); both rely on this key for server changes.
<Show when={requireServerKey(params.serverKey)} keyed> <Show when={requireServerKey(params.serverKey)} keyed>
<ServerSDKProvider server={conn}> <ServerSDKProvider server={conn}>
<ServerSyncProvider server={conn}> <ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
<TargetSessionRouteContent />
</ServerSyncProvider>
</ServerSDKProvider> </ServerSDKProvider>
</Show> </Show>
) )
} }
const TargetSessionRoute = () => (
<TargetServerRoute>
<TargetSessionRouteContent />
</TargetServerRoute>
)
function LegacyTargetSessionRoute() {
const params = useParams<{ serverKey: string; id: string }>()
return (
<TargetServerRoute>
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
<LegacyTargetSessionRedirect />
</SessionRouteErrorBoundary>
</TargetServerRoute>
)
}
function LegacyTargetSessionRedirect() {
const params = useParams<{ id: string }>()
const navigate = useNavigate()
const sync = useServerSync()
const current = createSessionLineage(
() => params.id,
() => sync().session.lineage,
)
createEffect(() => {
const directory = current()?.session.directory
if (!directory) return
navigate(legacySessionHref(directory, params.id), { replace: true })
})
return null
}
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected // Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/ // server via ServerKey, then provide the server-scoped shell (Permission/Layout/
// Notification/Models + the visual Layout) for that server. // Notification/Models + the visual Layout) for that server.
@@ -136,6 +175,7 @@ function LegacyServerLayout(props: ParentProps<{ serverScoped?: JSX.Element }>)
function DraftRoute() { function DraftRoute() {
const [search] = useSearchParams<{ draftId?: string }>() const [search] = useSearchParams<{ draftId?: string }>()
const settings = useSettings()
const tabs = useTabs() const tabs = useTabs()
return ( return (
<Show when={tabs.ready()}> <Show when={tabs.ready()}>
@@ -144,7 +184,14 @@ function DraftRoute() {
keyed keyed
fallback={<Navigate href="/" />} fallback={<Navigate href="/" />}
> >
{(draft) => <ResolvedDraftRoute draft={draft} />} {(draft) => (
<Show
when={settings.general.newLayoutDesigns()}
fallback={<Navigate href={`/${base64Encode(draft.directory)}/session`} />}
>
<ResolvedDraftRoute draft={draft} />
</Show>
)}
</Show> </Show>
</Show> </Show>
) )
@@ -186,7 +233,7 @@ declare global {
deepLinks?: string[] deepLinks?: string[]
} }
api?: { api?: {
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void> setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
exportDebugLogs?: () => Promise<string> exportDebugLogs?: () => Promise<string>
} }
} }
@@ -320,8 +367,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
<MetaProvider> <MetaProvider>
<Font /> <Font />
<ThemeProvider <ThemeProvider
onThemeApplied={(_, mode) => { onThemeApplied={(_, mode, scheme) => {
void window.api?.setTitlebar?.({ mode }) void window.api?.setTitlebar?.({ mode, scheme })
}} }}
> >
<LanguageProvider locale={props.locale}> <LanguageProvider locale={props.locale}>
@@ -542,7 +589,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout> <LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
)} )}
> >
<Show when={!settings.general.newLayoutDesigns()}>{<Route path="/" component={LegacyHome} />}</Show> <Show when={!settings.general.newLayoutDesigns()}>
{
<>
<Route path="/" component={LegacyHome} />
<Route path="/server/:serverKey/session/:id" component={LegacyTargetSessionRoute} />
</>
}
</Show>
<Route path="/:dir" component={DirectoryLayout}> <Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} /> <Route path="/" component={() => <Navigate href="session" />} />
<Route path="/session/:id?" component={SessionRoute} /> <Route path="/session/:id?" component={SessionRoute} />
@@ -550,15 +604,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
</Route> </Route>
<Show when={settings.general.newLayoutDesigns()}> <Show when={settings.general.newLayoutDesigns()}>
<Route path="/" component={NewHome} /> <Route path="/" component={NewHome} />
<Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} /> <Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
</Show> </Show>
<Route path="/new-session" component={DraftRoute} /> <Route path="/new-session" component={DraftRoute} />
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
</> </>
) )
} }
function LegacyTargetSessionRoute() { function NewLayoutLegacySessionRedirect() {
const server = useServer() const server = useServer()
const tabs = useTabs() const tabs = useTabs()
const params = useParams<{ id: string }>() const params = useParams<{ id: string }>()
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,325 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { getFilename } from "@opencode-ai/core/util/path"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useNavigate } from "@solidjs/router"
import { createMemo, onCleanup } from "solid-js"
import { useCommand, type CommandOption } from "@/context/command"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { createSessionTabs } from "@/pages/session/helpers"
import { useSessionLayout } from "@/pages/session/session-layout"
import { decode64 } from "@/utils/base64"
export type CommandPaletteEntry = {
id: string
type: "command" | "file" | "session"
title: string
description?: string
keybind?: string
category: string
option?: CommandOption
path?: string
directory?: string
sessionID?: string
archived?: number
updated?: number
}
const ENTRY_LIMIT = 5
const COMMON_COMMAND_IDS = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
] as const
export function uniqueCommandPaletteEntries(items: CommandPaletteEntry[]) {
const seen = new Set<string>()
return items.filter((item) => {
if (seen.has(item.id)) return false
seen.add(item.id)
return true
})
}
export function createCommandPaletteFileEntry(path: string, category: string): CommandPaletteEntry {
return {
id: "file:" + path,
type: "file",
title: path,
category,
path,
}
}
export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => void) {
const file = useFile()
const layout = useLayout()
const { tabs, view } = useSessionLayout()
return (path: string) => {
const value = file.tab(path)
void tabs().open(value)
void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
onOpenFile?.(path)
tabs().setActive(value)
}
}
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
const command = useCommand()
const language = useLanguage()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const navigate = useNavigate()
const serverSDK = useServerSDK()()
const serverSync = useServerSync()
const { params, tabs } = useSessionLayout()
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
const state = { cleanup: undefined as (() => void) | void, committed: false }
const filesOnly = () => props.filesOnly?.() ?? false
const allowedCommands = createMemo(() => {
if (filesOnly()) return []
return command.options.filter(
(option) =>
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
)
})
const commandEntries = createMemo(() => {
const category = language.t("palette.group.commands")
return allowedCommands().map((option) => createCommandEntry(option, category))
})
const preferredCommandEntries = createMemo(() => {
const all = allowedCommands()
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
const category = language.t("palette.group.commands")
return sorted.map((option) => createCommandEntry(option, category))
})
const tabState = createSessionTabs({
tabs,
pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
})
const recentFileEntries = createMemo(() => {
const all = tabState.openedTabs()
const active = tabState.activeFileTab()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const category = language.t("palette.group.files")
return order
.map((item) => file.pathFromTab(item))
.filter((path): path is string => {
if (!path || seen.has(path)) return false
seen.add(path)
return true
})
.slice(0, ENTRY_LIMIT)
.map((path) => createCommandPaletteFileEntry(path, category))
})
const rootFileEntries = createMemo(() => {
const category = language.t("palette.group.files")
return file.tree
.children("")
.filter((node) => node.type === "file")
.map((node) => node.path)
.sort((a, b) => a.localeCompare(b))
.slice(0, ENTRY_LIMIT)
.map((path) => createCommandPaletteFileEntry(path, category))
})
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
const project = createMemo(() => {
const directory = projectDirectory()
if (!directory) return undefined
return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
})
const workspaces = createMemo(() => {
const directory = projectDirectory()
const current = project()
if (!current) return directory ? [directory] : []
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
if (directory && !dirs.includes(directory)) return [...dirs, directory]
return dirs
})
const homedir = createMemo(() => serverSync().data.path.home)
const sessions = createSessionEntries({
workspaces,
label: (directory) => {
const current = project()
const kind =
current && directory === current.worktree
? language.t("workspace.type.local")
: language.t("workspace.type.sandbox")
const [store] = serverSync().child(directory, { bootstrap: false })
const home = homedir()
const path = home ? directory.replace(home, "~") : directory
const name = store.vcs?.branch ?? getFilename(directory)
return `${kind} : ${name || path}`
},
load: (directory) => serverSDK.client.session.list({ directory, roots: true }),
untitled: () => language.t("command.session.new"),
category: () => language.t("command.category.session"),
})
const highlight = (item: CommandPaletteEntry | undefined) => {
state.cleanup?.()
state.cleanup = undefined
if (item?.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}
const select = (item: CommandPaletteEntry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()
if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}
if (item.type === "session") {
if (!item.directory || !item.sessionID) return
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
return
}
if (!item.path) return
openFile(item.path)
}
onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})
return {
language,
file,
commandEntries,
preferredCommandEntries,
recentFileEntries,
rootFileEntries,
sessions,
highlight,
select,
close: () => dialog.close(),
}
}
function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
return {
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category,
option,
}
}
function createSessionEntries(props: {
workspaces: () => string[]
label: (directory: string) => string
load: (directory: string) => ReturnType<ServerSDK["client"]["session"]["list"]>
untitled: () => string
category: () => string
}) {
const state: {
token: number
inflight: Promise<CommandPaletteEntry[]> | undefined
cached: CommandPaletteEntry[] | undefined
} = { token: 0, inflight: undefined, cached: undefined }
return (text: string) => {
if (!text.trim()) {
state.token += 1
state.inflight = undefined
state.cached = undefined
return [] as CommandPaletteEntry[]
}
if (state.cached) return state.cached
if (state.inflight) return state.inflight
const current = state.token
const dirs = props.workspaces()
if (dirs.length === 0) return [] as CommandPaletteEntry[]
state.inflight = Promise.all(
dirs.map((directory) => {
const description = props.label(directory)
return props
.load(directory)
.then((result) =>
(result.data ?? [])
.filter((session) => !!session?.id)
.map((session) => ({
id: session.id,
title: session.title ?? props.untitled(),
description,
directory,
archived: session.time?.archived,
updated: session.time?.updated,
})),
)
.catch(() => [] as SessionEntryInput[])
}),
)
.then((results) => {
if (state.token !== current) return [] as CommandPaletteEntry[]
const seen = new Set<string>()
const next = results
.flat()
.filter((item) => {
const key = `${item.directory}:${item.id}`
if (seen.has(key)) return false
seen.add(key)
return true
})
.map((item) => createSessionEntry(item, props.category()))
state.cached = next
return next
})
.catch(() => [] as CommandPaletteEntry[])
.finally(() => {
state.inflight = undefined
})
return state.inflight
}
}
type SessionEntryInput = {
directory: string
id: string
title: string
description: string
archived?: number
updated?: number
}
function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry {
return {
id: `session:${input.directory}:${input.id}`,
type: "session",
title: input.title,
description: input.description,
category,
directory: input.directory,
sessionID: input.id,
archived: input.archived,
updated: input.updated,
}
}
@@ -0,0 +1,220 @@
.command-palette-v2 {
overflow: hidden;
}
/* Anchor to the top edge of where a centered 480px-tall dialog would sit, so the
top stays put while the content-driven height grows and shrinks. */
[data-component="dialog-v2"]:has(.command-palette-v2) {
align-items: flex-start;
}
[data-component="dialog-v2"]:has(.command-palette-v2) [data-slot="dialog-container"] {
width: min(calc(100vw - 24px), 640px);
height: auto;
min-height: 280px;
max-height: min(calc(100vh - 96px), 480px);
margin-top: max(48px, calc((100vh - 480px) / 2));
border-radius: 12px;
background: var(--v2-background-bg-base);
box-shadow: var(--v2-elevation-floating);
}
.command-palette-v2-body {
display: flex;
min-height: 0;
flex: 1;
flex-direction: column;
gap: 0;
padding: 0;
}
.command-palette-v2-search {
flex-shrink: 0;
padding: 6px;
}
.command-palette-v2-search [data-component="text-input-v2"] {
width: 100%;
height: 36px;
border-radius: 6px;
outline: 0;
background: color-mix(in srgb, var(--v2-background-bg-layer-02) 60%, transparent);
box-shadow: none;
transition:
background-color 120ms ease-in-out,
box-shadow 120ms ease-in-out;
}
.command-palette-v2-search [data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]),
.command-palette-v2-search [data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
background: var(--v2-background-bg-layer-02);
box-shadow: none;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
border-radius: 6px;
background: transparent;
gap: 8px;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
padding-left: 12px;
}
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
border-radius: 6px;
background: transparent;
}
.command-palette-v2-scroll {
min-height: 0;
flex: 1;
}
.command-palette-v2-results {
display: flex;
flex-direction: column;
gap: 16px;
padding: 6px 6px 8px;
}
.command-palette-v2-group {
display: flex;
flex-direction: column;
gap: 1px;
}
.command-palette-v2-group-title {
margin: 6px 0;
padding: 0 12px;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
user-select: none;
}
.command-palette-v2-row {
display: flex;
width: 100%;
height: 36px;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--v2-text-text-base);
text-align: left;
cursor: default;
scroll-margin: 6px 0;
}
.command-palette-v2-row[data-active] {
background: var(--v2-overlay-simple-overlay-hover);
}
.command-palette-v2-row:focus-visible {
background: var(--v2-overlay-simple-overlay-hover);
outline: none;
}
.command-palette-v2-row-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 8px;
}
.command-palette-v2-row-icon {
flex-shrink: 0;
color: var(--v2-icon-icon-muted);
}
.command-palette-v2-row-text {
display: flex;
min-width: 0;
align-items: center;
gap: 6px;
}
.command-palette-v2-title {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-base);
font-size: 13px;
font-weight: 530;
line-height: 16px;
letter-spacing: -0.04px;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-description,
.command-palette-v2-meta {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-meta {
flex-shrink: 0;
}
.command-palette-v2-file-path {
display: flex;
min-width: 0;
align-items: baseline;
font-size: 13px;
line-height: 16px;
letter-spacing: -0.04px;
}
.command-palette-v2-file-dir {
min-width: 0;
overflow: hidden;
color: var(--v2-text-text-muted);
font-weight: 440;
text-overflow: ellipsis;
white-space: nowrap;
}
.command-palette-v2-file-name {
flex-shrink: 0;
color: var(--v2-text-text-base);
font-weight: 530;
white-space: nowrap;
}
.command-palette-v2-state {
display: grid;
min-height: 120px;
place-items: center;
color: var(--v2-text-text-muted);
font-size: 13px;
font-weight: 440;
line-height: 16px;
letter-spacing: -0.04px;
}
@media (max-width: 640px) {
.command-palette-v2-row-text {
flex-direction: column;
align-items: flex-start;
gap: 1px;
}
.command-palette-v2-description {
max-width: 100%;
}
}
@@ -0,0 +1,221 @@
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
import { Icon } from "@opencode-ai/ui/v2/icon"
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
import { formatKeybindParts } from "@/context/command"
import { useLanguage } from "@/context/language"
import { getRelativeTime } from "@/utils/time"
import {
createCommandPaletteFileEntry,
createCommandPaletteModel,
uniqueCommandPaletteEntries,
type CommandPaletteEntry,
} from "./command-palette"
import "./dialog-command-palette-v2.css"
function groups(entries: CommandPaletteEntry[]) {
const map = new Map<string, CommandPaletteEntry[]>()
for (const entry of entries) map.set(entry.category, [...(map.get(entry.category) ?? []), entry])
return Array.from(map.entries()).map(([category, entries]) => ({ category, entries }))
}
function matchesEntry(entry: CommandPaletteEntry, query: string) {
const value = query.toLowerCase()
return [entry.title, entry.description, entry.category].some((text) => text?.toLowerCase().includes(value))
}
export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
const palette = createCommandPaletteModel(props)
const [query, setQuery] = createSignal("")
const [active, setActive] = createSignal(0)
const loadItems = async (text: string) => {
const q = text.trim()
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))])
const category = palette.language.t("palette.group.files")
return [
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
...nextSessions.filter((entry) => matchesEntry(entry, q)),
...files.map((path) => createCommandPaletteFileEntry(path, category)),
]
}
const [entries] = createResource(query, loadItems, { initialValue: [] as CommandPaletteEntry[] })
// Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
const groupedEntries = createMemo(() => groups(visibleEntries()))
const activeEntry = createMemo(() => visibleEntries()[active()])
createEffect(() => {
query()
visibleEntries()
setActive(0)
})
createEffect(() => {
palette.highlight(activeEntry())
})
let resultsRef: HTMLDivElement | undefined
const move = (delta: -1 | 1) => {
const count = visibleEntries().length
if (count === 0) return
setActive((index) => (index + delta + count) % count)
requestAnimationFrame(() => {
resultsRef?.querySelector("[data-active]")?.scrollIntoView({ block: "nearest" })
})
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "ArrowDown") {
event.preventDefault()
move(1)
return
}
if (event.key === "ArrowUp") {
event.preventDefault()
move(-1)
return
}
if (event.key === "Enter") {
event.preventDefault()
palette.select(activeEntry())
return
}
if (event.key === "Escape") {
event.preventDefault()
palette.close()
}
}
return (
<Dialog class="command-palette-v2" size="large">
<DialogBody class="command-palette-v2-body">
<div class="command-palette-v2-search">
<TextInputV2
value={query()}
autofocus
autocomplete="off"
spellcheck={false}
appearance="large"
placeholder={palette.language.t("palette.search.placeholder")}
leadingIcon={<Icon name="magnifying-glass" />}
onInput={(event) => setQuery(event.currentTarget.value)}
onKeyDown={handleKeyDown}
/>
</div>
<ScrollView class="command-palette-v2-scroll" viewportRef={(el) => (resultsRef = el)}>
<div class="command-palette-v2-results" role="listbox">
<Show
when={visibleEntries().length > 0}
fallback={
<div class="command-palette-v2-state">
{entries.loading ? palette.language.t("common.loading") : palette.language.t("palette.empty")}
</div>
}
>
<For each={groupedEntries()}>
{(group) => (
<div class="command-palette-v2-group">
<Show when={group.category}>
<div class="command-palette-v2-group-title">{group.category}</div>
</Show>
<For each={group.entries}>
{(item) => (
<PaletteRow
item={item}
active={activeEntry()?.id === item.id}
language={palette.language}
onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
onSelect={() => palette.select(item)}
/>
)}
</For>
</div>
)}
</For>
</Show>
</div>
</ScrollView>
</DialogBody>
</Dialog>
)
}
function PaletteRow(props: {
item: CommandPaletteEntry
active: boolean
language: ReturnType<typeof useLanguage>
onActive: () => void
onSelect: () => void
}) {
return (
<button
type="button"
class="command-palette-v2-row"
role="option"
aria-selected={props.active}
data-active={props.active ? "" : undefined}
onMouseMove={(event) => {
// Ignore hover from a static cursor when keyboard scrolling moves rows underneath it.
if (event.movementX === 0 && event.movementY === 0) return
props.onActive()
}}
onMouseDown={(event) => event.preventDefault()}
onClick={props.onSelect}
>
<Switch
fallback={
<div class="command-palette-v2-row-main">
<FileIcon node={{ path: props.item.path ?? "", type: "file" }} class="command-palette-v2-row-icon size-4" />
<div class="command-palette-v2-file-path">
<span class="command-palette-v2-file-dir">{getDirectory(props.item.path ?? "")}</span>
<span class="command-palette-v2-file-name">{getFilename(props.item.path ?? "")}</span>
</div>
</div>
}
>
<Match when={props.item.type === "command"}>
<div class="command-palette-v2-row-main">
<div class="command-palette-v2-row-text">
<span class="command-palette-v2-title">{props.item.title}</span>
<Show when={props.item.description}>
<span class="command-palette-v2-description">{props.item.description}</span>
</Show>
</div>
</div>
<Show when={props.item.keybind}>
<KeybindV2 keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
</Show>
</Match>
<Match when={props.item.type === "session"}>
<div class="command-palette-v2-row-main">
<Icon name="status" class="command-palette-v2-row-icon" />
<div class="command-palette-v2-row-text">
<span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
{props.item.title}
</span>
<Show when={props.item.description}>
<span class="command-palette-v2-description" classList={{ "opacity-70": !!props.item.archived }}>
{props.item.description}
</span>
</Show>
</div>
</div>
<Show when={props.item.updated}>
<span class="command-palette-v2-meta">
{getRelativeTime(new Date(props.item.updated!).toISOString(), props.language.t)}
</span>
</Show>
</Match>
</Switch>
</button>
)
}
@@ -1,397 +1,55 @@
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog" import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon" import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon" import { Icon } from "@opencode-ai/ui/icon"
import { Keybind } from "@opencode-ai/ui/keybind" import { Keybind } from "@opencode-ai/ui/keybind"
import { List } from "@opencode-ai/ui/list" import { List } from "@opencode-ai/ui/list"
import { base64Encode } from "@opencode-ai/core/util/encode"
import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
import { useNavigate } from "@solidjs/router" import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js" import { formatKeybind } from "@/context/command"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command" import { useServerSDK } from "@/context/server-sdk"
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { useLayout } from "@/context/layout"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
import { getRelativeTime } from "@/utils/time" import { getRelativeTime } from "@/utils/time"
import {
createCommandPaletteFileEntry,
createCommandPaletteFileOpener,
createCommandPaletteModel,
uniqueCommandPaletteEntries,
type CommandPaletteEntry,
} from "./command-palette"
import { DialogCommandPaletteV2 } from "./dialog-command-palette-v2"
const DialogSelectFileV2 = lazy(() => const DialogSelectFileV2 = lazy(() =>
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })), import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
) )
type EntryType = "command" | "file" | "session"
type Entry = {
id: string
type: EntryType
title: string
description?: string
keybind?: string
category: string
option?: CommandOption
path?: string
directory?: string
sessionID?: string
archived?: number
updated?: number
}
type DialogSelectFileMode = "all" | "files" type DialogSelectFileMode = "all" | "files"
const ENTRY_LIMIT = 5
const COMMON_COMMAND_IDS = [
"session.new",
"workspace.new",
"session.previous",
"session.next",
"terminal.toggle",
"review.toggle",
] as const
const uniqueEntries = (items: Entry[]) => {
const seen = new Set<string>()
const out: Entry[] = []
for (const item of items) {
if (seen.has(item.id)) continue
seen.add(item.id)
out.push(item)
}
return out
}
const createCommandEntry = (option: CommandOption, category: string): Entry => ({
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category,
option,
})
const createFileEntry = (path: string, category: string): Entry => ({
id: "file:" + path,
type: "file",
title: path,
category,
path,
})
const createSessionEntry = (
input: {
directory: string
id: string
title: string
description: string
archived?: number
updated?: number
},
category: string,
): Entry => ({
id: `session:${input.directory}:${input.id}`,
type: "session",
title: input.title,
description: input.description,
category,
directory: input.directory,
sessionID: input.id,
archived: input.archived,
updated: input.updated,
})
function createCommandEntries(props: {
filesOnly: () => boolean
command: ReturnType<typeof useCommand>
language: ReturnType<typeof useLanguage>
}) {
const allowed = createMemo(() => {
if (props.filesOnly()) return []
return props.command.options.filter(
(option) =>
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
)
})
const list = createMemo(() => {
const category = props.language.t("palette.group.commands")
return allowed().map((option) => createCommandEntry(option, category))
})
const picks = createMemo(() => {
const all = allowed()
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
const category = props.language.t("palette.group.commands")
return sorted.map((option) => createCommandEntry(option, category))
})
return { allowed, list, picks }
}
function createFileEntries(props: {
file: ReturnType<typeof useFile>
tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
language: ReturnType<typeof useLanguage>
}) {
const tabState = createSessionTabs({
tabs: props.tabs,
pathFromTab: props.file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? props.file.tab(tab) : tab),
})
const recent = createMemo(() => {
const all = tabState.openedTabs()
const active = tabState.activeFileTab()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const category = props.language.t("palette.group.files")
const items: Entry[] = []
for (const item of order) {
const path = props.file.pathFromTab(item)
if (!path) continue
if (seen.has(path)) continue
seen.add(path)
items.push(createFileEntry(path, category))
}
return items.slice(0, ENTRY_LIMIT)
})
const root = createMemo(() => {
const category = props.language.t("palette.group.files")
const nodes = props.file.tree.children("")
const paths = nodes
.filter((node) => node.type === "file")
.map((node) => node.path)
.sort((a, b) => a.localeCompare(b))
return paths.slice(0, ENTRY_LIMIT).map((path) => createFileEntry(path, category))
})
return { recent, root }
}
function createSessionEntries(props: {
workspaces: () => string[]
label: (directory: string) => string
serverSDK: ServerSDK
language: ReturnType<typeof useLanguage>
}) {
const state: {
token: number
inflight: Promise<Entry[]> | undefined
cached: Entry[] | undefined
} = {
token: 0,
inflight: undefined,
cached: undefined,
}
const sessions = (text: string) => {
const query = text.trim()
if (!query) {
state.token += 1
state.inflight = undefined
state.cached = undefined
return [] as Entry[]
}
if (state.cached) return state.cached
if (state.inflight) return state.inflight
const current = state.token
const dirs = props.workspaces()
if (dirs.length === 0) return [] as Entry[]
state.inflight = Promise.all(
dirs.map((directory) => {
const description = props.label(directory)
return props.serverSDK.client.session
.list({ directory, roots: true })
.then((x) =>
(x.data ?? [])
.filter((s) => !!s?.id)
.map((s) => ({
id: s.id,
title: s.title ?? props.language.t("command.session.new"),
description,
directory,
archived: s.time?.archived,
updated: s.time?.updated,
})),
)
.catch(
() =>
[] as {
id: string
title: string
description: string
directory: string
archived?: number
updated?: number
}[],
)
}),
)
.then((results) => {
if (state.token !== current) return [] as Entry[]
const seen = new Set<string>()
const category = props.language.t("command.category.session")
const next = results
.flat()
.filter((item) => {
const key = `${item.directory}:${item.id}`
if (seen.has(key)) return false
seen.add(key)
return true
})
.map((item) => createSessionEntry(item, category))
state.cached = next
return next
})
.catch(() => [] as Entry[])
.finally(() => {
state.inflight = undefined
})
return state.inflight
}
return { sessions }
}
export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) { export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
const command = useCommand()
const language = useLanguage()
const platform = usePlatform() const platform = usePlatform()
const settings = useSettings() const settings = useSettings()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const navigate = useNavigate()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
const { params, tabs, view } = useSessionLayout()
const filesOnly = () => props.mode === "files" const filesOnly = () => props.mode === "files"
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const commandEntries = createCommandEntries({ filesOnly, command, language })
const fileEntries = createFileEntries({ file, tabs, language })
const projectDirectory = createMemo(() => decode64(params.dir) ?? "") if (!filesOnly() && settings.general.newLayoutDesigns()) {
const project = createMemo(() => { return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
const directory = projectDirectory()
if (!directory) return
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
})
const workspaces = createMemo(() => {
const directory = projectDirectory()
const current = project()
if (!current) return directory ? [directory] : []
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
if (directory && !dirs.includes(directory)) return [...dirs, directory]
return dirs
})
const homedir = createMemo(() => serverSync().data.path.home)
const label = (directory: string) => {
const current = project()
const kind =
current && directory === current.worktree
? language.t("workspace.type.local")
: language.t("workspace.type.sandbox")
const [store] = serverSync().child(directory, { bootstrap: false })
const home = homedir()
const path = home ? directory.replace(home, "~") : directory
const name = store.vcs?.branch ?? getFilename(directory)
return `${kind} : ${name || path}`
} }
const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language })
const items = async (text: string) => {
const query = text.trim()
setGrouped(query.length > 0)
if (!query && filesOnly()) {
const loaded = file.tree.state("")?.loaded
const pending = loaded ? Promise.resolve() : file.tree.list("")
const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
if (loaded || next.length > 0) {
void pending
return next
}
await pending
return uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
}
if (!query) return [...commandEntries.picks(), ...fileEntries.recent()]
if (filesOnly()) {
const files = await file.searchFiles(query)
const category = language.t("palette.group.files")
return files.map((path) => createFileEntry(path, category))
}
const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))])
const category = language.t("palette.group.files")
const entries = files.map((path) => createFileEntry(path, category))
return [...commandEntries.list(), ...nextSessions, ...entries]
}
const handleMove = (item: Entry | undefined) => {
state.cleanup?.()
if (!item) return
if (item.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}
const open = (path: string) => {
const value = file.tab(path)
void tabs().open(value)
void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
props.onOpenFile?.(path)
tabs().setActive(value)
}
const handleSelect = (item: Entry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()
if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}
if (item.type === "session") {
if (!item.directory || !item.sessionID) return
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
return
}
if (!item.path) return
open(item.path)
}
onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) { if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
return <DialogSelectFileDesktopV2 onOpenFile={props.onOpenFile} />
}
return <DialogSelectFileLegacy filesOnly={filesOnly} onOpenFile={props.onOpenFile} />
}
function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) {
const language = useLanguage()
const serverSDK = useServerSDK()
const { params } = useSessionLayout()
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
return ( return (
<DialogSelectFileV2 <DialogSelectFileV2
server={serverSDK().server} server={serverSDK().server}
@@ -400,32 +58,71 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
title={language.t("session.header.searchFiles")} title={language.t("session.header.searchFiles")}
onSelect={(result) => { onSelect={(result) => {
if (typeof result !== "string") return if (typeof result !== "string") return
open(result) openFile(result)
}} }}
/> />
) )
} }
function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) {
const palette = createCommandPaletteModel(props)
const [grouped, setGrouped] = createSignal(false)
const items = async (text: string) => {
const query = text.trim()
setGrouped(query.length > 0)
if (!query && props.filesOnly()) {
const loaded = palette.file.tree.state("")?.loaded
const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
if (loaded || next.length > 0) {
void pending
return next
}
await pending
return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
}
if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
if (props.filesOnly()) {
const files = await palette.file.searchFiles(query)
const category = palette.language.t("palette.group.files")
return files.map((path) => createCommandPaletteFileEntry(path, category))
}
const [files, nextSessions] = await Promise.all([
palette.file.searchFiles(query),
Promise.resolve(palette.sessions(query)),
])
const category = palette.language.t("palette.group.files")
const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
return [...palette.commandEntries(), ...nextSessions, ...entries]
}
return ( return (
<Dialog class="pt-3 pb-0 !max-h-[480px]" transition> <Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
<List <List
class="px-3" class="px-3"
search={{ search={{
placeholder: filesOnly() placeholder: props.filesOnly()
? language.t("session.header.searchFiles") ? palette.language.t("session.header.searchFiles")
: language.t("palette.search.placeholder"), : palette.language.t("palette.search.placeholder"),
autofocus: true, autofocus: true,
hideIcon: true, hideIcon: true,
}} }}
emptyMessage={language.t("palette.empty")} emptyMessage={palette.language.t("palette.empty")}
loadingMessage={language.t("common.loading")} loadingMessage={palette.language.t("common.loading")}
items={items} items={items}
key={(item) => item.id} key={(item) => item.id}
filterKeys={["title", "description", "category"]} filterKeys={["title", "description", "category"]}
skipFilter={(item) => item.type === "file"} skipFilter={(item) => item.type === "file"}
groupBy={grouped() ? (item) => item.category : () => ""} groupBy={grouped() ? (item) => item.category : () => ""}
onMove={handleMove} onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
onSelect={handleSelect} onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
> >
{(item) => ( {(item) => (
<Switch <Switch
@@ -452,7 +149,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
</Show> </Show>
</div> </div>
<Show when={item.keybind}> <Show when={item.keybind}>
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", language.t)}</Keybind> <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", palette.language.t)}</Keybind>
</Show> </Show>
</div> </div>
</Match> </Match>
@@ -479,7 +176,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
</div> </div>
<Show when={item.updated}> <Show when={item.updated}>
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2"> <span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
{getRelativeTime(new Date(item.updated!).toISOString(), language.t)} {getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
</span> </span>
</Show> </Show>
</div> </div>
@@ -0,0 +1,173 @@
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
import { Icon } from "@opencode-ai/ui/v2/icon"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js"
import { useLocal } from "@/context/local"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { decode64 } from "@/utils/base64"
import { useLanguage } from "@/context/language"
import { ModelTooltip } from "./model-tooltip"
type ModelState = ReturnType<typeof useLocal>["model"]
export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (props) => {
const local = useLocal()
const model = props.model ?? local.model
const dialog = useDialog()
const directory = () => decode64(local.slug())
const providers = useProviders(directory)
const language = useLanguage()
const modelKey = (item: ReturnType<ModelState["list"]>[number]) => `${item.provider.id}:${item.id}`
const currentKey = createMemo(() => {
const c = model.current()
return c ? `${c.provider.id}:${c.id}` : undefined
})
const isFree = (item: ReturnType<ModelState["list"]>[number]) =>
item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)
const openProviders = (provider?: string) => {
void import("./dialog-connect-provider").then((x) => {
const controller = x.useProviderConnectController()
controller.select(provider)
void dialog.show(() => <x.DialogConnectProvider controller={controller} directory={directory} />)
})
}
const selectModel = (item: ReturnType<ModelState["list"]>[number]) => {
model.set({ modelID: item.id, providerID: item.provider.id }, { recent: true })
dialog.close()
}
// Focus starts on the dialog's close button, outside the list, so listen at the
// document level while the dialog is mounted instead of on the list container.
let listEl: HTMLDivElement | undefined
onMount(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return
if (!listEl) return
const buttons = Array.from(listEl.querySelectorAll<HTMLButtonElement>("button"))
if (buttons.length === 0) return
const index = buttons.indexOf(document.activeElement as HTMLButtonElement)
const next =
index < 0 ? (e.key === "ArrowDown" ? 0 : buttons.length - 1) : index + (e.key === "ArrowDown" ? 1 : -1)
buttons[(next + buttons.length) % buttons.length]?.focus()
e.preventDefault()
}
document.addEventListener("keydown", handleKeyDown)
onCleanup(() => document.removeEventListener("keydown", handleKeyDown))
})
return (
<DialogV2 containerClass="!h-[min(calc(100vh_-_16px),480px)] !w-[min(calc(100vw_-_16px),560px)]">
<DialogHeader closeLabel={language.t("common.close")}>
<DialogTitle>{language.t("dialog.model.select.title")}</DialogTitle>
</DialogHeader>
<div class="h-px w-full shrink-0 bg-v2-border-border-muted" />
<DialogBody class="min-h-0 flex-1 gap-0">
<ScrollView class="min-h-0 flex-1 w-full">
<div ref={listEl} class="flex min-h-full flex-col">
<div class="flex h-fit w-full flex-col items-start gap-0.5 px-3.5 pb-3.5 pt-3">
<div class="flex h-8 w-full flex-none select-none flex-row items-center gap-2 self-stretch px-2.5 pb-2 pt-1">
<div class="flex h-5 flex-none flex-row items-center p-0 font-[440] text-[13px] leading-5 tracking-[-0.04px] text-v2-text-text-faint [font-family:Inter,var(--font-family-sans)] [font-variant-numeric:tabular-nums] [font-variation-settings:'slnt'_0]">
{language.t("dialog.model.unpaid.freeModels.title")}
</div>
</div>
<For each={model.list()}>
{(item) => (
<TooltipV2
class="w-full"
placement="right-start"
gutter={6}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item)} v2 />}
>
<button
type="button"
class="flex w-full scroll-my-3.5 flex-row items-center gap-2 rounded-md px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
onClick={() => selectModel(item)}
>
<span class="min-w-0 truncate">{item.name}</span>
<Show when={isFree(item)}>
<Tag class="shrink-0">{language.t("model.tag.free")}</Tag>
</Show>
<Show when={item.latest}>
<Tag class="shrink-0">{language.t("model.tag.latest")}</Tag>
</Show>
<Show when={currentKey() === modelKey(item)}>
<Icon name="check" class="ml-auto size-4 shrink-0 text-v2-icon-icon-base" />
</Show>
</button>
</TooltipV2>
)}
</For>
</div>
<div class="flex w-full flex-col p-2.5 pt-0">
<div class="flex h-fit w-full flex-none grow-0 flex-col items-start gap-0.5 self-stretch rounded-lg bg-v2-background-bg-layer-02 p-1 shadow-[var(--v2-elevation-switch-off)]">
<div class="flex h-8 w-full flex-none select-none flex-row items-center gap-2 self-stretch px-2.5 py-1.5">
<div class="flex h-5 flex-none flex-row items-center p-0 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint [font-family:Inter,var(--font-family-sans)] [font-variant-numeric:tabular-nums] [font-variation-settings:'slnt'_0]">
{language.t("dialog.model.unpaid.addMore.title")}
</div>
</div>
<div class="flex w-full flex-col">
<For
each={[...providers.popular()].sort((a, b) => {
if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) {
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
}
return a.name.localeCompare(b.name)
})}
>
{(provider) => (
<button
type="button"
class="flex w-full scroll-my-3.5 flex-row items-center gap-2 rounded-[6px] px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
onClick={() => openProviders(provider.id)}
>
<ProviderIcon id={provider.id} class="size-4 shrink-0 text-v2-icon-icon-muted" />
<span class="min-w-0 truncate">{provider.name}</span>
<Show when={provider.id === "opencode"}>
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
{language.t("dialog.provider.opencode.tagline")}
</span>
<Tag class="shrink-0">{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={provider.id === "opencode-go"}>
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
{language.t("dialog.provider.opencodeGo.tagline")}
</span>
<Tag class="shrink-0">{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={provider.id === "anthropic"}>
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
{language.t("dialog.provider.anthropic.note")}
</span>
</Show>
</button>
)}
</For>
<button
type="button"
class="flex h-9 w-full scroll-my-3.5 flex-row items-center justify-start gap-2 rounded-[6px] px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
onClick={() => openProviders()}
>
<span class="flex size-4 shrink-0 items-center justify-center text-v2-icon-icon-muted">
<Icon name="dot-grid" size="small" />
</span>
<span class="min-w-0 truncate text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
{language.t("dialog.provider.viewAll")}
</span>
</button>
</div>
</div>
</div>
</div>
</ScrollView>
</DialogBody>
</DialogV2>
)
}
@@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Icon } from "@opencode-ai/ui/v2/icon" import { Icon } from "@opencode-ai/ui/v2/icon"
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2" import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { ModelTooltip } from "./model-tooltip" import { ModelTooltip } from "./model-tooltip"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
@@ -92,6 +93,7 @@ const ModelList: Component<{
class="w-full" class="w-full"
placement="right-start" placement="right-start"
gutter={12} gutter={12}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />} value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
> >
{node} {node}
@@ -446,11 +448,25 @@ export function ModelSelectorPopoverV2(props: {
<MenuV2.RadioGroup value={current()}> <MenuV2.RadioGroup value={current()}>
<For each={group.items}> <For each={group.items}>
{(item) => ( {(item) => (
<TooltipV2
class="w-full"
placement="right-start"
gutter={6}
openDelay={0}
value={
<ModelTooltip
model={item}
latest={item.latest}
free={isFree(item.provider.id, item.cost)}
v2
/>
}
>
<MenuV2.RadioItem <MenuV2.RadioItem
value={modelKey(item)} value={modelKey(item)}
data-option-key={modelKey(item)} data-option-key={modelKey(item)}
data-selected-model={current() === modelKey(item) ? true : undefined} data-selected-model={current() === modelKey(item) ? true : undefined}
class="scroll-my-6" class="scroll-my-6 w-full"
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }} classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
onMouseEnter={() => { onMouseEnter={() => {
setStore("active", modelKey(item)) setStore("active", modelKey(item))
@@ -458,7 +474,7 @@ export function ModelSelectorPopoverV2(props: {
}} }}
onSelect={() => selectModel(item)} onSelect={() => selectModel(item)}
> >
<span class="min-w-0 truncate">{item.name}</span> <span class="min-w-0 truncate leading-5">{item.name}</span>
<Show when={isFree(item.provider.id, item.cost)}> <Show when={isFree(item.provider.id, item.cost)}>
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2> <TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
</Show> </Show>
@@ -466,6 +482,7 @@ export function ModelSelectorPopoverV2(props: {
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2> <TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
</Show> </Show>
</MenuV2.RadioItem> </MenuV2.RadioItem>
</TooltipV2>
)} )}
</For> </For>
</MenuV2.RadioGroup> </MenuV2.RadioGroup>
@@ -201,6 +201,7 @@ export default function FileTree(props: {
kinds?: ReadonlyMap<string, Kind> kinds?: ReadonlyMap<string, Kind>
draggable?: boolean draggable?: boolean
onFileClick?: (file: FileNode) => void onFileClick?: (file: FileNode) => void
onFileDoubleClick?: (file: FileNode) => void
_filter?: Filter _filter?: Filter
_marks?: Set<string> _marks?: Set<string>
@@ -440,6 +441,7 @@ export default function FileTree(props: {
active={props.active} active={props.active}
draggable={props.draggable} draggable={props.draggable}
onFileClick={props.onFileClick} onFileClick={props.onFileClick}
onFileDoubleClick={props.onFileDoubleClick}
_filter={filter()} _filter={filter()}
_marks={marks()} _marks={marks()}
_deeps={deeps()} _deeps={deeps()}
@@ -462,6 +464,7 @@ export default function FileTree(props: {
as="button" as="button"
type="button" type="button"
onClick={() => props.onFileClick?.(node)} onClick={() => props.onFileClick?.(node)}
onDblClick={() => props.onFileDoubleClick?.(node)}
> >
<div class="w-4 shrink-0" /> <div class="w-4 shrink-0" />
<Switch> <Switch>
+132 -42
View File
@@ -1,54 +1,144 @@
import { Icon } from "@opencode-ai/ui/v2/icon" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { Popover } from "@opencode-ai/ui/popover" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { createSignal, Show } from "solid-js" import { createSignal, Show } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
import { usePlatform } from "@/context/platform"
import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
import { Persist, persisted } from "@/utils/persist"
const helpIcon = (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
data-slot="icon-svg"
>
<path
d="M6.94235 10.5714V10.4854C6.94617 9.76302 7.01879 9.18777 7.16022 8.75968C7.30546 8.33158 7.50804 7.98567 7.76796 7.72193C8.02787 7.45819 8.34321 7.21548 8.71397 6.99379C8.93948 6.85619 9.14206 6.69374 9.32171 6.50645C9.50518 6.31916 9.64851 6.10511 9.75171 5.86431C9.85874 5.62351 9.91225 5.35404 9.91225 5.0559C9.91225 4.69661 9.82625 4.38509 9.65424 4.12136C9.48607 3.85762 9.26055 3.65504 8.9777 3.51362C8.69486 3.36837 8.38143 3.29575 8.03743 3.29575C7.73165 3.29575 7.43733 3.35882 7.15448 3.48495C6.87546 3.61108 6.6423 3.80984 6.45501 4.08122C6.26772 4.3526 6.15878 4.70425 6.12821 5.13617H4.56299C4.59357 4.47109 4.76557 3.9054 5.07899 3.43908C5.39242 2.96894 5.80522 2.61156 6.31741 2.36694C6.83341 2.12231 7.40675 2 8.03743 2C8.72161 2 9.31789 2.13378 9.82625 2.40134C10.3384 2.66507 10.734 3.0301 11.0131 3.49642C11.2959 3.96273 11.4373 4.49976 11.4373 5.1075C11.4373 5.53177 11.3724 5.914 11.2424 6.25418C11.1124 6.59436 10.9251 6.89823 10.6805 7.16579C10.4397 7.43335 10.1492 7.67033 9.80905 7.87673C9.48033 8.08313 9.21468 8.301 9.0121 8.53034C8.80952 8.75585 8.66237 9.02341 8.57063 9.33302C8.4789 9.64262 8.42921 10.0268 8.42156 10.4854V10.5714H6.94235ZM7.72782 14C7.43351 14 7.17933 13.8949 6.96528 13.6847C6.75506 13.4744 6.64994 13.2203 6.64994 12.9221C6.64994 12.6278 6.75506 12.3755 6.96528 12.1653C7.17933 11.9551 7.43351 11.85 7.72782 11.85C8.02214 11.85 8.27441 11.9551 8.48463 12.1653C8.69868 12.3755 8.8057 12.6278 8.8057 12.9221C8.8057 13.1209 8.75601 13.3024 8.65663 13.4668C8.55726 13.6273 8.4273 13.7573 8.26676 13.8567C8.10623 13.9522 7.92658 14 7.72782 14Z"
fill="var(--v2-icon-icon-base)"
/>
</svg>
)
const triggerClass =
"size-7 !rounded-full shrink-0 bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]"
// TODO: wire to changelog / seen-state when available
const showPopover = () => true
export function HelpButton() { export function HelpButton() {
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */ const platform = usePlatform()
const [shown, setShown] = createSignal(false)
return ( return (
<Show when={!state.dismissed}> <a
<div class="fixed bottom-4 right-4 z-50 hidden md:block"> href="https://opencode.ai"
<Popover aria-label="Open the OpenCode website"
open={shown()} data-component="icon-button-v2"
onOpenChange={setShown} data-size="large"
triggerAs="button" class={`${triggerClass} fixed bottom-5 right-5 z-50 flex items-center justify-center`}
triggerProps={{ onClick={(event) => {
type: "button", event.preventDefault()
"aria-label": "Help", platform.openLink(event.currentTarget.href)
class:
"size-7 rounded-full bg-background-base shadow-[var(--shadow-lg-border-base)] flex items-center justify-center text-text-base hover:text-text-strong transition-colors",
}}
trigger={<span aria-hidden="true">?</span>}
class="[&_[data-slot=popover-body]]:p-0 w-[320px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
gutter={8}
placement="top-end"
>
<Show when={shown()}>
<div class="relative flex flex-col gap-1 w-[320px] p-4 rounded-xl bg-background-strong shadow-[var(--shadow-lg-border-base)]">
<button
type="button"
aria-label="Close"
class="absolute top-3.5 right-3.5 size-6 rounded-md flex items-center justify-center text-text-base hover:text-text-strong hover:bg-surface-raised-base-hover transition-colors"
onClick={() => {
setShown(false)
setState("dismissed", true)
}} }}
> >
<Icon name="xmark-small" /> {helpIcon}
</button> </a>
<span class="text-14-regular text-text-strong">Lorem ipsum dolor sit amet</span> )
<p class="text-12-regular text-text-weak"> }
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation. // can remove this after the tabs rollout has been out for a while
</p> export function TabsInfoPopup() {
</div> if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
</Show>
</Popover> const [state, setState] = persisted(Persist.global("tabsInfoPopup"), createStore({ dismissed: false }))
</div> // setState({ dismissed: false }) // for testing
</Show> const [drawerOpen, setDrawerOpen] = createSignal(false)
return (
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
<Show when={!state.dismissed}>
<div
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
aria-label="Introducing Tabs. A faster, more intuitive way to work."
>
<button
type="button"
aria-label="Dismiss Tabs information"
class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
onClick={() => setState("dismissed", true)}
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="white" />
</svg>
</button>
<button
type="button"
class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
onClick={() => {
setState("dismissed", true)
setDrawerOpen(true)
}}
>
<video
src={introducingTabsVideo}
class="absolute inset-0 h-full w-full object-cover"
loop
muted
autoplay
playsinline
aria-hidden="true"
onContextMenu={(event) => event.preventDefault()}
/>
<div class="absolute inset-x-0 bottom-0 flex w-full flex-col items-start gap-1.5 bg-[linear-gradient(180deg,rgba(0,0,0,0)_0%,#000000_100%)] px-3 py-5">
<p class="w-full select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-[#FFFFFF]">
Introducing Tabs
</p>
<p class="w-full select-none text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-[#808080]">
A faster, more intuitive way to work.
</p>
</div>
</button>
</div>
</Show>
<DrawerContent>
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
June 16
</p>
<DrawerClose
as={IconButtonV2}
type="button"
size="small"
variant="ghost-muted"
aria-label="Close"
icon={<IconV2 name="xmark-small" />}
/>
</div>
<div class="relative flex w-full flex-col items-start gap-6 p-8">
<p class="w-full shrink-0 self-stretch text-[21px] font-[610] leading-6 tracking-[-0.37px] tabular-nums text-v2-text-text-base">
Introducing Tabs Navigation.
</p>
<p class="w-full flex-1 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base">
We've introduced tabs as the primary navigation in OpenCode. Your most important session are now pinned at
the top of your screen at all times. No more hunting through menus or losing your place mid-session. Switch
contexts instantly, pick up exactly where you left off, and keep your focus where it belongs: on the
sessions.
</p>
</div>
</DrawerContent>
</Drawer>
) )
} }
+35 -2
View File
@@ -1,4 +1,4 @@
import { Show, type Component } from "solid-js" import { Show, type Component, type JSX } from "solid-js"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
type InputKey = "text" | "image" | "audio" | "video" | "pdf" type InputKey = "text" | "image" | "audio" | "video" | "pdf"
@@ -23,7 +23,18 @@ type ModelInfo = {
} }
} }
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => { function ModelTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
return (
<div class="flex min-w-0 items-center gap-4">
<span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
<span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
</div>
)
}
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean; v2?: boolean }> = (
props,
) => {
const language = useLanguage() const language = useLanguage()
const sourceName = (model: ModelInfo) => { const sourceName = (model: ModelInfo) => {
const value = `${model.id} ${model.name}`.toLowerCase() const value = `${model.id} ${model.name}`.toLowerCase()
@@ -51,6 +62,13 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
const suffix = tags.length ? ` (${tags.join(", ")})` : "" const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${sourceName(props.model)} ${props.model.name}${suffix}` return `${sourceName(props.model)} ${props.model.name}${suffix}`
} }
const name = () => {
const tags: Array<string> = []
if (props.latest) tags.push(language.t("model.tag.latest"))
if (props.free) tags.push(language.t("model.tag.free"))
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
return `${props.model.name}${suffix}`
}
const inputs = () => { const inputs = () => {
if (props.model.capabilities) { if (props.model.capabilities) {
const input = props.model.capabilities.input const input = props.model.capabilities.input
@@ -73,6 +91,21 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
: language.t("model.tooltip.reasoning.none") : language.t("model.tooltip.reasoning.none")
} }
const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() }) const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
const contextLimit = () => props.model.limit.context.toLocaleString(language.intl())
if (props.v2) {
return (
<div class="flex w-[180px] flex-col gap-2">
<ModelTooltipRow name={language.t("model.tooltip.model")} value={name()} />
<ModelTooltipRow name={language.t("model.tooltip.provider")} value={props.model.provider.name} />
<Show when={inputs()}>
{(value) => <ModelTooltipRow name={language.t("model.tooltip.inputs")} value={value()} />}
</Show>
<ModelTooltipRow name={language.t("model.tooltip.reasoning")} value={reasoning()} />
<ModelTooltipRow name={language.t("model.tooltip.context.label")} value={contextLimit()} />
</div>
)
}
return ( return (
<div class="flex flex-col gap-1 py-1"> <div class="flex flex-col gap-1 py-1">
@@ -1,8 +1,6 @@
// @ts-nocheck // @ts-nocheck
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import type { Todo } from "@opencode-ai/sdk/v2"
import { createPromptState } from "@/context/prompt" import { createPromptState } from "@/context/prompt"
import { SessionComposerRegion, createSessionComposerRegionController } from "@/pages/session/composer"
import { createPromptInputHistory, PromptInput } from "./prompt-input" import { createPromptInputHistory, PromptInput } from "./prompt-input"
function createPromptInputStoryRuntime() { function createPromptInputStoryRuntime() {
@@ -104,94 +102,6 @@ function PromptInputExample() {
) )
} }
const todos: Todo[] = [
{ id: "todo-1", content: "Inspect the session composer animation", status: "completed" },
{ id: "todo-2", content: "Keep the dock settled on initial render", status: "in_progress" },
{ id: "todo-3", content: "Verify session navigation behavior", status: "pending" },
]
function PromptInputWithOpenDock() {
const input = createPromptInputStoryRuntime()
const [controls, setControls] = createStore({
agent: "build",
activeTab: undefined as string | undefined,
todoCollapsed: false,
})
const inputControls = {
agents: {
available: [],
options: ["build"],
get current() {
return controls.agent
},
loading: false,
visible: true,
select: (agent?: string) => setControls("agent", agent ?? "build"),
},
model: {
selection: {
current: () => ({ id: "claude-3-7-sonnet", name: "Claude 3.7 Sonnet", provider: { id: "anthropic" } }),
variant: { list: () => [], current: () => undefined, set: () => {} },
},
paid: true,
loading: false,
},
session: {
id: "story-session",
tabs: {
active: () => controls.activeTab,
all: () => [],
open: () => {},
setActive: (tab: string) => setControls("activeTab", tab),
},
reviewPanel: { opened: () => false, open: () => {} },
},
newLayoutDesigns: true,
}
const state = {
blocked: () => false,
questionRequest: () => undefined,
permissionRequest: () => undefined,
permissionResponding: () => false,
decide: () => {},
todos: () => todos,
dock: () => true,
closing: () => false,
opening: () => false,
}
return (
<SessionComposerRegion
controller={createSessionComposerRegionController({
state,
sessionKey: () => "story-session",
sessionID: () => "story-session",
prompt: input.state,
ready: () => true,
centered: () => false,
todo: {
collapsed: () => controls.todoCollapsed,
onToggle: () => setControls("todoCollapsed", (collapsed) => !collapsed),
},
followup: () => undefined,
revert: () => undefined,
onResponseSubmit: () => {},
openParent: () => {},
setPromptRef: () => {},
setDockRef: () => {},
})}
promptInput={
<PromptInput
controls={inputControls}
{...input}
ref={() => {}}
newSessionWorktree=""
onNewSessionWorktreeReset={() => {}}
/>
}
/>
)
}
export default { export default {
title: "App/PromptInput", title: "App/PromptInput",
id: "app-prompt-input", id: "app-prompt-input",
@@ -206,12 +116,3 @@ export const Basic = {
</div> </div>
), ),
} }
export const DockAlreadyOpen = {
render: () => (
<div class="pt-10">
<h1 class="mb-4">Prompt Input with open Todo dock</h1>
<PromptInputWithOpenDock />
</div>
),
}
+127 -18
View File
@@ -43,6 +43,8 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
import { Select } from "@opencode-ai/ui/select" import { Select } from "@opencode-ai/ui/select"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { ModelSelectorPopover, ModelSelectorPopoverV2 } from "@/components/dialog-select-model" import { ModelSelectorPopover, ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
import { DialogSelectModelUnpaidV2 } from "@/components/dialog-select-model-unpaid-v2"
import { useCommand } from "@/context/command" import { useCommand } from "@/context/command"
import { Persist, persisted } from "@/utils/persist" import { Persist, persisted } from "@/utils/persist"
import { usePermission } from "@/context/permission" import { usePermission } from "@/context/permission"
@@ -520,7 +522,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const setMode = (mode: "normal" | "shell") => { const setMode = (mode: "normal" | "shell") => {
setStore("mode", mode) setStore("mode", mode)
setStore("popover", null) setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
requestAnimationFrame(() => editorRef?.focus()) requestAnimationFrame(() => editorRef?.focus())
} }
@@ -554,7 +556,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}, },
]) ])
const closePopover = () => setStore("popover", null) const closePopover = () => setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
const resetHistoryNavigation = (force = false) => { const resetHistoryNavigation = (force = false) => {
if (!force && (store.historyIndex < 0 || store.applyingHistory)) return if (!force && (store.historyIndex < 0 || store.applyingHistory)) return
@@ -800,17 +802,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const handleSlashSelect = (cmd: SlashCommand | undefined) => { const handleSlashSelect = (cmd: SlashCommand | undefined) => {
if (!cmd) return if (!cmd) return
const menu = store.slashMenu
closePopover() closePopover()
const images = imageAttachments() const images = imageAttachments()
if (cmd.type === "custom") { if (cmd.type === "custom") {
const text = `/${cmd.trigger} ` const text = `/${cmd.trigger} `
if (menu) {
editorRef.focus()
setCursorPosition(editorRef, 0)
addPart({ type: "text", content: text, start: 0, end: text.length })
focusEditorEnd()
return
}
setEditorText(text) setEditorText(text)
prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length) prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length)
focusEditorEnd() focusEditorEnd()
return return
} }
if (menu) {
command.trigger(cmd.id, "slash")
return
}
clearEditor() clearEditor()
prompt.set([...DEFAULT_PROMPT, ...images], 0) prompt.set([...DEFAULT_PROMPT, ...images], 0)
command.trigger(cmd.id, "slash") command.trigger(cmd.id, "slash")
@@ -1072,10 +1087,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (atMatch) { if (atMatch) {
atOnInput(atMatch[1]) atOnInput(atMatch[1])
setStore("popover", "at") setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
} else if (slashMatch) { } else if (slashMatch) {
slashOnInput(slashMatch[1]) slashOnInput(slashMatch[1])
setStore("popover", "slash") setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
} else { } else {
closePopover() closePopover()
} }
@@ -1171,6 +1186,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return true return true
} }
const openCommands = () => {
const populated = prompt.dirty() || commentCount() > 0
requestAnimationFrame(() => {
if (!populated) {
if (!addPart({ type: "text", content: "/", start: 0, end: 0 })) return
slashOnInput("")
setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
return
}
slashOnInput("")
setStore({ popover: "slash", slashMenu: true, slashMenuQuery: "" })
})
}
const openContext = () => {
requestAnimationFrame(() => {
if (!addPart({ type: "text", content: "@", start: 0, end: 0 })) return
atOnInput("")
setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
})
}
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => { const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
history.add(prompt, mode, mode === "shell" ? [] : historyComments()) history.add(prompt, mode, mode === "shell" ? [] : historyComments())
} }
@@ -1199,7 +1236,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
} }
setStore("mode", "normal") setStore("mode", "normal")
setStore("popover", null) closePopover()
setStore("historyIndex", -1) setStore("historyIndex", -1)
setStore("savedPrompt", null) setStore("savedPrompt", null)
prompt.set(edit.prompt, promptLength(edit.prompt)) prompt.set(edit.prompt, promptLength(edit.prompt))
@@ -1286,7 +1323,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
resetHistoryNavigation(true) resetHistoryNavigation(true)
}, },
setMode: (mode) => setStore("mode", mode), setMode: (mode) => setStore("mode", mode),
setPopover: (popover) => setStore("popover", popover), setPopover: (popover) => {
if (!popover) return closePopover()
setStore({ popover, slashMenu: false, slashMenuQuery: "" })
},
newSessionWorktree: () => props.newSessionWorktree, newSessionWorktree: () => props.newSessionWorktree,
onNewSessionWorktreeReset: props.onNewSessionWorktreeReset, onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
shouldQueue: props.shouldQueue, shouldQueue: props.shouldQueue,
@@ -1325,7 +1365,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const cursorPosition = getCursorPosition(editorRef) const cursorPosition = getCursorPosition(editorRef)
if (cursorPosition === 0) { if (cursorPosition === 0) {
setStore("mode", "shell") setStore("mode", "shell")
setStore("popover", null) closePopover()
event.preventDefault() event.preventDefault()
return return
} }
@@ -1460,6 +1500,29 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
} }
} }
const handleSlashMenuKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
closePopover()
requestAnimationFrame(() => editorRef.focus())
event.preventDefault()
return
}
if (event.key === "Tab") {
selectPopoverActive()
event.preventDefault()
return
}
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
const nav = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter"
const ctrlNav = ctrl && (event.key === "n" || event.key === "p")
if (!nav && !ctrlNav) return
slashOnKeyDown(event)
if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) scrollSlashActiveIntoView()
event.preventDefault()
}
const agentsLoading = () => props.controls.agents.loading const agentsLoading = () => props.controls.agents.loading
const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading()) const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading())
const providersLoading = () => props.controls.model.loading const providersLoading = () => props.controls.model.loading
@@ -1488,9 +1551,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
style: control(), style: control(),
onClose: restoreFocus, onClose: restoreFocus,
onUnpaidClick: () => { onUnpaidClick: () => {
void import("@/components/dialog-select-model-unpaid").then((x) => { if (props.controls.newLayoutDesigns) {
dialog.show(() => <x.DialogSelectModelUnpaid model={props.controls.model.selection} />) dialog.show(() => <DialogSelectModelUnpaidV2 model={props.controls.model.selection} />)
}) return
}
dialog.show(() => <DialogSelectModelUnpaid model={props.controls.model.selection} />)
}, },
})) }))
@@ -1527,6 +1592,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
slashActive={slashActive() ?? undefined} slashActive={slashActive() ?? undefined}
setSlashActive={setSlashActive} setSlashActive={setSlashActive}
onSlashSelect={handleSlashSelect} onSlashSelect={handleSlashSelect}
slashMenu={store.slashMenu}
slashMenuQuery={store.slashMenuQuery}
onSlashMenuInput={(value) => {
setStore("slashMenuQuery", value)
slashOnInput(value)
}}
onSlashMenuKeyDown={handleSlashMenuKeyDown}
commandKeybind={command.keybind} commandKeybind={command.keybind}
commandKeybindParts={command.keybindParts} commandKeybindParts={command.keybindParts}
newLayoutDesigns={props.controls.newLayoutDesigns} newLayoutDesigns={props.controls.newLayoutDesigns}
@@ -1625,23 +1697,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
placement="top" placement="top"
value={ value={
<> <>
{language.t("prompt.action.attachFile")} {language.t("prompt.menu.addImagesAndFiles")}
<KeybindV2 keys={command.keybindParts("file.attach")} variant="neutral" /> <KeybindV2 keys={command.keybindParts("file.attach")} variant="neutral" />
</> </>
} }
> >
<IconButton <MenuV2 gutter={6} modal={false} placement="top-start">
<MenuV2.Trigger
as={IconButton}
data-action="prompt-attach" data-action="prompt-attach"
type="button" type="button"
icon="plus" icon="plus"
variant="ghost" variant="ghost"
class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted" class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
style={buttons()} style={buttons()}
onClick={pick}
disabled={store.mode !== "normal"} disabled={store.mode !== "normal"}
tabIndex={store.mode === "normal" ? undefined : -1} tabIndex={store.mode === "normal" ? undefined : -1}
aria-label={language.t("prompt.action.attachFile")} aria-label={language.t("prompt.menu.addImagesAndFiles")}
/> />
<MenuV2.Portal>
<MenuV2.Content
class="[&_[data-slot=menu-v2-item-shortcut]]:w-5 [&_[data-slot=menu-v2-item-shortcut]]:justify-center"
style={{ "min-width": "180px" }}
>
<MenuV2.Item onSelect={pick} shortcut={command.keybind("file.attach")}>
{language.t("prompt.menu.imagesAndFiles")}
</MenuV2.Item>
<MenuV2.Separator />
<MenuV2.Item onSelect={openCommands} shortcut="/">
{language.t("prompt.menu.commands")}
</MenuV2.Item>
<MenuV2.Item onSelect={openContext} shortcut="@">
{language.t("prompt.menu.context")}
</MenuV2.Item>
<MenuV2.Item onSelect={() => setMode("shell")} shortcut="!">
{language.t("prompt.menu.shellCommand")}
</MenuV2.Item>
</MenuV2.Content>
</MenuV2.Portal>
</MenuV2>
</TooltipV2> </TooltipV2>
<Show when={showAgentControl()}> <Show when={showAgentControl()}>
<ComposerAgentControl state={agentControlState()} /> <ComposerAgentControl state={agentControlState()} />
@@ -1682,7 +1776,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
class="max-w-[160px] justify-start capitalize" class="max-w-[160px] justify-start capitalize"
style={control()} style={control()}
> >
<span class="truncate"> <span class="truncate leading-5">
{props.controls.model.selection.variant.current() ?? language.t("common.default")} {props.controls.model.selection.variant.current() ?? language.t("common.default")}
</span> </span>
<span class="-ml-0.5 -mr-1 flex shrink-0"> <span class="-ml-0.5 -mr-1 flex shrink-0">
@@ -1969,11 +2063,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
class="min-w-0 max-w-[320px] text-13-regular text-text-base group" class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
style={control()} style={control()}
onClick={() => { onClick={() => {
void import("@/components/dialog-select-model-unpaid").then((x) => {
dialog.show(() => ( dialog.show(() => (
<x.DialogSelectModelUnpaid model={props.controls.model.selection} /> <DialogSelectModelUnpaid model={props.controls.model.selection} />
)) ))
})
}} }}
> >
<Show when={props.controls.model.selection.current()?.provider?.id}> <Show when={props.controls.model.selection.current()?.provider?.id}>
@@ -2140,6 +2232,9 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
</> </>
} }
> >
<Show
when={props.state.newLayoutDesigns}
fallback={
<Button <Button
data-action="prompt-model" data-action="prompt-model"
as="div" as="div"
@@ -2164,6 +2259,20 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" /> <Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
</span> </span>
</Button> </Button>
}
>
<ButtonV2
data-action="prompt-model"
variant="ghost-muted"
size="normal"
class="min-w-0 max-w-[220px] justify-start ![font-weight:440] group"
classList={{ "animate-in fade-in": props.state.shouldAnimate }}
style={props.state.style}
onClick={props.state.onUnpaidClick}
>
<ModelControlContent state={props.state} v2 />
</ButtonV2>
</Show>
</TooltipV2> </TooltipV2>
} }
> >
@@ -41,6 +41,10 @@ type PromptPopoverProps = {
slashActive?: string slashActive?: string
setSlashActive: (id: string) => void setSlashActive: (id: string) => void
onSlashSelect: (item: SlashCommand) => void onSlashSelect: (item: SlashCommand) => void
slashMenu: boolean
slashMenuQuery: string
onSlashMenuInput: (value: string) => void
onSlashMenuKeyDown: (event: KeyboardEvent) => void
commandKeybind: (id: string) => string | undefined commandKeybind: (id: string) => string | undefined
commandKeybindParts: (id: string) => string[] commandKeybindParts: (id: string) => string[]
newLayoutDesigns: boolean newLayoutDesigns: boolean
@@ -254,6 +258,20 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
</Show> </Show>
</Match> </Match>
<Match when={props.popover === "slash"}> <Match when={props.popover === "slash"}>
<Show when={props.slashMenu}>
<div class="px-2 py-1">
<input
ref={(el) => requestAnimationFrame(() => el.focus())}
value={props.slashMenuQuery}
onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)}
onKeyDown={props.onSlashMenuKeyDown}
onMouseDown={(event) => event.stopPropagation()}
aria-label={props.t("prompt.menu.commands")}
placeholder="/"
class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint"
/>
</div>
</Show>
<Show <Show
when={props.slashFlat.length > 0} when={props.slashFlat.length > 0}
fallback={ fallback={
@@ -221,8 +221,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const sessionID = params.id const sessionID = params.id
if (!sessionID) return Promise.resolve() if (!sessionID) return Promise.resolve()
serverSync().session.set("todo", sessionID, [])
input.onAbort?.() input.onAbort?.()
const key = pendingKey(sessionID) const key = pendingKey(sessionID)
@@ -4,6 +4,8 @@ import type { PromptHistoryEntry } from "./history"
export type PromptInputTransientState = { export type PromptInputTransientState = {
popover: "at" | "slash" | null popover: "at" | "slash" | null
slashMenu: boolean
slashMenuQuery: string
historyIndex: number historyIndex: number
savedPrompt: PromptHistoryEntry | null savedPrompt: PromptHistoryEntry | null
placeholder: number placeholder: number
@@ -16,6 +18,8 @@ export type PromptInputTransientState = {
function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) { function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) {
setStore({ setStore({
popover: null, popover: null,
slashMenu: false,
slashMenuQuery: "",
historyIndex: -1, historyIndex: -1,
savedPrompt: null, savedPrompt: null,
draggingType: null, draggingType: null,
@@ -28,6 +32,8 @@ function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTr
export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) { export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) {
const [store, setStore] = createStore<PromptInputTransientState>({ const [store, setStore] = createStore<PromptInputTransientState>({
popover: null, popover: null,
slashMenu: false,
slashMenuQuery: "",
historyIndex: -1, historyIndex: -1,
savedPrompt: null, savedPrompt: null,
placeholder, placeholder,
@@ -4,6 +4,7 @@ import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { createMediaQuery } from "@solid-primitives/media"
import { useFile } from "@/context/file" import { useFile } from "@/context/file"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
@@ -14,6 +15,7 @@ import { useSDK } from "@/context/sdk"
import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics" import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers" import { createSessionTabs } from "@/pages/session/helpers"
import { useSettings } from "@/context/settings"
interface SessionContextUsageProps { interface SessionContextUsageProps {
variant?: "button" | "indicator" variant?: "button" | "indicator"
@@ -47,8 +49,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const layout = useLayout() const layout = useLayout()
const language = useLanguage() const language = useLanguage()
const sdk = useSDK() const sdk = useSDK()
const settings = useSettings()
const providers = useProviders(() => sdk().directory) const providers = useProviders(() => sdk().directory)
const { params, tabs, view } = useSessionLayout() const { params, tabs, view } = useSessionLayout()
const isDesktop = createMediaQuery("(min-width: 768px)")
const variant = createMemo(() => props.variant ?? "button") const variant = createMemo(() => props.variant ?? "button")
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default") const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
@@ -56,6 +60,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
tabs, tabs,
pathFromTab: file.pathFromTab, pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab), normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id,
}) })
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : [])) const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined)) const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
@@ -24,6 +24,7 @@ import { focusTerminalById } from "@/pages/session/helpers"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { messageAgentColor } from "@/utils/agent" import { messageAgentColor } from "@/utils/agent"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
import { fileManagerApp } from "@/utils/file-manager"
import { Persist, persisted } from "@/utils/persist" import { Persist, persisted } from "@/utils/persist"
import { StatusPopover, StatusPopoverV2 } from "../status-popover" import { StatusPopover, StatusPopoverV2 } from "../status-popover"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
@@ -175,11 +176,7 @@ export function SessionHeader() {
return LINUX_APPS return LINUX_APPS
}) })
const fileManager = createMemo(() => { const fileManager = createMemo(() => fileManagerApp(os()))
if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const }
if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const }
return { label: "session.header.open.fileManager", icon: "finder" as const }
})
createEffect(() => { createEffect(() => {
if (platform.platform !== "desktop") return if (platform.platform !== "desktop") return
@@ -10,7 +10,7 @@ import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useCommand } from "@/context/command" import { useCommand } from "@/context/command"
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element { export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
return ( return (
<div class="flex items-center gap-x-1.5 min-w-0"> <div class="flex items-center gap-x-1.5 min-w-0">
<Show <Show
@@ -22,12 +22,19 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
<FileIcon node={{ path: props.path, type: "file" }} mono class="absolute inset-0 size-4 tab-fileicon-mono" /> <FileIcon node={{ path: props.path, type: "file" }} mono class="absolute inset-0 size-4 tab-fileicon-mono" />
</span> </span>
</Show> </Show>
<span class="text-14-medium truncate">{getFilename(props.path)}</span> <span class="text-14-medium truncate" classList={{ italic: props.temporary }}>
{getFilename(props.path)}
</span>
</div> </div>
) )
} }
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element { export function SortableTab(props: {
tab: string
temporary?: boolean
onTabClose: (tab: string) => void
onTabDoubleClick?: (tab: string) => void
}): JSX.Element {
const file = useFile() const file = useFile()
const language = useLanguage() const language = useLanguage()
const command = useCommand() const command = useCommand()
@@ -36,7 +43,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
const content = createMemo(() => { const content = createMemo(() => {
const value = path() const value = path()
if (!value) return if (!value) return
return <FileVisual path={value} /> return <FileVisual path={value} temporary={props.temporary} />
}) })
return ( return (
<div use:sortable class="h-full flex items-center" classList={{ "opacity-0": sortable.isActiveDraggable }}> <div use:sortable class="h-full flex items-center" classList={{ "opacity-0": sortable.isActiveDraggable }}>
@@ -61,6 +68,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
} }
hideCloseButton hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)} onMiddleClick={() => props.onTabClose(props.tab)}
onDblClick={() => props.onTabDoubleClick?.(props.tab)}
> >
<Show when={content()}>{(value) => value()}</Show> <Show when={content()}>{(value) => value()}</Show>
</Tabs.Trigger> </Tabs.Trigger>
@@ -4,7 +4,7 @@ import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
export function useSettingsDialog() { export function useSettingsDialog(defaultValue?: string) {
const dialog = useDialog() const dialog = useDialog()
const params = useParams<{ id?: string }>() const params = useParams<{ id?: string }>()
let run = 0 let run = 0
@@ -19,7 +19,7 @@ export function useSettingsDialog() {
const sessionID = params.id const sessionID = params.id
void import("@/components/settings-v2").then((module) => { void import("@/components/settings-v2").then((module) => {
if (dead || run !== current) return if (dead || run !== current) return
void dialog.show(() => <module.DialogSettings sessionID={sessionID} />) void dialog.show(() => <module.DialogSettings sessionID={sessionID} defaultValue={defaultValue} />)
}) })
} }
} }
@@ -440,11 +440,6 @@ export const SettingsGeneral: Component = () => {
value={(o) => o.value} value={(o) => o.value}
label={(o) => o.label} label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)} onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
variant="secondary" variant="secondary"
size="small" size="small"
triggerVariant="settings" triggerVariant="settings"
@@ -471,11 +466,6 @@ export const SettingsGeneral: Component = () => {
if (!option) return if (!option) return
theme.setTheme(option.id) theme.setTheme(option.id)
}} }}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
variant="secondary" variant="secondary"
size="small" size="small"
triggerVariant="settings" triggerVariant="settings"
@@ -422,11 +422,6 @@ export const SettingsGeneralV2: Component<{
value={(o) => o.value} value={(o) => o.value}
label={(o) => o.label} label={(o) => o.label}
onSelect={(option) => option && theme.setColorScheme(option.value)} onSelect={(option) => option && theme.setColorScheme(option.value)}
onHighlight={(option) => {
if (!option) return
theme.previewColorScheme(option.value)
return () => theme.cancelPreview()
}}
/> />
</SettingsRowV2> </SettingsRowV2>
@@ -454,11 +449,6 @@ export const SettingsGeneralV2: Component<{
if (!option) return if (!option) return
theme.setTheme(option.id) theme.setTheme(option.id)
}} }}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
/> />
</SettingsRowV2> </SettingsRowV2>
@@ -246,18 +246,23 @@ export function TabNavItem(props: {
}} }}
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]" class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
> >
<Show when={props.session()}> <span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
<Show
when={props.session()}
fallback={
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
}
>
{(session) => ( {(session) => (
<span data-slot="project-avatar-slot">
<SessionTabAvatar <SessionTabAvatar
project={project()} project={project()}
directory={session().directory} directory={session().directory}
sessionId={session().id} sessionId={session().id}
server={props.server} server={props.server}
/> />
</span>
)} )}
</Show> </Show>
</span>
<span <span
ref={(el) => { ref={(el) => {
titleEl = el titleEl = el
@@ -40,6 +40,7 @@ function SessionTabSlot(props: {
let ref!: HTMLDivElement let ref!: HTMLDivElement
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null) const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId)) const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
const persisted = createMemo(() => tabs.info[props.id])
const [loadedSession] = createResource( const [loadedSession] = createResource(
() => { () => {
const ctx = props.serverCtx() const ctx = props.serverCtx()
@@ -71,8 +72,10 @@ function SessionTabSlot(props: {
createEffect(() => { createEffect(() => {
const value = session() const value = session()
if (!value) return
tabs.rememberSessionInfo(props.tab, value)
const current = sdk() const current = sdk()
if (!value || !current) return if (!current) return
createTabPromptState(tabs, props.tab, current.scope, { createTabPromptState(tabs, props.tab, current.scope, {
dir: base64Encode(value.directory), dir: base64Encode(value.directory),
id: value.id, id: value.id,
@@ -86,7 +89,7 @@ function SessionTabSlot(props: {
data-tab-key={props.id} data-tab-key={props.id}
data-active={props.active()} data-active={props.active()}
class="relative flex w-56 min-w-7 max-w-56 flex-shrink" class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
classList={{ hidden: !session() && !missingSession() }} classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
> >
<TabNavItem <TabNavItem
ref={(el) => { ref={(el) => {
@@ -95,7 +98,7 @@ function SessionTabSlot(props: {
href={tabHref(props.tab)} href={tabHref(props.tab)}
server={props.tab.server} server={props.tab.server}
session={session} session={session}
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined} fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
onTitleChange={(title) => { onTitleChange={(title) => {
const value = session() const value = session()
const ctx = props.serverCtx() const ctx = props.serverCtx()
+119
View File
@@ -0,0 +1,119 @@
/**
* Taken from https://www.solid-ui.com/docs/components/drawer
* Only used in one place hence not a v2 component yet... can be promoted to ui/v2 later
*/
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
import { splitProps } from "solid-js"
import type { ContentProps, DescriptionProps, DynamicProps, LabelProps, OverlayProps } from "@corvu/drawer"
import DrawerPrimitive from "@corvu/drawer"
const Drawer = DrawerPrimitive
const DrawerTrigger = DrawerPrimitive.Trigger
const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
type DrawerOverlayProps<T extends ValidComponent = "div"> = OverlayProps<T> & { class?: string }
const DrawerOverlay = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerOverlayProps<T>>) => {
const [, rest] = splitProps(props as DrawerOverlayProps, ["class"])
const drawerContext = DrawerPrimitive.useContext()
const overlayStyle = () => {
const state = drawerContext.transitionState()
if (state === "opening" || state === "closing") return undefined
const open = drawerContext.openPercentage()
return {
opacity: open,
"backdrop-filter": `blur(${4 * open}px)`,
}
}
return (
<DrawerPrimitive.Overlay
class={props.class}
classList={{
"fixed inset-0 z-[100] bg-v2-overlay-simple-overlay-scrim opacity-0 backdrop-blur-none transition-[opacity,backdrop-filter] duration-300 data-[opening]:opacity-100 data-[opening]:backdrop-blur-[4px] data-[closing]:opacity-0 data-[closing]:backdrop-blur-none": true,
}}
style={overlayStyle()}
{...rest}
/>
)
}
type DrawerContentProps<T extends ValidComponent = "div"> = ContentProps<T> & {
class?: string
children?: JSX.Element
}
const DrawerContent = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerContentProps<T>>) => {
const [, rest] = splitProps(props as DrawerContentProps, ["class", "children"])
return (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
class={props.class}
classList={{
"group/drawer-content fixed inset-y-[6px] right-[6px] left-auto z-[100] flex h-auto max-h-[calc(100vh-12px)] w-[560px] max-w-[calc(100vw-12px)] flex-col items-start rounded-[8px] bg-v2-background-bg-base p-0 shadow-[var(--v2-elevation-overlay)] data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none": true,
}}
{...rest}
>
{props.children}
</DrawerPrimitive.Content>
</DrawerPortal>
)
}
const DrawerHeader: Component<ComponentProps<"div">> = (props) => {
const [, rest] = splitProps(props, ["class"])
return <div class={props.class} classList={{ "grid gap-1.5 p-4 text-center sm:text-left": true }} {...rest} />
}
const DrawerFooter: Component<ComponentProps<"div">> = (props) => {
const [, rest] = splitProps(props, ["class"])
return <div class={props.class} classList={{ "mt-auto flex flex-col gap-2 p-4": true }} {...rest} />
}
type DrawerTitleProps<T extends ValidComponent = "div"> = LabelProps<T> & { class?: string }
const DrawerTitle = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerTitleProps<T>>) => {
const [, rest] = splitProps(props as DrawerTitleProps, ["class"])
return (
<DrawerPrimitive.Label
class={props.class}
classList={{ "text-base font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base": true }}
{...rest}
/>
)
}
type DrawerDescriptionProps<T extends ValidComponent = "div"> = DescriptionProps<T> & {
class?: string
}
const DrawerDescription = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerDescriptionProps<T>>) => {
const [, rest] = splitProps(props as DrawerDescriptionProps, ["class"])
return (
<DrawerPrimitive.Description
class={props.class}
classList={{
"text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-v2-text-text-muted": true,
}}
{...rest}
/>
)
}
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}
+17 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { upsertCommandRegistration } from "./command" import { resolveKeybindOption, upsertCommandRegistration } from "./command"
describe("upsertCommandRegistration", () => { describe("upsertCommandRegistration", () => {
test("replaces keyed registrations", () => { test("replaces keyed registrations", () => {
@@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
expect(next[1]?.options).toBe(one) expect(next[1]?.options).toBe(one)
}) })
}) })
describe("resolveKeybindOption", () => {
test("prefers a matching contextual command over the global fallback", () => {
const fallback = { id: "tab.close", title: "Close tab" }
const contextual = { id: "terminal.close", title: "Close terminal", when: () => true }
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(contextual)
})
test("uses the global fallback outside the command context", () => {
const fallback = { id: "tab.close", title: "Close tab" }
const contextual = { id: "terminal.close", title: "Close terminal", when: () => false }
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
})
})
+16 -5
View File
@@ -82,10 +82,15 @@ export interface CommandOption {
suggested?: boolean suggested?: boolean
disabled?: boolean disabled?: boolean
hidden?: boolean hidden?: boolean
when?: (event: KeyboardEvent) => boolean
onSelect?: (source?: "palette" | "keybind" | "slash") => void onSelect?: (source?: "palette" | "keybind" | "slash") => void
onHighlight?: () => (() => void) | void onHighlight?: () => (() => void) | void
} }
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
}
type CommandSource = "palette" | "keybind" | "slash" type CommandSource = "palette" | "keybind" | "slash"
export type CommandCatalogItem = { export type CommandCatalogItem = {
@@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}) })
const keymap = createMemo(() => { const keymap = createMemo(() => {
const map = new Map<string, CommandOption>() const map = new Map<string, CommandOption[]>()
for (const option of options()) { for (const option of options()) {
if (option.id.startsWith(SUGGESTED_PREFIX)) continue if (option.id.startsWith(SUGGESTED_PREFIX)) continue
if (option.disabled) continue if (option.disabled) continue
@@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
for (const kb of keybinds) { for (const kb of keybinds) {
if (!kb.key) continue if (!kb.key) continue
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt) const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
if (map.has(sig)) continue const existing = map.get(sig)
map.set(sig, option) if (existing) {
existing.push(option)
continue
}
map.set(sig, [option])
} }
} }
return map return map
@@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const sig = signatureFromEvent(event) const sig = signatureFromEvent(event)
const isPalette = palette().has(sig) const isPalette = palette().has(sig)
const option = keymap().get(sig) const option = resolveKeybindOption(keymap().get(sig), event)
const modified = event.ctrlKey || event.metaKey || event.altKey const modified = event.ctrlKey || event.metaKey || event.altKey
const isTab = event.key === "Tab" const isTab = event.key === "Tab"
@@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
if (isPalette) { if (isPalette) {
event.preventDefault() event.preventDefault()
event.stopPropagation()
showPalette() showPalette()
return return
} }
if (!option) return if (!option) return
event.preventDefault() event.preventDefault()
event.stopPropagation()
option.onSelect?.("keybind") option.onSelect?.("keybind")
} }
onMount(() => { onMount(() => {
makeEventListener(document, "keydown", handleKeyDown) makeEventListener(document, "keydown", handleKeyDown, { capture: true })
}) })
function register(cb: () => CommandOption[]): void function register(cb: () => CommandOption[]): void
@@ -11,7 +11,6 @@ const sessionFields = new Set([
"session_status", "session_status",
"session_working", "session_working",
"session_diff", "session_diff",
"todo",
"permission", "permission",
"question", "question",
"message", "message",
@@ -115,7 +114,6 @@ export const createDirSyncContext = (
index(sessionID) index(sessionID)
}, },
diff: serverSync.session.diff, diff: serverSync.session.diff,
todo: serverSync.session.todo,
history: serverSync.session.history, history: serverSync.session.history,
evict(sessionID: string) { evict(sessionID: string) {
serverSync.session.evict(sessionID) serverSync.session.evict(sessionID)
+8 -4
View File
@@ -203,12 +203,15 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
return promise return promise
} }
const search = (query: string, dirs: "true" | "false") => const search = (query: string, dirs: "true" | "false", options?: { limit?: number; signal?: AbortSignal }) =>
sdk() sdk()
.client.find.files({ query, dirs }) .client.find.files({ query, dirs, limit: options?.limit }, { signal: options?.signal })
.then( .then(
(x) => (x.data ?? []).map(path.normalize), (x) => (x.data ?? []).map(path.normalize),
() => [], (error) => {
if (options?.signal?.aborted) throw error
return []
},
) )
const stop = sdk().event.listen((e) => { const stop = sdk().event.listen((e) => {
@@ -284,7 +287,8 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
setScrollLeft, setScrollLeft,
selectedLines, selectedLines,
setSelectedLines, setSelectedLines,
searchFiles: (query: string) => search(query, "false"), searchFiles: (query: string, options?: { limit?: number; signal?: AbortSignal }) =>
search(query, "false", options),
searchFilesAndDirectories: (query: string) => search(query, "true"), searchFilesAndDirectories: (query: string) => search(query, "true"),
} }
}, },
@@ -30,7 +30,6 @@ function directoryState() {
return this.session_status[id]?.type !== "idle" return this.session_status[id]?.type !== "idle"
}, },
session_diff: {}, session_diff: {},
todo: {},
permission: {}, permission: {},
question: {}, question: {},
mcp_ready: true, mcp_ready: true,
@@ -223,7 +223,6 @@ export function createChildStoreManager(input: {
return (type ?? "idle") !== "idle" return (type ?? "idle") !== "idle"
}, },
session_diff: {}, session_diff: {},
todo: {},
permission: {}, permission: {},
question: {}, question: {},
get mcp_ready() { get mcp_ready() {
@@ -72,7 +72,6 @@ const baseState = (input: Partial<State> = {}) =>
sessionTotal: 0, sessionTotal: 0,
session_status: {}, session_status: {},
session_diff: {}, session_diff: {},
todo: {},
permission: {}, permission: {},
question: {}, question: {},
mcp: {}, mcp: {},
@@ -216,7 +215,6 @@ describe("applyDirectoryEvent", () => {
message: { ses_1: [message] }, message: { ses_1: [message] },
part: { [message.id]: [textPart("prt_1", "ses_1", message.id)] }, part: { [message.id]: [textPart("prt_1", "ses_1", message.id)] },
session_diff: { ses_1: [] }, session_diff: { ses_1: [] },
todo: { ses_1: [] },
permission: { ses_1: [] }, permission: { ses_1: [] },
question: { ses_1: [] }, question: { ses_1: [] },
session_status: { ses_1: { type: "busy" } }, session_status: { ses_1: { type: "busy" } },
@@ -237,7 +235,6 @@ describe("applyDirectoryEvent", () => {
expect(store.message.ses_1).toBeUndefined() expect(store.message.ses_1).toBeUndefined()
expect(store.part[message.id]).toBeUndefined() expect(store.part[message.id]).toBeUndefined()
expect(store.session_diff.ses_1).toBeUndefined() expect(store.session_diff.ses_1).toBeUndefined()
expect(store.todo.ses_1).toBeUndefined()
expect(store.permission.ses_1).toBeUndefined() expect(store.permission.ses_1).toBeUndefined()
expect(store.question.ses_1).toBeUndefined() expect(store.question.ses_1).toBeUndefined()
expect(store.session_status.ses_1).toBeUndefined() expect(store.session_status.ses_1).toBeUndefined()
@@ -262,7 +259,6 @@ describe("applyDirectoryEvent", () => {
message: { [item.info.id]: [message] }, message: { [item.info.id]: [message] },
part: { [message.id]: [textPart("prt_1", item.info.id, message.id)] }, part: { [message.id]: [textPart("prt_1", item.info.id, message.id)] },
session_diff: { [item.info.id]: [] }, session_diff: { [item.info.id]: [] },
todo: { [item.info.id]: [] },
permission: { [item.info.id]: [] }, permission: { [item.info.id]: [] },
question: { [item.info.id]: [] }, question: { [item.info.id]: [] },
session_status: { [item.info.id]: { type: "busy" } }, session_status: { [item.info.id]: { type: "busy" } },
@@ -283,7 +279,6 @@ describe("applyDirectoryEvent", () => {
expect(store.message[item.info.id]).toBeUndefined() expect(store.message[item.info.id]).toBeUndefined()
expect(store.part[message.id]).toBeUndefined() expect(store.part[message.id]).toBeUndefined()
expect(store.session_diff[item.info.id]).toBeUndefined() expect(store.session_diff[item.info.id]).toBeUndefined()
expect(store.todo[item.info.id]).toBeUndefined()
expect(store.permission[item.info.id]).toBeUndefined() expect(store.permission[item.info.id]).toBeUndefined()
expect(store.question[item.info.id]).toBeUndefined() expect(store.question[item.info.id]).toBeUndefined()
expect(store.session_status[item.info.id]).toBeUndefined() expect(store.session_status[item.info.id]).toBeUndefined()
@@ -294,7 +289,6 @@ describe("applyDirectoryEvent", () => {
const dropped = rootSession({ id: "ses_b" }) const dropped = rootSession({ id: "ses_b" })
const kept = rootSession({ id: "ses_a" }) const kept = rootSession({ id: "ses_a" })
const message = userMessage("msg_1", dropped.id) const message = userMessage("msg_1", dropped.id)
const todos: string[] = []
const [store, setStore] = createStore( const [store, setStore] = createStore(
baseState({ baseState({
limit: 1, limit: 1,
@@ -302,7 +296,6 @@ describe("applyDirectoryEvent", () => {
message: { [dropped.id]: [message] }, message: { [dropped.id]: [message] },
part: { [message.id]: [textPart("prt_1", dropped.id, message.id)] }, part: { [message.id]: [textPart("prt_1", dropped.id, message.id)] },
session_diff: { [dropped.id]: [] }, session_diff: { [dropped.id]: [] },
todo: { [dropped.id]: [] },
permission: { [dropped.id]: [] }, permission: { [dropped.id]: [] },
question: { [dropped.id]: [] }, question: { [dropped.id]: [] },
session_status: { [dropped.id]: { type: "busy" } }, session_status: { [dropped.id]: { type: "busy" } },
@@ -316,21 +309,15 @@ describe("applyDirectoryEvent", () => {
push() {}, push() {},
directory: "/tmp", directory: "/tmp",
loadLsp() {}, loadLsp() {},
setSessionTodo(sessionID, value) {
if (value !== undefined) return
todos.push(sessionID)
},
}) })
expect(store.session.map((x) => x.id)).toEqual([kept.id]) expect(store.session.map((x) => x.id)).toEqual([kept.id])
expect(store.message[dropped.id]).toBeUndefined() expect(store.message[dropped.id]).toBeUndefined()
expect(store.part[message.id]).toBeUndefined() expect(store.part[message.id]).toBeUndefined()
expect(store.session_diff[dropped.id]).toBeUndefined() expect(store.session_diff[dropped.id]).toBeUndefined()
expect(store.todo[dropped.id]).toBeUndefined()
expect(store.permission[dropped.id]).toBeUndefined() expect(store.permission[dropped.id]).toBeUndefined()
expect(store.question[dropped.id]).toBeUndefined() expect(store.question[dropped.id]).toBeUndefined()
expect(store.session_status[dropped.id]).toBeUndefined() expect(store.session_status[dropped.id]).toBeUndefined()
expect(todos).toEqual([dropped.id])
}) })
test("cleanupDroppedSessionCaches clears part-only orphan state", () => { test("cleanupDroppedSessionCaches clears part-only orphan state", () => {
@@ -8,8 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
import { trimSessions } from "./session-trim" import { trimSessions } from "./session-trim"
@@ -19,7 +18,6 @@ import { diffs as list, message as clean } from "@/utils/diffs"
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"]) const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
const SESSION_CONTENT_EVENTS = new Set([ const SESSION_CONTENT_EVENTS = new Set([
"session.diff", "session.diff",
"todo.updated",
"session.status", "session.status",
"message.updated", "message.updated",
"message.removed", "message.removed",
@@ -62,13 +60,8 @@ export function applyGlobalEvent(input: {
) )
} }
function cleanupSessionCaches( function cleanupSessionCaches(setStore: SetStoreFunction<State>, sessionID: string) {
setStore: SetStoreFunction<State>,
sessionID: string,
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void,
) {
if (!sessionID) return if (!sessionID) return
setSessionTodo?.(sessionID, undefined)
setStore( setStore(
produce((draft) => { produce((draft) => {
dropSessionCaches(draft, [sessionID]) dropSessionCaches(draft, [sessionID])
@@ -76,17 +69,11 @@ function cleanupSessionCaches(
) )
} }
export function cleanupDroppedSessionCaches( export function cleanupDroppedSessionCaches(store: Store<State>, setStore: SetStoreFunction<State>, next: Session[]) {
store: Store<State>,
setStore: SetStoreFunction<State>,
next: Session[],
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void,
) {
const keep = new Set(next.map((item) => item.id)) const keep = new Set(next.map((item) => item.id))
const stale = [ const stale = [
...Object.keys(store.message), ...Object.keys(store.message),
...Object.keys(store.session_diff), ...Object.keys(store.session_diff),
...Object.keys(store.todo),
...Object.keys(store.permission), ...Object.keys(store.permission),
...Object.keys(store.question), ...Object.keys(store.question),
...Object.keys(store.session_status), ...Object.keys(store.session_status),
@@ -95,9 +82,6 @@ export function cleanupDroppedSessionCaches(
.filter((sessionID): sessionID is string => !!sessionID), .filter((sessionID): sessionID is string => !!sessionID),
].filter((sessionID, index, list) => !keep.has(sessionID) && list.indexOf(sessionID) === index) ].filter((sessionID, index, list) => !keep.has(sessionID) && list.indexOf(sessionID) === index)
if (stale.length === 0) return if (stale.length === 0) return
for (const sessionID of stale) {
setSessionTodo?.(sessionID, undefined)
}
setStore( setStore(
produce((draft) => { produce((draft) => {
dropSessionCaches(draft, stale) dropSessionCaches(draft, stale)
@@ -114,7 +98,6 @@ export function applyDirectoryEvent(input: {
loadLsp: () => void loadLsp: () => void
loadReferences?: () => void loadReferences?: () => void
vcsCache?: VcsCache vcsCache?: VcsCache
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
retainedLimit?: number retainedLimit?: number
sessionContent?: boolean sessionContent?: boolean
permission?: State["permission"] permission?: State["permission"]
@@ -138,7 +121,7 @@ export function applyDirectoryEvent(input: {
next.splice(result.index, 0, info) next.splice(result.index, 0, info)
const trimmed = trimSessions(next, { limit, permission: input.permission ?? input.store.permission }) const trimmed = trimSessions(next, { limit, permission: input.permission ?? input.store.permission })
input.setStore("session", reconcile(trimmed, { key: "id" })) input.setStore("session", reconcile(trimmed, { key: "id" }))
cleanupDroppedSessionCaches(input.store, input.setStore, trimmed, input.setSessionTodo) cleanupDroppedSessionCaches(input.store, input.setStore, trimmed)
if (!info.parentID) input.setStore("sessionTotal", (value) => value + 1) if (!info.parentID) input.setStore("sessionTotal", (value) => value + 1)
break break
} }
@@ -155,7 +138,7 @@ export function applyDirectoryEvent(input: {
}), }),
) )
} }
cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo) cleanupSessionCaches(input.setStore, info.id)
if (info.parentID) break if (info.parentID) break
input.setStore("sessionTotal", (value) => Math.max(0, value - 1)) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
break break
@@ -168,7 +151,7 @@ export function applyDirectoryEvent(input: {
next.splice(result.index, 0, info) next.splice(result.index, 0, info)
const trimmed = trimSessions(next, { limit, permission: input.permission ?? input.store.permission }) const trimmed = trimSessions(next, { limit, permission: input.permission ?? input.store.permission })
input.setStore("session", reconcile(trimmed, { key: "id" })) input.setStore("session", reconcile(trimmed, { key: "id" }))
cleanupDroppedSessionCaches(input.store, input.setStore, trimmed, input.setSessionTodo) cleanupDroppedSessionCaches(input.store, input.setStore, trimmed)
break break
} }
case "session.deleted": { case "session.deleted": {
@@ -182,22 +165,16 @@ export function applyDirectoryEvent(input: {
}), }),
) )
} }
cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo) cleanupSessionCaches(input.setStore, info.id)
if (info.parentID) break if (info.parentID) break
input.setStore("sessionTotal", (value) => Math.max(0, value - 1)) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
break break
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] } const props = event.properties as { sessionID: string; diff: FileDiffInfo[] }
input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" })) input.setStore("session_diff", props.sessionID, reconcile(list(props.diff), { key: "file" }))
break break
} }
case "todo.updated": {
const props = event.properties as { sessionID: string; todos: Todo[] }
input.setStore("todo", props.sessionID, reconcile(props.todos, { key: "id" }))
input.setSessionTodo?.(props.sessionID, props.todos)
break
}
case "session.status": { case "session.status": {
const props = event.properties as { sessionID: string; status: SessionStatus } const props = event.properties as { sessionID: string; status: SessionStatus }
input.setStore("session_status", props.sessionID, reconcile(props.status)) input.setStore("session_status", props.sessionID, reconcile(props.status))
@@ -5,8 +5,7 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache" import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
@@ -33,8 +32,7 @@ describe("app session cache", () => {
test("dropSessionCaches clears orphaned parts without message rows", () => { test("dropSessionCaches clears orphaned parts without message rows", () => {
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
permission: Record<string, PermissionRequest[] | undefined> permission: Record<string, PermissionRequest[] | undefined>
@@ -43,7 +41,6 @@ describe("app session cache", () => {
} = { } = {
session_status: { ses_1: { type: "busy" } as SessionStatus }, session_status: { ses_1: { type: "busy" } as SessionStatus },
session_diff: { ses_1: [] }, session_diff: { ses_1: [] },
todo: { ses_1: [] as Todo[] },
message: {}, message: {},
part: { msg_1: [part("prt_1", "ses_1", "msg_1")] }, part: { msg_1: [part("prt_1", "ses_1", "msg_1")] },
permission: { ses_1: [] as PermissionRequest[] }, permission: { ses_1: [] as PermissionRequest[] },
@@ -56,7 +53,6 @@ describe("app session cache", () => {
expect(store.message.ses_1).toBeUndefined() expect(store.message.ses_1).toBeUndefined()
expect(store.part.msg_1).toBeUndefined() expect(store.part.msg_1).toBeUndefined()
expect(store.part_text_accum_delta.prt_1).toBeUndefined() expect(store.part_text_accum_delta.prt_1).toBeUndefined()
expect(store.todo.ses_1).toBeUndefined()
expect(store.session_diff.ses_1).toBeUndefined() expect(store.session_diff.ses_1).toBeUndefined()
expect(store.session_status.ses_1).toBeUndefined() expect(store.session_status.ses_1).toBeUndefined()
expect(store.permission.ses_1).toBeUndefined() expect(store.permission.ses_1).toBeUndefined()
@@ -67,8 +63,7 @@ describe("app session cache", () => {
const m = msg("msg_1", "ses_1") const m = msg("msg_1", "ses_1")
const store: { const store: {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
permission: Record<string, PermissionRequest[] | undefined> permission: Record<string, PermissionRequest[] | undefined>
@@ -77,7 +72,6 @@ describe("app session cache", () => {
} = { } = {
session_status: {}, session_status: {},
session_diff: {}, session_diff: {},
todo: {},
message: { ses_1: [m] }, message: { ses_1: [m] },
part: { [m.id]: [part("prt_1", "ses_1", m.id)] }, part: { [m.id]: [part("prt_1", "ses_1", m.id)] },
permission: {}, permission: {},
@@ -4,16 +4,14 @@ import type {
PermissionRequest, PermissionRequest,
QuestionRequest, QuestionRequest,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
export const SESSION_CACHE_LIMIT = 40 export const SESSION_CACHE_LIMIT = 40
type SessionCache = { type SessionCache = {
session_status: Record<string, SessionStatus | undefined> session_status: Record<string, SessionStatus | undefined>
session_diff: Record<string, SnapshotFileDiff[] | undefined> session_diff: Record<string, FileDiffInfo[] | undefined>
todo: Record<string, Todo[] | undefined>
message: Record<string, Message[] | undefined> message: Record<string, Message[] | undefined>
part: Record<string, Part[] | undefined> part: Record<string, Part[] | undefined>
permission: Record<string, PermissionRequest[] | undefined> permission: Record<string, PermissionRequest[] | undefined>
@@ -36,7 +34,6 @@ export function dropSessionCaches(store: SessionCache, sessionIDs: Iterable<stri
for (const sessionID of stale) { for (const sessionID of stale) {
delete store.message[sessionID] delete store.message[sessionID]
delete store.todo[sessionID]
delete store.session_diff[sessionID] delete store.session_diff[sessionID]
delete store.session_status[sessionID] delete store.session_status[sessionID]
delete store.permission[sessionID] delete store.permission[sessionID]
@@ -13,8 +13,7 @@ import type {
ReferenceInfo, ReferenceInfo,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo,
VcsInfo, VcsInfo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
@@ -51,10 +50,7 @@ export type State = {
} }
session_working(id: string): boolean session_working(id: string): boolean
session_diff: { session_diff: {
[sessionID: string]: SnapshotFileDiff[] [sessionID: string]: FileDiffInfo[]
}
todo: {
[sessionID: string]: Todo[]
} }
permission: { permission: {
[sessionID: string]: PermissionRequest[] [sessionID: string]: PermissionRequest[]
@@ -0,0 +1,82 @@
import { describe, expect, test } from "bun:test"
import {
SESSION_OPEN_FILE_TAB,
closeSessionTab,
openSessionTab,
previewSessionTab,
type SessionTabState,
} from "./layout-tabs"
const state = (all: string[], active?: string, preview?: string): SessionTabState => ({
tabs: { all, active },
preview,
})
describe("previewSessionTab", () => {
test("appends the Open File placeholder", () => {
expect(previewSessionTab(state(["file://a.ts"], "file://a.ts"), SESSION_OPEN_FILE_TAB)).toEqual(
state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB),
)
})
test("replaces the current preview in place", () => {
expect(
previewSessionTab(
state(["context", SESSION_OPEN_FILE_TAB, "file://b.ts"], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB),
"file://a.ts",
),
).toEqual(state(["context", "file://a.ts", "file://b.ts"], "file://a.ts", "file://a.ts"))
})
test("activates a durable tab without duplicating it", () => {
expect(
previewSessionTab(
state(["file://a.ts", SESSION_OPEN_FILE_TAB, "file://b.ts"], SESSION_OPEN_FILE_TAB, SESSION_OPEN_FILE_TAB),
"file://b.ts",
),
).toEqual(state(["file://a.ts", "file://b.ts"], "file://b.ts"))
})
test("replaces a restored Open File placeholder", () => {
expect(
previewSessionTab(state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB), "file://b.ts"),
).toEqual(state(["file://a.ts", "file://b.ts"], "file://b.ts", "file://b.ts"))
})
})
describe("openSessionTab", () => {
test("pins the current preview", () => {
expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "file://a.ts")).toEqual(
state(["file://a.ts"], "file://a.ts"),
)
})
test("replaces a preview with a directly opened file", () => {
expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "file://b.ts")).toEqual(
state(["file://b.ts"], "file://b.ts"),
)
})
test("keeps the preview when switching to Review", () => {
expect(openSessionTab(state(["file://a.ts"], "file://a.ts", "file://a.ts"), "review")).toEqual(
state(["file://a.ts"], "review", "file://a.ts"),
)
})
test("replaces a restored Open File placeholder with a direct open", () => {
expect(openSessionTab(state(["file://a.ts", SESSION_OPEN_FILE_TAB], SESSION_OPEN_FILE_TAB), "file://b.ts")).toEqual(
state(["file://a.ts", "file://b.ts"], "file://b.ts"),
)
})
})
describe("closeSessionTab", () => {
test("clears preview metadata and selects the left neighbor", () => {
expect(
closeSessionTab(
state(["file://a.ts", "file://b.ts", "file://c.ts"], "file://b.ts", "file://b.ts"),
"file://b.ts",
),
).toEqual(state(["file://a.ts", "file://c.ts"], "file://a.ts"))
})
})
+103
View File
@@ -0,0 +1,103 @@
export const SESSION_OPEN_FILE_TAB = "open-file"
export type SessionTabs = {
active?: string
all: string[]
}
export type SessionTabState = {
tabs: SessionTabs
preview?: string
}
const sessionTabPreview = (current: SessionTabState) =>
current.preview ?? (current.tabs.all.includes(SESSION_OPEN_FILE_TAB) ? SESSION_OPEN_FILE_TAB : undefined)
export function previewSessionTab(current: SessionTabState, tab: string): SessionTabState {
const preview = sessionTabPreview(current)
const previewIndex = preview ? current.tabs.all.indexOf(preview) : -1
const existingIndex = current.tabs.all.indexOf(tab)
if (existingIndex !== -1) {
if (previewIndex === -1 || preview === tab) {
return { tabs: { all: current.tabs.all, active: tab }, preview: preview === tab ? tab : undefined }
}
return {
tabs: { all: current.tabs.all.filter((item) => item !== preview), active: tab },
}
}
if (previewIndex === -1) {
return { tabs: { all: [...current.tabs.all, tab], active: tab }, preview: tab }
}
return {
tabs: {
all: current.tabs.all.map((item, index) => (index === previewIndex ? tab : item)),
active: tab,
},
preview: tab,
}
}
export function openSessionTab(current: SessionTabState, tab: string): SessionTabState {
const preview = sessionTabPreview(current)
if (tab === "review") {
return {
tabs: { all: current.tabs.all.filter((item) => item !== tab), active: tab },
preview,
}
}
if (tab === "context") {
return {
tabs: { all: [tab, ...current.tabs.all.filter((item) => item !== tab)], active: tab },
preview,
}
}
const previewIndex = preview ? current.tabs.all.indexOf(preview) : -1
const existingIndex = current.tabs.all.indexOf(tab)
if (existingIndex !== -1) {
if (previewIndex === -1 || preview === tab) {
return { tabs: { all: current.tabs.all, active: tab } }
}
return {
tabs: { all: current.tabs.all.filter((item) => item !== preview), active: tab },
}
}
if (previewIndex === -1) {
return { tabs: { all: [...current.tabs.all, tab], active: tab } }
}
return {
tabs: {
all: current.tabs.all.map((item, index) => (index === previewIndex ? tab : item)),
active: tab,
},
}
}
export function closeSessionTab(current: SessionTabState, tab: string): SessionTabState {
if (tab === "review") {
if (current.tabs.active !== tab) return current
return {
tabs: { all: current.tabs.all, active: current.tabs.all[0] },
preview: current.preview,
}
}
const all = current.tabs.all.filter((item) => item !== tab)
const preview = current.preview === tab ? undefined : current.preview
if (current.tabs.active !== tab) return { tabs: { ...current.tabs, all }, preview }
const index = current.tabs.all.indexOf(tab)
return {
tabs: {
all,
active: current.tabs.all[index - 1] ?? current.tabs.all[index + 1] ?? all[0],
},
preview,
}
}
+77 -47
View File
@@ -19,6 +19,7 @@ import { migrateLegacySessionStateKeys, ServerScope, SessionStateKey } from "@/u
import { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } from "./layout-helpers" import { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } from "./layout-helpers"
import { requireServerKey } from "@/utils/session-route" import { requireServerKey } from "@/utils/session-route"
import { type DraftTab, useTabs } from "./tabs" import { type DraftTab, useTabs } from "./tabs"
import { closeSessionTab, openSessionTab, previewSessionTab, type SessionTabs } from "./layout-tabs"
export { createSessionKeyReader, ensureSessionKey, pruneSessionKeys } export { createSessionKeyReader, ensureSessionKey, pruneSessionKeys }
@@ -55,17 +56,13 @@ export function getProjectAvatarVariant(key?: string): ProjectAvatarVariant {
return "gray" return "gray"
} }
type SessionTabs = {
active?: string
all: string[]
}
type SessionView = { type SessionView = {
scroll: Record<string, SessionScroll> scroll: Record<string, SessionScroll>
reviewOpen?: string[] reviewOpen?: string[]
reviewMode?: ReviewChangeMode
reviewFile?: string
pendingMessage?: string pendingMessage?: string
pendingMessageAt?: number pendingMessageAt?: number
todoCollapsed?: boolean
} }
type TabHandoff = { type TabHandoff = {
@@ -79,6 +76,7 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string } export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
export type ReviewDiffStyle = "unified" | "split" export type ReviewDiffStyle = "unified" | "split"
export type ReviewChangeMode = "git" | "branch" | "turn"
export type ReviewPanelSource = "context-button" | "other" export type ReviewPanelSource = "context-button" | "other"
export type LayoutRoute = export type LayoutRoute =
@@ -87,14 +85,6 @@ export type LayoutRoute =
| { type: "dir-new-sesssion"; dir: string; dirBase64: string; server?: ServerConnection.Key } | { type: "dir-new-sesssion"; dir: string; dirBase64: string; server?: ServerConnection.Key }
| { type: "session"; sessionId: string; server?: ServerConnection.Key } | { type: "session"; sessionId: string; server?: ServerConnection.Key }
function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string): SessionTabs {
const all = current?.all ?? []
if (tab === "review") return { all: all.filter((x) => x !== "review"), active: tab }
if (tab === "context") return { all: [tab, ...all.filter((x) => x !== tab)], active: tab }
if (!all.includes(tab)) return { all: [...all, tab], active: tab }
return { all, active: tab }
}
const sessionPath = (key: string) => { const sessionPath = (key: string) => {
const dir = SessionStateKey.route(key).split("/")[0] const dir = SessionStateKey.route(key).split("/")[0]
if (!dir) return if (!dir) return
@@ -308,6 +298,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
) )
const [ephemeral, setEphemeral] = createStore({ const [ephemeral, setEphemeral] = createStore({
reviewPanelSource: "other" as ReviewPanelSource, reviewPanelSource: "other" as ReviewPanelSource,
sessionTabPreview: {} as Record<string, string | undefined>,
}) })
const MAX_SESSION_KEYS = 50 const MAX_SESSION_KEYS = 50
@@ -366,6 +357,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
scroll.drop(drop) scroll.drop(drop)
dropSessionState(drop) dropSessionState(drop)
setEphemeral(
"sessionTabPreview",
produce((draft) => {
for (const key of drop) delete draft[key]
}),
)
for (const key of drop) { for (const key of drop) {
usage.used.delete(key) usage.used.delete(key)
@@ -791,6 +788,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
view(sessionKey: string | Accessor<string>) { view(sessionKey: string | Accessor<string>) {
const key = createSessionKeyReader(sessionKey, ensureKey) const key = createSessionKeyReader(sessionKey, ensureKey)
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} }) const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
const reviewMode = createMemo(() => {
const mode = s().reviewMode
if (mode === "git" || mode === "branch" || mode === "turn") return mode
})
const reviewFile = createMemo(() => {
const file = s().reviewFile
if (typeof file === "string") return file
})
const terminalOpened = createMemo(() => store.terminal?.opened ?? false) const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED) const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other")) const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
@@ -836,18 +841,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setScroll(tab: string, pos: SessionScroll) { setScroll(tab: string, pos: SessionScroll) {
scroll.setScroll(key(), tab, pos) scroll.setScroll(key(), tab, pos)
}, },
todoCollapsed: {
get: () => s().todoCollapsed ?? false,
set(collapsed: boolean) {
const session = key()
const current = store.sessionView[session]
if (!current) {
setStore("sessionView", session, { scroll: {}, todoCollapsed: collapsed })
} else {
setStore("sessionView", session, "todoCollapsed", collapsed)
}
},
},
terminal: { terminal: {
opened: terminalOpened, opened: terminalOpened,
open() { open() {
@@ -874,6 +867,32 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}, },
}, },
review: { review: {
mode: reviewMode,
setMode(mode: ReviewChangeMode) {
const session = key()
const current = store.sessionView[session]
if (!current) {
setStore("sessionView", session, { scroll: {}, reviewMode: mode })
prune(session)
return
}
if (current.reviewMode === mode) return
setStore("sessionView", session, "reviewMode", mode)
prune(session)
},
file: reviewFile,
setFile(file: string) {
const session = key()
const current = store.sessionView[session]
if (!current) {
setStore("sessionView", session, { scroll: {}, reviewFile: file })
prune(session)
return
}
if (current.reviewFile === file) return
setStore("sessionView", session, "reviewFile", file)
prune(session)
},
open: createMemo(() => s().reviewOpen ?? []), open: createMemo(() => s().reviewOpen ?? []),
setOpen(open: string[]) { setOpen(open: string[]) {
const session = key() const session = key()
@@ -945,10 +964,17 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] }) const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
const normalize = (tab: string) => normalizeSessionTab(path(), tab) const normalize = (tab: string) => normalizeSessionTab(path(), tab)
const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all) const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all)
const apply = (session: string, next: ReturnType<typeof openSessionTab>) => {
batch(() => {
setStore("sessionTabs", session, next.tabs)
setEphemeral("sessionTabPreview", session, next.preview)
})
}
return { return {
tabs, tabs,
active: createMemo(() => tabs().active), active: createMemo(() => tabs().active),
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")), all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
preview: createMemo(() => ephemeral.sessionTabPreview[key()]),
setActive(tab: string | undefined) { setActive(tab: string | undefined) {
const session = key() const session = key()
const next = tab ? normalize(tab) : tab const next = tab ? normalize(tab) : tab
@@ -961,40 +987,44 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setAll(all: string[]) { setAll(all: string[]) {
const session = key() const session = key()
const next = normalizeAll(all).filter((tab) => tab !== "review") const next = normalizeAll(all).filter((tab) => tab !== "review")
batch(() => {
if (!store.sessionTabs[session]) { if (!store.sessionTabs[session]) {
setStore("sessionTabs", session, { all: next, active: undefined }) setStore("sessionTabs", session, { all: next, active: undefined })
} else { } else {
setStore("sessionTabs", session, "all", next) setStore("sessionTabs", session, "all", next)
} }
const preview = ephemeral.sessionTabPreview[session]
if (preview && !next.includes(preview)) setEphemeral("sessionTabPreview", session, undefined)
})
}, },
async open(tab: string) { async open(tab: string) {
const session = key() const session = key()
const next = nextSessionTabsForOpen(store.sessionTabs[session], normalize(tab)) apply(
setStore("sessionTabs", session, next) session,
openSessionTab(
{ tabs: store.sessionTabs[session] ?? { all: [] }, preview: ephemeral.sessionTabPreview[session] },
normalize(tab),
),
)
},
previewTab(tab: string) {
const session = key()
apply(
session,
previewSessionTab(
{ tabs: store.sessionTabs[session] ?? { all: [] }, preview: ephemeral.sessionTabPreview[session] },
normalize(tab),
),
)
}, },
close(tab: string) { close(tab: string) {
const session = key() const session = key()
const current = store.sessionTabs[session] const current = store.sessionTabs[session]
if (!current) return if (!current) return
apply(
if (tab === "review") { session,
if (current.active !== tab) return closeSessionTab({ tabs: current, preview: ephemeral.sessionTabPreview[session] }, normalize(tab)),
setStore("sessionTabs", session, "active", current.all[0]) )
return
}
const all = current.all.filter((x) => x !== tab)
if (current.active !== tab) {
setStore("sessionTabs", session, "all", all)
return
}
const index = current.all.findIndex((f) => f === tab)
const next = current.all[index - 1] ?? current.all[index + 1] ?? all[0]
batch(() => {
setStore("sessionTabs", session, "all", all)
setStore("sessionTabs", session, "active", next)
})
}, },
move(tab: string, to: number) { move(tab: string, to: number) {
const session = key() const session = key()
@@ -151,7 +151,6 @@ function setup(sessions: Record<string, Session>) {
return response() return response()
}, },
diff: async () => ({ data: [] }), diff: async () => ({ data: [] }),
todo: async () => ({ data: [] }),
}, },
} as unknown as OpencodeClient } as unknown as OpencodeClient
return { get, messages, store: createServerSession(client) } return { get, messages, store: createServerSession(client) }
+4 -27
View File
@@ -8,8 +8,7 @@ import type {
QuestionRequest, QuestionRequest,
Session, Session,
SessionStatus, SessionStatus,
SnapshotFileDiff, FileDiffInfo,
Todo,
} from "@opencode-ai/sdk/v2/client" } from "@opencode-ai/sdk/v2/client"
import { batch } from "solid-js" import { batch } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store" import { createStore, produce, reconcile } from "solid-js/store"
@@ -139,8 +138,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
const [data, setData] = createStore({ const [data, setData] = createStore({
info: {} as Record<string, Session | undefined>, info: {} as Record<string, Session | undefined>,
session_status: {} as Record<string, SessionStatus>, session_status: {} as Record<string, SessionStatus>,
session_diff: {} as Record<string, SnapshotFileDiff[]>, session_diff: {} as Record<string, FileDiffInfo[]>,
todo: {} as Record<string, Todo[]>,
permission: {} as Record<string, PermissionRequest[]>, permission: {} as Record<string, PermissionRequest[]>,
question: {} as Record<string, QuestionRequest[]>, question: {} as Record<string, QuestionRequest[]>,
message: {} as Record<string, Message[]>, message: {} as Record<string, Message[]>,
@@ -153,7 +151,6 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
const requests = new Map<string, Promise<Session>>() const requests = new Map<string, Promise<Session>>()
const inflight = new Map<string, Promise<void>>() const inflight = new Map<string, Promise<void>>()
const inflightDiff = new Map<string, Promise<void>>() const inflightDiff = new Map<string, Promise<void>>()
const inflightTodo = new Map<string, Promise<void>>()
const optimistic = new Map<string, Map<string, OptimisticItem>>() const optimistic = new Map<string, Map<string, OptimisticItem>>()
const messageLoads = new Map<string, MessageLoadState>() const messageLoads = new Map<string, MessageLoadState>()
const pendingParts = new Map<string, Map<string, Set<string>>>() const pendingParts = new Map<string, Map<string, Set<string>>>()
@@ -199,7 +196,6 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
...requests.keys(), ...requests.keys(),
...inflight.keys(), ...inflight.keys(),
...inflightDiff.keys(), ...inflightDiff.keys(),
...inflightTodo.keys(),
...messageLoads.keys(), ...messageLoads.keys(),
...optimistic.keys(), ...optimistic.keys(),
...Object.entries(data.permission) ...Object.entries(data.permission)
@@ -254,8 +250,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
!requests.has(sessionID) && !requests.has(sessionID) &&
!messageLoads.has(sessionID) && !messageLoads.has(sessionID) &&
!inflight.has(sessionID) && !inflight.has(sessionID) &&
!inflightDiff.has(sessionID) && !inflightDiff.has(sessionID)
!inflightTodo.has(sessionID)
) )
generations.delete(sessionID) generations.delete(sessionID)
} }
@@ -418,7 +413,6 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
requests.delete(sessionID) requests.delete(sessionID)
inflight.delete(sessionID) inflight.delete(sessionID)
inflightDiff.delete(sessionID) inflightDiff.delete(sessionID)
inflightTodo.delete(sessionID)
messageLoads.delete(sessionID) messageLoads.delete(sessionID)
pendingParts.delete(sessionID) pendingParts.delete(sessionID)
orphanParts.delete(sessionID) orphanParts.delete(sessionID)
@@ -448,7 +442,6 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
...requests.keys(), ...requests.keys(),
...inflight.keys(), ...inflight.keys(),
...inflightDiff.keys(), ...inflightDiff.keys(),
...inflightTodo.keys(),
...messageLoads.keys(), ...messageLoads.keys(),
...optimistic.keys(), ...optimistic.keys(),
...Object.entries(data.permission) ...Object.entries(data.permission)
@@ -769,15 +762,10 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
return return
} }
case "session.diff": { case "session.diff": {
const props = event.properties as { sessionID: string; diff: SnapshotFileDiff[] } const props = event.properties as { sessionID: string; diff: FileDiffInfo[] }
setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" })) setData("session_diff", props.sessionID, reconcile(cleanDiffs(props.diff), { key: "file" }))
return return
} }
case "todo.updated": {
const props = event.properties as { sessionID: string; todos: Todo[] }
setData("todo", props.sessionID, reconcile(props.todos, { key: "id" }))
return
}
case "session.status": { case "session.status": {
const props = event.properties as { sessionID: string; status: SessionStatus } const props = event.properties as { sessionID: string; status: SessionStatus }
setData("session_status", props.sessionID, reconcile(props.status)) setData("session_status", props.sessionID, reconcile(props.status))
@@ -1140,17 +1128,6 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
}) })
}) })
}, },
todo(sessionID: string, options?: { force?: boolean }) {
touch(sessionID)
if (data.todo[sessionID] !== undefined && !options?.force) return Promise.resolve()
return runInflight(inflightTodo, sessionID, () => {
const active = generation(sessionID)
return retry(() => client.session.todo({ sessionID })).then((result) => {
if (generations.get(sessionID) !== active) return
setData("todo", sessionID, reconcile(result.data ?? [], { key: "id" }))
})
})
},
history: { history: {
more: (sessionID: string) => more: (sessionID: string) =>
data.message[sessionID] !== undefined && data.message[sessionID] !== undefined &&
+35 -2
View File
@@ -28,6 +28,11 @@ export type DraftTab = {
export type Tab = SessionTab | DraftTab export type Tab = SessionTab | DraftTab
export type TabInfo = {
title?: string
directory?: string
}
type RecentTab = { type RecentTab = {
key?: string key?: string
} }
@@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
createStore<Tab[]>([]), createStore<Tab[]>([]),
) )
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({})) const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([])) const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
const params = useParams() const params = useParams()
@@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform) for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
} }
const removeInfo = (key: string) => {
if (!info[key]) return
setInfo(
produce((draft) => {
delete draft[key]
}),
)
}
onCleanup(memory.dispose) onCleanup(memory.dispose)
createEffect(() => { createEffect(() => {
@@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const next = store.filter((tab) => servers.has(tab.server)) const next = store.filter((tab) => servers.has(tab.server))
if (next.length !== store.length) { if (next.length !== store.length) {
for (const tab of store) { for (const tab of store) {
if (!servers.has(tab.server)) memory.remove(tabKey(tab)) if (!servers.has(tab.server)) {
const key = tabKey(tab)
memory.remove(key)
removeInfo(key)
}
} }
setStore(() => next) setStore(() => next)
} }
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined) if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
const keys = new Set(next.map(tabKey))
for (const key of Object.keys(info)) {
if (!keys.has(key)) removeInfo(key)
}
}) })
createEffect(() => { createEffect(() => {
@@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
if (nextTab) navigateTab(nextTab) if (nextTab) navigateTab(nextTab)
}).finally(() => closing.delete(key)) }).finally(() => closing.delete(key))
memory.remove(key) memory.remove(key)
removeInfo(key)
if (draftID) removeDraftPersisted(draftID) if (draftID) removeDraftPersisted(draftID)
} }
@@ -264,6 +288,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const removed = store.filter((tab) => tab.server === key).map(tabKey) const removed = store.filter((tab) => tab.server === key).map(tabKey)
setStore((tabs) => tabs.filter((tab) => tab.server !== key)) setStore((tabs) => tabs.filter((tab) => tab.server !== key))
for (const key of removed) memory.remove(key) for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined) if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
for (const draftID of drafts) removeDraftPersisted(draftID) for (const draftID of drafts) removeDraftPersisted(draftID)
if (server.key === key) navigate("/") if (server.key === key) navigate("/")
@@ -318,6 +343,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined) if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
}) })
for (const key of removed) memory.remove(key) for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
},
rememberSessionInfo(tab: SessionTab, session: Session) {
const key = tabKey(tab)
const next = { title: session.title, directory: session.directory }
const current = info[key]
if (current?.title === next.title && current.directory === next.directory) return
setInfo(key, next)
}, },
select: navigateTab, select: navigateTab,
remember(tab: Tab) { remember(tab: Tab) {
@@ -342,6 +375,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
}, },
} }
return { ...actions, store, ready, recentReady } return { ...actions, store, info, ready, recentReady }
}, },
}) })
+10
View File
@@ -12,6 +12,16 @@ interface ImportMeta {
readonly env: ImportMetaEnv readonly env: ImportMetaEnv
} }
declare module "*.png" {
const src: string
export default src
}
declare module "*.mp4" {
const src: string
export default src
}
export declare module "solid-js" { export declare module "solid-js" {
namespace JSX { namespace JSX {
interface Directives { interface Directives {
+154 -8
View File
@@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "داكن", "theme.scheme.dark": "داكن",
"command.sidebar.toggle": "تبديل الشريط الجانبي", "command.sidebar.toggle": "تبديل الشريط الجانبي",
"command.project.open": "فتح مشروع", "command.project.open": "فتح مشروع",
"command.project.previous": "المشروع السابق",
"command.project.next": "المشروع التالي",
"command.project.index": "التبديل إلى المشروع {{index}}",
"command.provider.connect": "اتصال بموفر", "command.provider.connect": "اتصال بموفر",
"command.server.switch": "تبديل الخادم", "command.server.switch": "تبديل الخادم",
"command.settings.open": "فتح الإعدادات", "command.settings.open": "فتح الإعدادات",
@@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "جلسة جديدة", "command.session.new": "جلسة جديدة",
"command.file.open": "فتح ملف", "command.file.open": "فتح ملف",
"command.tab.close": "إغلاق علامة التبويب", "command.tab.close": "إغلاق علامة التبويب",
"command.tab.reopenClosed": "إعادة فتح علامة التبويب المغلقة",
"command.context.addSelection": "إضافة التحديد إلى السياق", "command.context.addSelection": "إضافة التحديد إلى السياق",
"command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي", "command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي",
"command.input.focus": "التركيز على حقل الإدخال", "command.input.focus": "التركيز على حقل الإدخال",
@@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "فيديو", "model.input.video": "فيديو",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "يسمح: {{inputs}}", "model.tooltip.allows": "يسمح: {{inputs}}",
"model.tooltip.context.label": "السياق",
"model.tooltip.inputs": "المدخلات",
"model.tooltip.model": "النموذج",
"model.tooltip.provider": "الموفر",
"model.tooltip.reasoning": "الاستنتاج",
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج", "model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
"model.tooltip.reasoning.none": "بدون استنتاج", "model.tooltip.reasoning.none": "بدون استنتاج",
"model.tooltip.context": "حد السياق {{limit}}", "model.tooltip.context": "حد السياق {{limit}}",
@@ -202,6 +211,7 @@ export const dict = {
"common.loading": "جارٍ التحميل", "common.loading": "جارٍ التحميل",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "إلغاء", "common.cancel": "إلغاء",
"common.clear": "مسح",
"common.connect": "اتصال", "common.connect": "اتصال",
"common.disconnect": "قطع الاتصال", "common.disconnect": "قطع الاتصال",
"common.continue": "إرسال", "common.continue": "إرسال",
@@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc للخروج", "prompt.mode.shell.exit": "esc للخروج",
"session.child.promptDisabled": "لا يمكن إرسال موجّهات إلى جلسات الوكلاء الفرعيين.",
"session.child.backToParent": "العودة إلى الجلسة الرئيسية.",
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية", "prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟", "prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
"prompt.example.3": "إصلاح الاختبارات المعطلة", "prompt.example.3": "إصلاح الاختبارات المعطلة",
@@ -255,6 +267,11 @@ export const dict = {
"prompt.context.removeActiveFile": "إزالة الملف النشط من السياق", "prompt.context.removeActiveFile": "إزالة الملف النشط من السياق",
"prompt.context.removeFile": "إزالة الملف من السياق", "prompt.context.removeFile": "إزالة الملف من السياق",
"prompt.action.attachFile": "إرفاق ملف", "prompt.action.attachFile": "إرفاق ملف",
"prompt.menu.addImagesAndFiles": "إضافة ملفات والمزيد",
"prompt.menu.imagesAndFiles": "الصور والملفات",
"prompt.menu.commands": "الأوامر",
"prompt.menu.context": "السياق",
"prompt.menu.shellCommand": "أمر shell",
"prompt.attachment.remove": "إزالة المرفق", "prompt.attachment.remove": "إزالة المرفق",
"prompt.action.send": "إرسال", "prompt.action.send": "إرسال",
"prompt.action.stop": "توقف", "prompt.action.stop": "توقف",
@@ -314,6 +331,74 @@ export const dict = {
"dialog.server.menu.delete": "حذف", "dialog.server.menu.delete": "حذف",
"dialog.server.current": "الخادم الحالي", "dialog.server.current": "الخادم الحالي",
"dialog.server.status.default": "افتراضي", "dialog.server.status.default": "افتراضي",
"wsl.server.add": "إضافة خادم WSL",
"wsl.server.addShort": "إضافة WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "خادم WSL",
"wsl.server.retryStart": "إعادة محاولة التشغيل",
"wsl.server.updating": "جارٍ التحديث...",
"wsl.onboarding.step.distro": "اختر توزيعة",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL...",
"wsl.onboarding.restartRequired": "يحتاج Windows إلى إعادة التشغيل لإكمال تثبيت WSL.",
"wsl.onboarding.ready": "WSL جاهز.",
"wsl.onboarding.required": "يلزم WSL للمتابعة.",
"wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات...",
"wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}...",
"wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}...",
"wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات...",
"wsl.onboarding.distroReady": "{{distro}} جاهزة.",
"wsl.onboarding.distroNotInstalled": "لم يتم تثبيت {{distro}} بعد.",
"wsl.onboarding.openDistroOnce": "افتح {{distro}} مرة واحدة لإكمال الإعداد.",
"wsl.onboarding.finishingDistro": "جارٍ إكمال إعداد {{distro}}.",
"wsl.onboarding.pickDistro": "اختر توزيعة أو ثبّت واحدة أدناه.",
"wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}...",
"wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "حدّث OpenCode في {{distro}}.",
"wsl.onboarding.updateOpencode": "تحديث OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode جاهز في {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode جاهز.",
"wsl.onboarding.installOpencodeIn": "ثبّت OpenCode في {{distro}}.",
"wsl.onboarding.installOpencode": "تثبيت OpenCode",
"wsl.onboarding.chooseDistroFirst": "اختر توزيعة أولاً.",
"wsl.onboarding.loadFailed": "فشل تحميل حالة WSL.",
"wsl.onboarding.loading": "جارٍ التحميل...",
"wsl.onboarding.installedDistros": "التوزيعات المثبتة",
"wsl.onboarding.checkAgain": "تحقق مجددًا",
"wsl.onboarding.distroStatus.ready": "جاهزة",
"wsl.onboarding.distroStatus.checking": "جارٍ التحقق...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode غير مثبت",
"wsl.onboarding.distroStatus.missingTools": "bash و curl مفقودان",
"wsl.onboarding.distroStatus.unsupported": "غير مدعومة · استخدم WSL 2",
"wsl.onboarding.needAnotherDistro": "هل تحتاج إلى توزيعة أخرى؟",
"wsl.onboarding.needAnotherDistroHint": "ثبّت توزيعة Linux من كتالوج WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL غير مثبت",
"wsl.onboarding.wslNotInstalled.description":
"يلزم WSL (نظام Windows الفرعي لـ Linux) قبل أن يتمكن OpenCode من إضافة خادم WSL",
"wsl.onboarding.wslUnavailable.title": "WSL غير متاح",
"wsl.onboarding.wslUnavailable.description": "تعذر على OpenCode التحقق من WSL على هذا الجهاز.",
"wsl.onboarding.installWsl": "تثبيت WSL",
"wsl.onboarding.windowsRestartRequired": "أعد تشغيل Windows لإكمال تثبيت WSL، ثم أعد فتح OpenCode.",
"wsl.onboarding.next": "التالي",
"wsl.onboarding.refresh": "تحديث",
"wsl.onboarding.allDistrosAdded": "تمت إضافة جميع التوزيعات المثبتة بالفعل.",
"wsl.onboarding.noDistros": "لم يتم اكتشاف أي توزيعات بعد.",
"wsl.onboarding.install": "تثبيت",
"wsl.onboarding.installing": "جارٍ التثبيت...",
"wsl.onboarding.installDistro": "تثبيت توزيعة",
"wsl.onboarding.searchDistros": "البحث عن توزيعات",
"wsl.onboarding.wsl2Required": "يلزم WSL 2.",
"wsl.onboarding.toolsRequired": "تحتاج هذه التوزيعة إلى bash و curl.",
"wsl.onboarding.openTerminal": "فتح المحطة الطرفية",
"wsl.onboarding.path": "المسار: {{path}}",
"wsl.onboarding.notFound": "غير موجود",
"wsl.onboarding.version": "الإصدار: {{version}}",
"wsl.onboarding.unknown": "غير معروف",
"wsl.onboarding.desktopVersion": "سطح المكتب {{version}}",
"wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.",
"wsl.onboarding.adding": "جارٍ الإضافة...",
"dialog.project.edit.title": "تحرير المشروع", "dialog.project.edit.title": "تحرير المشروع",
"dialog.project.edit.name": "الاسم", "dialog.project.edit.name": "الاسم",
"dialog.project.edit.icon": "أيقونة", "dialog.project.edit.icon": "أيقونة",
@@ -370,6 +455,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "لغة", "toast.language.title": "لغة",
@@ -406,10 +492,12 @@ export const dict = {
"toast.update.action.notYet": "ليس الآن", "toast.update.action.notYet": "ليس الآن",
"error.page.title": "حدث خطأ ما", "error.page.title": "حدث خطأ ما",
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.", "error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
"error.page.description.localServerStartup": "حدث خطأ أثناء بدء تشغيل الخادم المحلي.",
"error.page.details.label": "تفاصيل الخطأ", "error.page.details.label": "تفاصيل الخطأ",
"error.page.action.restart": "إعادة تشغيل", "error.page.action.restart": "إعادة تشغيل",
"error.page.action.report": "الإبلاغ عن الخطأ", "error.page.action.report": "الإبلاغ عن الخطأ",
"error.page.action.reported": "تم الإبلاغ عن الخطأ", "error.page.action.reported": "تم الإبلاغ عن الخطأ",
"error.page.action.exportLogs": "تصدير السجلات",
"error.page.action.checking": "جارٍ التحقق...", "error.page.action.checking": "جارٍ التحقق...",
"error.page.action.checkUpdates": "التحقق من وجود تحديثات", "error.page.action.checkUpdates": "التحقق من وجود تحديثات",
"error.page.action.updateTo": "تحديث إلى {{version}}", "error.page.action.updateTo": "تحديث إلى {{version}}",
@@ -450,16 +538,39 @@ export const dict = {
"home.recentProjects": "المشاريع الحديثة", "home.recentProjects": "المشاريع الحديثة",
"home.empty.title": "لا توجد مشاريع حديثة", "home.empty.title": "لا توجد مشاريع حديثة",
"home.empty.description": "ابدأ بفتح مشروع محلي", "home.empty.description": "ابدأ بفتح مشروع محلي",
"home.title": "الرئيسية",
"home.projects": "المشاريع",
"home.project.add": "إضافة مشروع",
"home.recentlyClosed": "المغلقة مؤخرًا",
"home.server.collapse": "طي مشاريع الخادم",
"home.server.expand": "توسيع مشاريع الخادم",
"home.sessions.search.placeholder": "البحث في الجلسات",
"home.sessions.search.placeholder.scoped": "البحث في جلسات {{scope}}",
"home.sessions.search.sessions": "الجلسات",
"home.sessions.search.noResults": "لم يتم العثور على جلسات لـ {{query}}",
"home.sessions.empty": "لا يوجد شيء هنا بعد",
"home.sessions.empty.description": "أنشئ جلسة للبدء",
"home.sessions.group.today": "اليوم",
"home.sessions.group.yesterday": "أمس",
"home.sessions.group.older": "الأقدم",
"home.providerTip": "اتصل بأكثر من 75 مزودًا لاستخدام نماذج أخرى، بما فيها Claude وGPT وGemini وغيرها",
"session.tab.session": "جلسة", "session.tab.session": "جلسة",
"session.tab.review": "مراجعة", "session.tab.review": "مراجعة",
"session.tab.context": "سياق", "session.tab.context": "سياق",
"session.tab.unknown": "جلسة غير معروفة",
"session.panel.reviewAndFiles": "المراجعة والملفات", "session.panel.reviewAndFiles": "المراجعة والملفات",
"session.error.notFound": "لا يمكن العثور على هذه الجلسة",
"session.error.notFound.description": "يشير هذا التبويب إلى جلسة لم تعد موجودة على هذا الخادم.",
"session.error.notFound.closeTab": "إغلاق علامة التبويب",
"session.error.serverConnection": "تعذر الاتصال بهذا الخادم",
"session.review.filesChanged": "تم تغيير {{count}} ملفات", "session.review.filesChanged": "تم تغيير {{count}} ملفات",
"session.review.change.one": "تغيير", "session.review.change.one": "تغيير",
"session.review.change.other": "تغييرات", "session.review.change.other": "تغييرات",
"session.review.loadingChanges": "جارٍ تحميل التغييرات...", "session.review.loadingChanges": "جارٍ تحميل التغييرات...",
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد", "session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
"session.review.noChanges": "لا توجد تغييرات", "session.review.noChanges": "لا توجد تغييرات",
"session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد",
"session.review.noBranchChanges": "لا توجد تغييرات في الفرع بعد",
"session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات", "session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
"session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة", "session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
"session.files.selectToOpen": "اختر ملفًا لفتحه", "session.files.selectToOpen": "اختر ملفًا لفتحه",
@@ -472,9 +583,10 @@ export const dict = {
"session.messages.loading": "جارٍ تحميل الرسائل...", "session.messages.loading": "جارٍ تحميل الرسائل...",
"session.messages.jumpToLatest": "الانتقال إلى الأحدث", "session.messages.jumpToLatest": "الانتقال إلى الأحدث",
"session.context.addToContext": "إضافة {{selection}} إلى السياق", "session.context.addToContext": "إضافة {{selection}} إلى السياق",
"session.todo.title": "المهام", "session.question.minimize": "تصغير السؤال",
"session.todo.collapse": "طي", "session.question.restore": "استعادة السؤال",
"session.todo.expand": "توسيع", "session.question.pending.one": "{{count}} سؤال معلق",
"session.question.pending.other": "الأسئلة المعلقة: {{count}}",
"session.followupDock.summary.one": "{{count}} رسالة في الانتظار", "session.followupDock.summary.one": "{{count}} رسالة في الانتظار",
"session.followupDock.summary.other": "{{count}} رسائل في الانتظار", "session.followupDock.summary.other": "{{count}} رسائل في الانتظار",
"session.followupDock.sendNow": "إرسال الآن", "session.followupDock.sendNow": "إرسال الآن",
@@ -487,9 +599,16 @@ export const dict = {
"session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها", "session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
"session.revertDock.restore": "استعادة الرسالة", "session.revertDock.restore": "استعادة الرسالة",
"session.new.title": "ابنِ أي شيء", "session.new.title": "ابنِ أي شيء",
"session.new.project.new": "مشروع جديد",
"session.new.project.search": "البحث عن المشاريع",
"session.new.project.add": "إضافة مشروع",
"session.new.worktree.main": "الفرع الرئيسي", "session.new.worktree.main": "الفرع الرئيسي",
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})", "session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
"session.new.worktree.create": "إنشاء شجرة عمل جديدة", "session.new.worktree.create": "إنشاء شجرة عمل جديدة",
"session.new.workspace.runIn": "تشغيل الجلسة في",
"session.new.workspace.triggerLocal": "محلي",
"session.new.workspace.local": "المستودع المحلي",
"session.new.workspace.existing": "مساحة عمل…",
"session.new.lastModified": "آخر تعديل", "session.new.lastModified": "آخر تعديل",
"session.header.search.placeholder": "بحث {{project}}", "session.header.search.placeholder": "بحث {{project}}",
"session.header.searchFiles": "بحث عن الملفات", "session.header.searchFiles": "بحث عن الملفات",
@@ -550,6 +669,8 @@ export const dict = {
"sidebar.project.recentSessions": "الجلسات الحديثة", "sidebar.project.recentSessions": "الجلسات الحديثة",
"sidebar.project.viewAllSessions": "عرض جميع الجلسات", "sidebar.project.viewAllSessions": "عرض جميع الجلسات",
"sidebar.project.clearNotifications": "مسح الإشعارات", "sidebar.project.clearNotifications": "مسح الإشعارات",
"sidebar.empty.title": "لا توجد مشاريع مفتوحة",
"sidebar.empty.description": "افتح مشروعًا للبدء",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "سطح المكتب", "settings.section.desktop": "سطح المكتب",
"settings.section.server": "الخادم", "settings.section.server": "الخادم",
@@ -559,6 +680,7 @@ export const dict = {
"settings.desktop.wsl.title": "تكامل WSL", "settings.desktop.wsl.title": "تكامل WSL",
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.", "settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
"settings.general.section.appearance": "المظهر", "settings.general.section.appearance": "المظهر",
"settings.general.section.advanced": "متقدم",
"settings.general.section.notifications": "إشعارات النظام", "settings.general.section.notifications": "إشعارات النظام",
"settings.general.section.updates": "التحديثات", "settings.general.section.updates": "التحديثات",
"settings.general.section.sounds": "المؤثرات الصوتية", "settings.general.section.sounds": "المؤثرات الصوتية",
@@ -566,6 +688,11 @@ export const dict = {
"settings.general.section.display": "شاشة العرض", "settings.general.section.display": "شاشة العرض",
"settings.general.row.language.title": "اللغة", "settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode", "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
"settings.general.row.shell.title": "Shell المحطة الطرفية",
"settings.general.row.shell.description":
"اختر shell المستخدم في المحطة الطرفية. تُستخدم واجهات shell المتوافقة أيضًا في استدعاءات أدوات الوكيل.",
"settings.general.row.shell.autoDefault": "تلقائي (افتراضي)",
"settings.general.row.shell.terminalOnly": "المحطة الطرفية فقط",
"settings.general.row.appearance.title": "المظهر", "settings.general.row.appearance.title": "المظهر",
"settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك", "settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
"settings.general.row.colorScheme.title": "مخطط الألوان", "settings.general.row.colorScheme.title": "مخطط الألوان",
@@ -574,14 +701,29 @@ export const dict = {
"settings.general.row.theme.description": "تخصيص سمة OpenCode.", "settings.general.row.theme.description": "تخصيص سمة OpenCode.",
"settings.general.row.font.title": "خط الكود", "settings.general.row.font.title": "خط الكود",
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية", "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "خط الطرفية",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية",
"settings.general.row.uiFont.title": "خط الواجهة", "settings.general.row.uiFont.title": "خط الواجهة",
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها", "settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
"settings.general.row.followup.title": "سلوك المتابعة", "settings.general.row.followup.title": "سلوك المتابعة",
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار", "settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
"settings.general.row.followup.option.queue": "قائمة انتظار", "settings.general.row.followup.option.queue": "قائمة انتظار",
"settings.general.row.followup.option.steer": "توجيه", "settings.general.row.followup.option.steer": "توجيه",
"settings.general.row.showFileTree.title": "شجرة الملفات",
"settings.general.row.showFileTree.description": "إظهار لوحة شجرة الملفات في الجلسات",
"settings.general.row.showNavigation.title": "عناصر التحكم في التنقل",
"settings.general.row.showNavigation.description": "إظهار زري الرجوع والتقدم في شريط عنوان سطح المكتب",
"settings.general.row.showSearch.title": "لوحة الأوامر",
"settings.general.row.showSearch.description": "إظهار زر البحث ولوحة الأوامر في شريط العنوان",
"settings.general.row.showTerminal.title": "المحطة الطرفية",
"settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب",
"settings.general.row.showStatus.title": "حالة الخادم",
"settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان",
"settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي",
"settings.general.row.mobileTitlebarBottom.description":
"وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة",
"settings.general.row.showCustomAgents.title": "الوكلاء المخصصون",
"settings.general.row.showCustomAgents.description": "إظهار محدد الوكلاء في محرر الرسائل",
"settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج", "settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج",
"settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني", "settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell", "settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
@@ -590,6 +732,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit", "settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني", "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
"settings.general.row.newLayoutDesigns.title": "التخطيط والتصاميم الجديدة",
"settings.general.row.newLayoutDesigns.description":
"تمكين التخطيط والصفحة الرئيسية ومحرر الرسائل وواجهة الجلسة المعاد تصميمها",
"settings.general.row.pinchZoom.title": "التكبير بإيماءة القرص",
"settings.general.row.pinchZoom.description": "السماح بإيماءة القرص على لوحة اللمس وإيماءة Ctrl-scroll للتكبير",
"settings.general.row.wayland.title": "استخدام Wayland الأصلي", "settings.general.row.wayland.title": "استخدام Wayland الأصلي",
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.", "settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -602,6 +749,8 @@ export const dict = {
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة", "settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
"settings.updates.action.checkNow": "تحقق الآن", "settings.updates.action.checkNow": "تحقق الآن",
"settings.updates.action.checking": "جارٍ التحقق...", "settings.updates.action.checking": "جارٍ التحقق...",
"settings.updates.action.downloading": "جارٍ التنزيل...",
"settings.updates.action.installing": "جارٍ التثبيت...",
"settings.updates.toast.latest.title": "أنت على آخر إصدار", "settings.updates.toast.latest.title": "أنت على آخر إصدار",
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.", "settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
"sound.option.none": "بلا", "sound.option.none": "بلا",
@@ -721,8 +870,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "تحميل مهارة بالاسم", "settings.permissions.tool.skill.description": "تحميل مهارة بالاسم",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة", "settings.permissions.tool.lsp.description": "تشغيل استعلامات خادم اللغة",
"settings.permissions.tool.todowrite.title": "كتابة المهام",
"settings.permissions.tool.todowrite.description": "تحديث قائمة المهام",
"settings.permissions.tool.webfetch.title": "جلب الويب", "settings.permissions.tool.webfetch.title": "جلب الويب",
"settings.permissions.tool.webfetch.description": "جلب محتوى من عنوان URL", "settings.permissions.tool.webfetch.description": "جلب محتوى من عنوان URL",
"settings.permissions.tool.websearch.title": "بحث الويب", "settings.permissions.tool.websearch.title": "بحث الويب",
@@ -785,7 +932,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "تتبع ومراجعة والتراجع عن التغييرات في هذا المشروع", "session.review.noVcs.createGit.description": "تتبع ومراجعة والتراجع عن التغييرات في هذا المشروع",
"session.review.noVcs.createGit.actionLoading": "جاري إنشاء مستودع Git...", "session.review.noVcs.createGit.actionLoading": "جاري إنشاء مستودع Git...",
"session.review.noVcs.createGit.action": "إنشاء مستودع Git", "session.review.noVcs.createGit.action": "إنشاء مستودع Git",
"session.todo.progress": "تم إكمال {{done}} من {{total}} مهام",
"session.question.progress": "{{current}} من {{total}} أسئلة", "session.question.progress": "{{current}} من {{total}} أسئلة",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "مستكشف الملفات", "session.header.open.fileExplorer": "مستكشف الملفات",
+159 -9
View File
@@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Escuro", "theme.scheme.dark": "Escuro",
"command.sidebar.toggle": "Alternar barra lateral", "command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir projeto", "command.project.open": "Abrir projeto",
"command.project.previous": "Projeto anterior",
"command.project.next": "Próximo projeto",
"command.project.index": "Alternar para o projeto {{index}}",
"command.provider.connect": "Conectar provedor", "command.provider.connect": "Conectar provedor",
"command.server.switch": "Trocar servidor", "command.server.switch": "Trocar servidor",
"command.settings.open": "Abrir configurações", "command.settings.open": "Abrir configurações",
@@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nova sessão", "command.session.new": "Nova sessão",
"command.file.open": "Abrir arquivo", "command.file.open": "Abrir arquivo",
"command.tab.close": "Fechar aba", "command.tab.close": "Fechar aba",
"command.tab.reopenClosed": "Reabrir aba fechada",
"command.context.addSelection": "Adicionar seleção ao contexto", "command.context.addSelection": "Adicionar seleção ao contexto",
"command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual", "command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual",
"command.input.focus": "Focar entrada", "command.input.focus": "Focar entrada",
@@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "vídeo", "model.input.video": "vídeo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Permite: {{inputs}}", "model.tooltip.allows": "Permite: {{inputs}}",
"model.tooltip.context.label": "Contexto",
"model.tooltip.inputs": "Entradas",
"model.tooltip.model": "Modelo",
"model.tooltip.provider": "Provedor",
"model.tooltip.reasoning": "Raciocínio",
"model.tooltip.reasoning.allowed": "Permite raciocínio", "model.tooltip.reasoning.allowed": "Permite raciocínio",
"model.tooltip.reasoning.none": "Sem raciocínio", "model.tooltip.reasoning.none": "Sem raciocínio",
"model.tooltip.context": "Limite de contexto {{limit}}", "model.tooltip.context": "Limite de contexto {{limit}}",
@@ -202,6 +211,7 @@ export const dict = {
"common.loading": "Carregando", "common.loading": "Carregando",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Cancelar", "common.cancel": "Cancelar",
"common.clear": "Limpar",
"common.connect": "Conectar", "common.connect": "Conectar",
"common.disconnect": "Desconectar", "common.disconnect": "Desconectar",
"common.continue": "Enviar", "common.continue": "Enviar",
@@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc para sair", "prompt.mode.shell.exit": "esc para sair",
"session.child.promptDisabled": "Não é possível enviar prompts em sessões de subagentes.",
"session.child.backToParent": "Voltar à sessão principal.",
"prompt.example.1": "Corrigir um TODO no código", "prompt.example.1": "Corrigir um TODO no código",
"prompt.example.2": "Qual é a stack tecnológica deste projeto?", "prompt.example.2": "Qual é a stack tecnológica deste projeto?",
"prompt.example.3": "Corrigir testes quebrados", "prompt.example.3": "Corrigir testes quebrados",
@@ -255,6 +267,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Remover arquivo ativo do contexto", "prompt.context.removeActiveFile": "Remover arquivo ativo do contexto",
"prompt.context.removeFile": "Remover arquivo do contexto", "prompt.context.removeFile": "Remover arquivo do contexto",
"prompt.action.attachFile": "Anexar arquivo", "prompt.action.attachFile": "Anexar arquivo",
"prompt.menu.addImagesAndFiles": "Adicionar arquivos e mais",
"prompt.menu.imagesAndFiles": "Imagens e arquivos",
"prompt.menu.commands": "Comandos",
"prompt.menu.context": "Contexto",
"prompt.menu.shellCommand": "Comando shell",
"prompt.attachment.remove": "Remover anexo", "prompt.attachment.remove": "Remover anexo",
"prompt.action.send": "Enviar", "prompt.action.send": "Enviar",
"prompt.action.stop": "Parar", "prompt.action.stop": "Parar",
@@ -314,6 +331,75 @@ export const dict = {
"dialog.server.menu.delete": "Excluir", "dialog.server.menu.delete": "Excluir",
"dialog.server.current": "Servidor atual", "dialog.server.current": "Servidor atual",
"dialog.server.status.default": "Padrão", "dialog.server.status.default": "Padrão",
"wsl.server.add": "Adicionar servidor WSL",
"wsl.server.addShort": "Adicionar WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Tentar iniciar novamente",
"wsl.server.updating": "Atualizando...",
"wsl.onboarding.step.distro": "Escolher distribuição",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Verificando o WSL...",
"wsl.onboarding.restartRequired": "O Windows precisa ser reiniciado para concluir a instalação do WSL.",
"wsl.onboarding.ready": "O WSL está pronto.",
"wsl.onboarding.required": "O WSL é necessário para continuar.",
"wsl.onboarding.checkingDistros": "Verificando distribuições...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Verificando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuições...",
"wsl.onboarding.distroReady": "{{distro}} está pronta.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ainda não está instalada.",
"wsl.onboarding.openDistroOnce": "Abra {{distro}} uma vez para concluir a configuração.",
"wsl.onboarding.finishingDistro": "Concluindo a configuração de {{distro}}.",
"wsl.onboarding.pickDistro": "Escolha uma distribuição ou instale uma abaixo.",
"wsl.onboarding.checkingOpencode": "Verificando o OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}...",
"wsl.onboarding.updatingOpencode": "Atualizando o OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Atualize o OpenCode em {{distro}}.",
"wsl.onboarding.updateOpencode": "Atualizar o OpenCode",
"wsl.onboarding.opencodeReadyIn": "O OpenCode está pronto em {{distro}}.",
"wsl.onboarding.opencodeReady": "O OpenCode está pronto.",
"wsl.onboarding.installOpencodeIn": "Instale o OpenCode em {{distro}}.",
"wsl.onboarding.installOpencode": "Instalar o OpenCode",
"wsl.onboarding.chooseDistroFirst": "Escolha primeiro uma distribuição.",
"wsl.onboarding.loadFailed": "Falha ao carregar o estado do WSL.",
"wsl.onboarding.loading": "Carregando...",
"wsl.onboarding.installedDistros": "Distribuições instaladas",
"wsl.onboarding.checkAgain": "Verificar novamente",
"wsl.onboarding.distroStatus.ready": "Pronta",
"wsl.onboarding.distroStatus.checking": "Verificando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode não instalado",
"wsl.onboarding.distroStatus.missingTools": "bash e curl ausentes",
"wsl.onboarding.distroStatus.unsupported": "Sem suporte · Use o WSL 2",
"wsl.onboarding.needAnotherDistro": "Precisa de outra distribuição?",
"wsl.onboarding.needAnotherDistroHint": "Instale uma distribuição Linux pelo catálogo do WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL não instalado",
"wsl.onboarding.wslNotInstalled.description":
"O WSL (Windows Subsystem for Linux) é necessário para que o OpenCode possa adicionar um servidor WSL",
"wsl.onboarding.wslUnavailable.title": "WSL indisponível",
"wsl.onboarding.wslUnavailable.description": "O OpenCode não conseguiu verificar o WSL neste computador.",
"wsl.onboarding.installWsl": "Instalar o WSL",
"wsl.onboarding.windowsRestartRequired":
"Reinicie o Windows para concluir a instalação do WSL e abra o OpenCode novamente.",
"wsl.onboarding.next": "Próximo",
"wsl.onboarding.refresh": "Atualizar",
"wsl.onboarding.allDistrosAdded": "Todas as distribuições instaladas já foram adicionadas.",
"wsl.onboarding.noDistros": "Nenhuma distribuição detectada ainda.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribuição",
"wsl.onboarding.searchDistros": "Buscar distribuições",
"wsl.onboarding.wsl2Required": "O WSL 2 é necessário.",
"wsl.onboarding.toolsRequired": "Esta distribuição precisa do bash e do curl.",
"wsl.onboarding.openTerminal": "Abrir terminal",
"wsl.onboarding.path": "Caminho: {{path}}",
"wsl.onboarding.notFound": "não encontrado",
"wsl.onboarding.version": "Versão: {{version}}",
"wsl.onboarding.unknown": "desconhecido",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.",
"wsl.onboarding.adding": "Adicionando...",
"dialog.project.edit.title": "Editar projeto", "dialog.project.edit.title": "Editar projeto",
"dialog.project.edit.name": "Nome", "dialog.project.edit.name": "Nome",
"dialog.project.edit.icon": "Ícone", "dialog.project.edit.icon": "Ícone",
@@ -371,6 +457,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Idioma", "toast.language.title": "Idioma",
@@ -407,10 +494,12 @@ export const dict = {
"toast.update.action.notYet": "Agora não", "toast.update.action.notYet": "Agora não",
"error.page.title": "Algo deu errado", "error.page.title": "Algo deu errado",
"error.page.description": "Ocorreu um erro ao carregar a aplicação.", "error.page.description": "Ocorreu um erro ao carregar a aplicação.",
"error.page.description.localServerStartup": "Ocorreu um erro ao iniciar o servidor local.",
"error.page.details.label": "Detalhes do Erro", "error.page.details.label": "Detalhes do Erro",
"error.page.action.restart": "Reiniciar", "error.page.action.restart": "Reiniciar",
"error.page.action.report": "Reportar erro", "error.page.action.report": "Reportar erro",
"error.page.action.reported": "Erro reportado", "error.page.action.reported": "Erro reportado",
"error.page.action.exportLogs": "Exportar logs",
"error.page.action.checking": "Verificando...", "error.page.action.checking": "Verificando...",
"error.page.action.checkUpdates": "Verificar atualizações", "error.page.action.checkUpdates": "Verificar atualizações",
"error.page.action.updateTo": "Atualizar para {{version}}", "error.page.action.updateTo": "Atualizar para {{version}}",
@@ -453,10 +542,32 @@ export const dict = {
"home.recentProjects": "Projetos recentes", "home.recentProjects": "Projetos recentes",
"home.empty.title": "Nenhum projeto recente", "home.empty.title": "Nenhum projeto recente",
"home.empty.description": "Comece abrindo um projeto local", "home.empty.description": "Comece abrindo um projeto local",
"home.title": "Início",
"home.projects": "Projetos",
"home.project.add": "Adicionar projeto",
"home.recentlyClosed": "Projetos fechados recentemente",
"home.server.collapse": "Recolher projetos do servidor",
"home.server.expand": "Expandir projetos do servidor",
"home.sessions.search.placeholder": "Buscar sessões",
"home.sessions.search.placeholder.scoped": "Buscar sessões em {{scope}}",
"home.sessions.search.sessions": "Sessões",
"home.sessions.search.noResults": "Nenhuma sessão encontrada para {{query}}",
"home.sessions.empty": "Nada por aqui ainda",
"home.sessions.empty.description": "Crie uma sessão para começar",
"home.sessions.group.today": "Hoje",
"home.sessions.group.yesterday": "Ontem",
"home.sessions.group.older": "Mais antigas",
"home.providerTip":
"Conecte-se a mais de 75 provedores para usar outros modelos, incluindo Claude, GPT, Gemini e muito mais",
"session.tab.session": "Sessão", "session.tab.session": "Sessão",
"session.tab.review": "Revisão", "session.tab.review": "Revisão",
"session.tab.context": "Contexto", "session.tab.context": "Contexto",
"session.tab.unknown": "Sessão desconhecida",
"session.panel.reviewAndFiles": "Revisão e arquivos", "session.panel.reviewAndFiles": "Revisão e arquivos",
"session.error.notFound": "Esta sessão não foi encontrada",
"session.error.notFound.description": "Esta aba aponta para uma sessão que não existe mais neste servidor.",
"session.error.notFound.closeTab": "Fechar aba",
"session.error.serverConnection": "Não é possível conectar a este servidor",
"session.review.filesChanged": "{{count}} Arquivos Alterados", "session.review.filesChanged": "{{count}} Arquivos Alterados",
"session.review.change.one": "Alteração", "session.review.change.one": "Alteração",
"session.review.change.other": "Alterações", "session.review.change.other": "Alterações",
@@ -466,6 +577,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis", "O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis",
"session.review.noChanges": "Sem alterações", "session.review.noChanges": "Sem alterações",
"session.review.noUncommittedChanges": "Ainda não há alterações sem commit",
"session.review.noBranchChanges": "Ainda não há alterações na branch",
"session.files.selectToOpen": "Selecione um arquivo para abrir", "session.files.selectToOpen": "Selecione um arquivo para abrir",
"session.files.all": "Todos os arquivos", "session.files.all": "Todos os arquivos",
"session.files.empty": "Nenhum arquivo", "session.files.empty": "Nenhum arquivo",
@@ -476,9 +589,10 @@ export const dict = {
"session.messages.loading": "Carregando mensagens...", "session.messages.loading": "Carregando mensagens...",
"session.messages.jumpToLatest": "Ir para a mais recente", "session.messages.jumpToLatest": "Ir para a mais recente",
"session.context.addToContext": "Adicionar {{selection}} ao contexto", "session.context.addToContext": "Adicionar {{selection}} ao contexto",
"session.todo.title": "Tarefas", "session.question.minimize": "Minimizar pergunta",
"session.todo.collapse": "Recolher", "session.question.restore": "Restaurar pergunta",
"session.todo.expand": "Expandir", "session.question.pending.one": "{{count}} pergunta pendente",
"session.question.pending.other": "{{count}} perguntas pendentes",
"session.followupDock.summary.one": "{{count}} mensagem na fila", "session.followupDock.summary.one": "{{count}} mensagem na fila",
"session.followupDock.summary.other": "{{count}} mensagens na fila", "session.followupDock.summary.other": "{{count}} mensagens na fila",
"session.followupDock.sendNow": "Enviar agora", "session.followupDock.sendNow": "Enviar agora",
@@ -491,9 +605,16 @@ export const dict = {
"session.revertDock.expand": "Expandir mensagens revertidas", "session.revertDock.expand": "Expandir mensagens revertidas",
"session.revertDock.restore": "Restaurar mensagem", "session.revertDock.restore": "Restaurar mensagem",
"session.new.title": "Crie qualquer coisa", "session.new.title": "Crie qualquer coisa",
"session.new.project.new": "Novo projeto",
"session.new.project.search": "Buscar projetos",
"session.new.project.add": "Adicionar projeto",
"session.new.worktree.main": "Branch principal", "session.new.worktree.main": "Branch principal",
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})", "session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
"session.new.worktree.create": "Criar novo worktree", "session.new.worktree.create": "Criar novo worktree",
"session.new.workspace.runIn": "Executar sessão em",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Repositório local",
"session.new.workspace.existing": "Espaço de trabalho…",
"session.new.lastModified": "Última modificação", "session.new.lastModified": "Última modificação",
"session.header.search.placeholder": "Buscar {{project}}", "session.header.search.placeholder": "Buscar {{project}}",
"session.header.searchFiles": "Buscar arquivos", "session.header.searchFiles": "Buscar arquivos",
@@ -557,15 +678,18 @@ export const dict = {
"sidebar.project.recentSessions": "Sessões recentes", "sidebar.project.recentSessions": "Sessões recentes",
"sidebar.project.viewAllSessions": "Ver todas as sessões", "sidebar.project.viewAllSessions": "Ver todas as sessões",
"sidebar.project.clearNotifications": "Limpar notificações", "sidebar.project.clearNotifications": "Limpar notificações",
"sidebar.empty.title": "Nenhum projeto aberto",
"sidebar.empty.description": "Abra um projeto para começar",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Servidor", "settings.section.server": "Servidor",
"settings.tab.general": "Geral", "settings.tab.general": "Geral",
"settings.tab.shortcuts": "Atalhos", "settings.tab.shortcuts": "Atalhos",
"settings.desktop.section.wsl": "WSL", "settings.desktop.section.wsl": "WSL",
"settings.desktop.wsl.title": "WSL integration", "settings.desktop.wsl.title": "Integração com WSL",
"settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.", "settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.",
"settings.general.section.appearance": "Aparência", "settings.general.section.appearance": "Aparência",
"settings.general.section.advanced": "Avançado",
"settings.general.section.notifications": "Notificações do sistema", "settings.general.section.notifications": "Notificações do sistema",
"settings.general.section.updates": "Atualizações", "settings.general.section.updates": "Atualizações",
"settings.general.section.sounds": "Efeitos sonoros", "settings.general.section.sounds": "Efeitos sonoros",
@@ -573,6 +697,11 @@ export const dict = {
"settings.general.section.display": "Tela", "settings.general.section.display": "Tela",
"settings.general.row.language.title": "Idioma", "settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode", "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
"settings.general.row.shell.title": "Shell do terminal",
"settings.general.row.shell.description":
"Escolha o shell usado no terminal. Os shells compatíveis também são usados nas chamadas de ferramentas do agente.",
"settings.general.row.shell.autoDefault": "Automático (Padrão)",
"settings.general.row.shell.terminalOnly": "somente no terminal",
"settings.general.row.appearance.title": "Aparência", "settings.general.row.appearance.title": "Aparência",
"settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo", "settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
"settings.general.row.colorScheme.title": "Esquema de cores", "settings.general.row.colorScheme.title": "Esquema de cores",
@@ -581,8 +710,8 @@ export const dict = {
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.", "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
"settings.general.row.font.title": "Fonte de código", "settings.general.row.font.title": "Fonte de código",
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código", "settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Fonte do terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal",
"settings.general.row.uiFont.title": "Fonte da interface", "settings.general.row.uiFont.title": "Fonte da interface",
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface", "settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
"settings.general.row.followup.title": "Comportamento de acompanhamento", "settings.general.row.followup.title": "Comportamento de acompanhamento",
@@ -590,6 +719,22 @@ export const dict = {
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila", "Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
"settings.general.row.followup.option.queue": "Fila", "settings.general.row.followup.option.queue": "Fila",
"settings.general.row.followup.option.steer": "Orientar", "settings.general.row.followup.option.steer": "Orientar",
"settings.general.row.showFileTree.title": "Árvore de arquivos",
"settings.general.row.showFileTree.description": "Mostrar o painel da árvore de arquivos nas sessões",
"settings.general.row.showNavigation.title": "Controles de navegação",
"settings.general.row.showNavigation.description":
"Mostrar os botões de voltar e avançar na barra de título do desktop",
"settings.general.row.showSearch.title": "Paleta de comandos",
"settings.general.row.showSearch.description": "Mostrar o botão de busca e paleta de comandos na barra de título",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop",
"settings.general.row.showStatus.title": "Status do servidor",
"settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegação inferior",
"settings.general.row.mobileTitlebarBottom.description":
"Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis",
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
"settings.general.row.showCustomAgents.description": "Mostrar o seletor de agentes na área de composição",
"settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio", "settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio",
"settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo", "settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell", "settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
@@ -598,6 +743,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição", "settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo", "Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
"settings.general.row.newLayoutDesigns.title": "Novo layout e design",
"settings.general.row.newLayoutDesigns.description":
"Ativar o layout, a página inicial, a área de composição e a interface de sessão reformulados",
"settings.general.row.pinchZoom.title": "Zoom com gesto de pinça",
"settings.general.row.pinchZoom.description":
"Permitir gestos de pinça no trackpad e de Ctrl+rolagem para aplicar zoom",
"settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.", "settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -610,6 +761,8 @@ export const dict = {
"settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver", "settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver",
"settings.updates.action.checkNow": "Verificar agora", "settings.updates.action.checkNow": "Verificar agora",
"settings.updates.action.checking": "Verificando...", "settings.updates.action.checking": "Verificando...",
"settings.updates.action.downloading": "Baixando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Você está atualizado", "settings.updates.toast.latest.title": "Você está atualizado",
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.", "settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
"sound.option.none": "Nenhum", "sound.option.none": "Nenhum",
@@ -731,8 +884,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Carregar uma habilidade por nome", "settings.permissions.tool.skill.description": "Carregar uma habilidade por nome",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Executar consultas de servidor de linguagem", "settings.permissions.tool.lsp.description": "Executar consultas de servidor de linguagem",
"settings.permissions.tool.todowrite.title": "Escrever Tarefas",
"settings.permissions.tool.todowrite.description": "Atualizar a lista de tarefas",
"settings.permissions.tool.webfetch.title": "Buscar Web", "settings.permissions.tool.webfetch.title": "Buscar Web",
"settings.permissions.tool.webfetch.description": "Buscar conteúdo de uma URL", "settings.permissions.tool.webfetch.description": "Buscar conteúdo de uma URL",
"settings.permissions.tool.websearch.title": "Pesquisa Web", "settings.permissions.tool.websearch.title": "Pesquisa Web",
@@ -795,7 +946,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Rastreie, revise e desfaça alterações neste projeto", "session.review.noVcs.createGit.description": "Rastreie, revise e desfaça alterações neste projeto",
"session.review.noVcs.createGit.actionLoading": "Criando repositório Git...", "session.review.noVcs.createGit.actionLoading": "Criando repositório Git...",
"session.review.noVcs.createGit.action": "Criar repositório Git", "session.review.noVcs.createGit.action": "Criar repositório Git",
"session.todo.progress": "{{done}} de {{total}} tarefas concluídas",
"session.question.progress": "{{current}} de {{total}} perguntas", "session.question.progress": "{{current}} de {{total}} perguntas",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Explorador de Arquivos", "session.header.open.fileExplorer": "Explorador de Arquivos",
+158 -8
View File
@@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku", "command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
"command.project.open": "Otvori projekat", "command.project.open": "Otvori projekat",
"command.project.previous": "Prethodni projekat",
"command.project.next": "Sljedeći projekat",
"command.project.index": "Prebaci na projekat {{index}}",
"command.provider.connect": "Poveži provajdera", "command.provider.connect": "Poveži provajdera",
"command.server.switch": "Promijeni server", "command.server.switch": "Promijeni server",
"command.settings.open": "Otvori postavke", "command.settings.open": "Otvori postavke",
@@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Nova sesija", "command.session.new": "Nova sesija",
"command.file.open": "Otvori datoteku", "command.file.open": "Otvori datoteku",
"command.tab.close": "Zatvori karticu", "command.tab.close": "Zatvori karticu",
"command.tab.reopenClosed": "Ponovo otvori zatvorenu karticu",
"command.context.addSelection": "Dodaj odabir u kontekst", "command.context.addSelection": "Dodaj odabir u kontekst",
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke", "command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
"command.input.focus": "Fokusiraj polje za unos", "command.input.focus": "Fokusiraj polje za unos",
@@ -209,6 +213,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Podržava: {{inputs}}", "model.tooltip.allows": "Podržava: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Ulazi",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Provajder",
"model.tooltip.reasoning": "Rasuđivanje",
"model.tooltip.reasoning.allowed": "Podržava rasuđivanje", "model.tooltip.reasoning.allowed": "Podržava rasuđivanje",
"model.tooltip.reasoning.none": "Bez rasuđivanja", "model.tooltip.reasoning.none": "Bez rasuđivanja",
"model.tooltip.context": "Limit konteksta {{limit}}", "model.tooltip.context": "Limit konteksta {{limit}}",
@@ -219,6 +228,7 @@ export const dict = {
"common.loading": "Učitavanje", "common.loading": "Učitavanje",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Otkaži", "common.cancel": "Otkaži",
"common.clear": "Očisti",
"common.connect": "Poveži", "common.connect": "Poveži",
"common.disconnect": "Prekini vezu", "common.disconnect": "Prekini vezu",
"common.continue": "Pošalji", "common.continue": "Pošalji",
@@ -236,6 +246,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc za izlaz", "prompt.mode.shell.exit": "esc za izlaz",
"session.child.promptDisabled": "Sesijama podagenta nije moguće slati upite.",
"session.child.backToParent": "Nazad na glavnu sesiju.",
"prompt.example.1": "Popravi TODO u bazi koda", "prompt.example.1": "Popravi TODO u bazi koda",
"prompt.example.2": "Koji je tehnološki stack ovog projekta?", "prompt.example.2": "Koji je tehnološki stack ovog projekta?",
@@ -275,6 +287,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta", "prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta",
"prompt.context.removeFile": "Ukloni datoteku iz konteksta", "prompt.context.removeFile": "Ukloni datoteku iz konteksta",
"prompt.action.attachFile": "Priloži datoteku", "prompt.action.attachFile": "Priloži datoteku",
"prompt.menu.addImagesAndFiles": "Dodaj datoteke i više",
"prompt.menu.imagesAndFiles": "Slike i datoteke",
"prompt.menu.commands": "Komande",
"prompt.menu.context": "Kontekst",
"prompt.menu.shellCommand": "Shell naredba",
"prompt.attachment.remove": "Ukloni prilog", "prompt.attachment.remove": "Ukloni prilog",
"prompt.action.send": "Pošalji", "prompt.action.send": "Pošalji",
"prompt.action.stop": "Zaustavi", "prompt.action.stop": "Zaustavi",
@@ -342,6 +359,75 @@ export const dict = {
"dialog.server.menu.delete": "Izbriši", "dialog.server.menu.delete": "Izbriši",
"dialog.server.current": "Trenutni server", "dialog.server.current": "Trenutni server",
"dialog.server.status.default": "Podrazumijevano", "dialog.server.status.default": "Podrazumijevano",
"wsl.server.add": "Dodaj WSL server",
"wsl.server.addShort": "Dodaj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL server",
"wsl.server.retryStart": "Ponovi pokretanje",
"wsl.server.updating": "Ažuriranje...",
"wsl.onboarding.step.distro": "Odaberi distribuciju",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Provjera WSL-a...",
"wsl.onboarding.restartRequired": "Potrebno je ponovo pokrenuti Windows da bi se završila instalacija WSL-a.",
"wsl.onboarding.ready": "WSL je spreman.",
"wsl.onboarding.required": "WSL je potreban za nastavak.",
"wsl.onboarding.checkingDistros": "Provjera distribucija...",
"wsl.onboarding.installingDistro": "Instaliranje {{distro}}...",
"wsl.onboarding.checkingDistro": "Provjera {{distro}}...",
"wsl.onboarding.listingDistros": "Učitavanje liste distribucija...",
"wsl.onboarding.distroReady": "Distribucija {{distro}} je spremna.",
"wsl.onboarding.distroNotInstalled": "Distribucija {{distro}} još nije instalirana.",
"wsl.onboarding.openDistroOnce": "Otvori {{distro}} jednom da završiš postavljanje.",
"wsl.onboarding.finishingDistro": "Završavanje postavljanja za {{distro}}.",
"wsl.onboarding.pickDistro": "Odaberi distribuciju ili instaliraj jednu ispod.",
"wsl.onboarding.checkingOpencode": "Provjera OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Ažuriraj OpenCode u distribuciji {{distro}}.",
"wsl.onboarding.updateOpencode": "Ažuriraj OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode je spreman u distribuciji {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode je spreman.",
"wsl.onboarding.installOpencodeIn": "Instaliraj OpenCode u distribuciji {{distro}}.",
"wsl.onboarding.installOpencode": "Instaliraj OpenCode",
"wsl.onboarding.chooseDistroFirst": "Prvo odaberi distribuciju.",
"wsl.onboarding.loadFailed": "Nije uspjelo učitavanje WSL stanja.",
"wsl.onboarding.loading": "Učitavanje...",
"wsl.onboarding.installedDistros": "Instalirane distribucije",
"wsl.onboarding.checkAgain": "Provjeri ponovo",
"wsl.onboarding.distroStatus.ready": "Spremno",
"wsl.onboarding.distroStatus.checking": "Provjera...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nije instaliran",
"wsl.onboarding.distroStatus.missingTools": "Nedostaju bash i curl",
"wsl.onboarding.distroStatus.unsupported": "Nije podržano · Koristi WSL 2",
"wsl.onboarding.needAnotherDistro": "Treba ti druga distribucija?",
"wsl.onboarding.needAnotherDistroHint": "Instaliraj Linux distribuciju iz WSL kataloga",
"wsl.onboarding.wslNotInstalled.title": "WSL nije instaliran",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) je potreban da bi OpenCode mogao dodati WSL server",
"wsl.onboarding.wslUnavailable.title": "WSL nije dostupan",
"wsl.onboarding.wslUnavailable.description": "OpenCode nije mogao provjeriti WSL na ovom uređaju.",
"wsl.onboarding.installWsl": "Instaliraj WSL",
"wsl.onboarding.windowsRestartRequired":
"Ponovo pokreni Windows da završiš instalaciju WSL-a, zatim ponovo otvori OpenCode.",
"wsl.onboarding.next": "Dalje",
"wsl.onboarding.refresh": "Osvježi",
"wsl.onboarding.allDistrosAdded": "Sve instalirane distribucije su već dodane.",
"wsl.onboarding.noDistros": "Još nema otkrivenih distribucija.",
"wsl.onboarding.install": "Instaliraj",
"wsl.onboarding.installing": "Instaliranje...",
"wsl.onboarding.installDistro": "Instaliraj distribuciju",
"wsl.onboarding.searchDistros": "Pretraži distribucije",
"wsl.onboarding.wsl2Required": "Potreban je WSL 2.",
"wsl.onboarding.toolsRequired": "Ovoj distribuciji su potrebni bash i curl.",
"wsl.onboarding.openTerminal": "Otvori terminal",
"wsl.onboarding.path": "Putanja: {{path}}",
"wsl.onboarding.notFound": "nije pronađeno",
"wsl.onboarding.version": "Verzija: {{version}}",
"wsl.onboarding.unknown": "nepoznato",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.",
"wsl.onboarding.adding": "Dodavanje...",
"dialog.project.edit.title": "Uredi projekat", "dialog.project.edit.title": "Uredi projekat",
"dialog.project.edit.name": "Naziv", "dialog.project.edit.name": "Naziv",
@@ -405,6 +491,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -453,10 +540,12 @@ export const dict = {
"error.page.title": "Nešto je pošlo po zlu", "error.page.title": "Nešto je pošlo po zlu",
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.", "error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
"error.page.description.localServerStartup": "Došlo je do greške pri pokretanju lokalnog servera.",
"error.page.details.label": "Detalji greške", "error.page.details.label": "Detalji greške",
"error.page.action.restart": "Restartuj", "error.page.action.restart": "Restartuj",
"error.page.action.report": "Prijavi grešku", "error.page.action.report": "Prijavi grešku",
"error.page.action.reported": "Greška prijavljena", "error.page.action.reported": "Greška prijavljena",
"error.page.action.exportLogs": "Izvezi logove",
"error.page.action.checking": "Provjera...", "error.page.action.checking": "Provjera...",
"error.page.action.checkUpdates": "Provjeri ažuriranja", "error.page.action.checkUpdates": "Provjeri ažuriranja",
"error.page.action.updateTo": "Ažuriraj na {{version}}", "error.page.action.updateTo": "Ažuriraj na {{version}}",
@@ -504,11 +593,33 @@ export const dict = {
"home.recentProjects": "Nedavni projekti", "home.recentProjects": "Nedavni projekti",
"home.empty.title": "Nema nedavnih projekata", "home.empty.title": "Nema nedavnih projekata",
"home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat", "home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat",
"home.title": "Početna",
"home.projects": "Projekti",
"home.project.add": "Dodaj projekat",
"home.recentlyClosed": "Nedavno zatvoreni projekti",
"home.server.collapse": "Sažmi projekte servera",
"home.server.expand": "Proširi projekte servera",
"home.sessions.search.placeholder": "Pretraži sesije",
"home.sessions.search.placeholder.scoped": "Pretraži sesije u {{scope}}",
"home.sessions.search.sessions": "Sesije",
"home.sessions.search.noResults": "Nema pronađenih sesija za {{query}}",
"home.sessions.empty": "Ovdje još nema ničega",
"home.sessions.empty.description": "Kreiraj sesiju za početak",
"home.sessions.group.today": "Danas",
"home.sessions.group.yesterday": "Jučer",
"home.sessions.group.older": "Starije",
"home.providerTip":
"Povežite se s više od 75 pružalaca usluga kako biste koristili druge modele, uključujući Claude, GPT, Gemini i druge",
"session.tab.session": "Sesija", "session.tab.session": "Sesija",
"session.tab.review": "Pregled", "session.tab.review": "Pregled",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Nepoznata sesija",
"session.panel.reviewAndFiles": "Pregled i datoteke", "session.panel.reviewAndFiles": "Pregled i datoteke",
"session.error.notFound": "Ova sesija nije pronađena",
"session.error.notFound.description": "Ova kartica upućuje na sesiju koja više ne postoji na ovom serveru.",
"session.error.notFound.closeTab": "Zatvori karticu",
"session.error.serverConnection": "Nije moguće povezati se s ovim serverom",
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka", "session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
"session.review.change.one": "Izmjena", "session.review.change.one": "Izmjena",
"session.review.change.other": "Izmjene", "session.review.change.other": "Izmjene",
@@ -518,6 +629,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne", "Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
"session.review.noChanges": "Nema izmjena", "session.review.noChanges": "Nema izmjena",
"session.review.noUncommittedChanges": "Još nema nepotvrđenih promjena",
"session.review.noBranchChanges": "Još nema promjena na grani",
"session.files.selectToOpen": "Odaberi datoteku za otvaranje", "session.files.selectToOpen": "Odaberi datoteku za otvaranje",
"session.files.all": "Sve datoteke", "session.files.all": "Sve datoteke",
@@ -531,9 +644,10 @@ export const dict = {
"session.messages.jumpToLatest": "Idi na najnovije", "session.messages.jumpToLatest": "Idi na najnovije",
"session.context.addToContext": "Dodaj {{selection}} u kontekst", "session.context.addToContext": "Dodaj {{selection}} u kontekst",
"session.todo.title": "Zadaci", "session.question.minimize": "Minimiziraj pitanje",
"session.todo.collapse": "Sažmi", "session.question.restore": "Vrati pitanje",
"session.todo.expand": "Proširi", "session.question.pending.one": "{{count}} pitanje na čekanju",
"session.question.pending.other": "{{count}} pitanja na čekanju",
"session.followupDock.summary.one": "{{count}} poruka na čekanju", "session.followupDock.summary.one": "{{count}} poruka na čekanju",
"session.followupDock.summary.other": "{{count}} poruka na čekanju", "session.followupDock.summary.other": "{{count}} poruka na čekanju",
"session.followupDock.sendNow": "Pošalji sada", "session.followupDock.sendNow": "Pošalji sada",
@@ -547,9 +661,16 @@ export const dict = {
"session.revertDock.restore": "Vrati poruku", "session.revertDock.restore": "Vrati poruku",
"session.new.title": "Napravi bilo šta", "session.new.title": "Napravi bilo šta",
"session.new.project.new": "Novi projekat",
"session.new.project.search": "Pretraži projekte",
"session.new.project.add": "Dodaj projekat",
"session.new.worktree.main": "Glavna grana", "session.new.worktree.main": "Glavna grana",
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})", "session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
"session.new.worktree.create": "Kreiraj novi worktree", "session.new.worktree.create": "Kreiraj novi worktree",
"session.new.workspace.runIn": "Pokreni sesiju u",
"session.new.workspace.triggerLocal": "Lokalno",
"session.new.workspace.local": "Lokalni repozitorij",
"session.new.workspace.existing": "Radni prostor…",
"session.new.lastModified": "Posljednja izmjena", "session.new.lastModified": "Posljednja izmjena",
"session.header.search.placeholder": "Pretraži {{project}}", "session.header.search.placeholder": "Pretraži {{project}}",
@@ -618,6 +739,8 @@ export const dict = {
"sidebar.project.recentSessions": "Nedavne sesije", "sidebar.project.recentSessions": "Nedavne sesije",
"sidebar.project.viewAllSessions": "Prikaži sve sesije", "sidebar.project.viewAllSessions": "Prikaži sve sesije",
"sidebar.project.clearNotifications": "Očisti obavijesti", "sidebar.project.clearNotifications": "Očisti obavijesti",
"sidebar.empty.title": "Nema otvorenih projekata",
"sidebar.empty.description": "Otvori projekat za početak",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@@ -630,6 +753,7 @@ export const dict = {
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.", "settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
"settings.general.section.appearance": "Izgled", "settings.general.section.appearance": "Izgled",
"settings.general.section.advanced": "Napredno",
"settings.general.section.notifications": "Sistemske obavijesti", "settings.general.section.notifications": "Sistemske obavijesti",
"settings.general.section.updates": "Ažuriranja", "settings.general.section.updates": "Ažuriranja",
"settings.general.section.sounds": "Zvučni efekti", "settings.general.section.sounds": "Zvučni efekti",
@@ -638,6 +762,11 @@ export const dict = {
"settings.general.row.language.title": "Jezik", "settings.general.row.language.title": "Jezik",
"settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u", "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
"settings.general.row.shell.title": "Shell terminala",
"settings.general.row.shell.description":
"Odaberi shell koji se koristi za terminal. Kompatibilni shellovi koriste se i za pozive alata agenta.",
"settings.general.row.shell.autoDefault": "Automatski (podrazumijevano)",
"settings.general.row.shell.terminalOnly": "samo terminal",
"settings.general.row.appearance.title": "Izgled", "settings.general.row.appearance.title": "Izgled",
"settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju", "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
"settings.general.row.colorScheme.title": "Šema boja", "settings.general.row.colorScheme.title": "Šema boja",
@@ -646,14 +775,30 @@ export const dict = {
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
"settings.general.row.font.title": "Font za kod", "settings.general.row.font.title": "Font za kod",
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda", "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Font terminala",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu",
"settings.general.row.uiFont.title": "UI font", "settings.general.row.uiFont.title": "UI font",
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu", "settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
"settings.general.row.followup.title": "Ponašanje nadovezivanja", "settings.general.row.followup.title": "Ponašanje nadovezivanja",
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu", "settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
"settings.general.row.followup.option.queue": "Red čekanja", "settings.general.row.followup.option.queue": "Red čekanja",
"settings.general.row.followup.option.steer": "Usmjeri", "settings.general.row.followup.option.steer": "Usmjeri",
"settings.general.row.showFileTree.title": "Stablo datoteka",
"settings.general.row.showFileTree.description": "Prikaži stablo datoteka u sesijama",
"settings.general.row.showNavigation.title": "Kontrole navigacije",
"settings.general.row.showNavigation.description":
"Prikaži dugmad za nazad i naprijed u naslovnoj traci desktop aplikacije",
"settings.general.row.showSearch.title": "Paleta komandi",
"settings.general.row.showSearch.description": "Prikaži dugme za pretragu i paletu komandi u naslovnoj traci",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije",
"settings.general.row.showStatus.title": "Status servera",
"settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci",
"settings.general.row.mobileTitlebarBottom.title": "Donja navigacija",
"settings.general.row.mobileTitlebarBottom.description":
"Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima",
"settings.general.row.showCustomAgents.title": "Prilagođeni agenti",
"settings.general.row.showCustomAgents.description": "Prikaži izbor agenta u uređivaču poruke",
"settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja", "settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja",
"settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci", "settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci",
@@ -663,6 +808,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje", "settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci", "Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
"settings.general.row.newLayoutDesigns.title": "Novi raspored i dizajn",
"settings.general.row.newLayoutDesigns.description":
"Omogući redizajnirani raspored, početnu stranicu, uređivač poruke i interfejs sesije",
"settings.general.row.pinchZoom.title": "Zumiranje štipanjem",
"settings.general.row.pinchZoom.description":
"Dozvoli zumiranje gestom štipanja na dodirnoj ploči i pomoću Ctrl-pomjeranja",
"settings.general.row.wayland.title": "Koristi nativni Wayland", "settings.general.row.wayland.title": "Koristi nativni Wayland",
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.", "settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -677,6 +828,8 @@ export const dict = {
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna", "settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
"settings.updates.action.checkNow": "Provjeri sada", "settings.updates.action.checkNow": "Provjeri sada",
"settings.updates.action.checking": "Provjera...", "settings.updates.action.checking": "Provjera...",
"settings.updates.action.downloading": "Preuzimanje...",
"settings.updates.action.installing": "Instaliranje...",
"settings.updates.toast.latest.title": "Sve je ažurno", "settings.updates.toast.latest.title": "Sve je ažurno",
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.", "settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
"sound.option.none": "Nijedan", "sound.option.none": "Nijedan",
@@ -805,8 +958,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu", "settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera", "settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera",
"settings.permissions.tool.todowrite.title": "Ažuriranje liste zadataka",
"settings.permissions.tool.todowrite.description": "Ažuriraj listu zadataka",
"settings.permissions.tool.webfetch.title": "Web preuzimanje", "settings.permissions.tool.webfetch.title": "Web preuzimanje",
"settings.permissions.tool.webfetch.description": "Preuzmi sadržaj sa URL-a", "settings.permissions.tool.webfetch.description": "Preuzmi sadržaj sa URL-a",
"settings.permissions.tool.websearch.title": "Web pretraga", "settings.permissions.tool.websearch.title": "Web pretraga",
@@ -871,7 +1022,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Pratite, pregledajte i poništite promjene u ovom projektu", "session.review.noVcs.createGit.description": "Pratite, pregledajte i poništite promjene u ovom projektu",
"session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija...", "session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija...",
"session.review.noVcs.createGit.action": "Kreiraj Git repozitorij", "session.review.noVcs.createGit.action": "Kreiraj Git repozitorij",
"session.todo.progress": "{{done}} od {{total}} zadataka završeno",
"session.question.progress": "{{current}} od {{total}} pitanja", "session.question.progress": "{{current}} od {{total}} pitanja",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "File Explorer", "session.header.open.fileExplorer": "File Explorer",
+156 -8
View File
@@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Skift sidebjælke", "command.sidebar.toggle": "Skift sidebjælke",
"command.project.open": "Åbn projekt", "command.project.open": "Åbn projekt",
"command.project.previous": "Forrige projekt",
"command.project.next": "Næste projekt",
"command.project.index": "Skift til projekt {{index}}",
"command.provider.connect": "Tilslut udbyder", "command.provider.connect": "Tilslut udbyder",
"command.server.switch": "Skift server", "command.server.switch": "Skift server",
"command.settings.open": "Åbn indstillinger", "command.settings.open": "Åbn indstillinger",
@@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Ny session", "command.session.new": "Ny session",
"command.file.open": "Åbn fil", "command.file.open": "Åbn fil",
"command.tab.close": "Luk fane", "command.tab.close": "Luk fane",
"command.tab.reopenClosed": "Åbn lukket fane igen",
"command.context.addSelection": "Tilføj markering til kontekst", "command.context.addSelection": "Tilføj markering til kontekst",
"command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil", "command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil",
"command.input.focus": "Fokuser inputfelt", "command.input.focus": "Fokuser inputfelt",
@@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Tillader: {{inputs}}", "model.tooltip.allows": "Tillader: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Input",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Udbyder",
"model.tooltip.reasoning": "Tænkning",
"model.tooltip.reasoning.allowed": "Tillader tænkning", "model.tooltip.reasoning.allowed": "Tillader tænkning",
"model.tooltip.reasoning.none": "Ingen tænkning", "model.tooltip.reasoning.none": "Ingen tænkning",
"model.tooltip.context": "Kontekstgrænse {{limit}}", "model.tooltip.context": "Kontekstgrænse {{limit}}",
@@ -217,6 +226,7 @@ export const dict = {
"common.loading": "Indlæser", "common.loading": "Indlæser",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Annuller", "common.cancel": "Annuller",
"common.clear": "Ryd",
"common.connect": "Forbind", "common.connect": "Forbind",
"common.disconnect": "Frakobl", "common.disconnect": "Frakobl",
"common.continue": "Indsend", "common.continue": "Indsend",
@@ -234,6 +244,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc for at afslutte", "prompt.mode.shell.exit": "esc for at afslutte",
"session.child.promptDisabled": "Der kan ikke sendes prompts til underagentsessioner.",
"session.child.backToParent": "Tilbage til hovedsessionen.",
"prompt.example.1": "Ret en TODO i koden", "prompt.example.1": "Ret en TODO i koden",
"prompt.example.2": "Hvad er teknologistakken for dette projekt?", "prompt.example.2": "Hvad er teknologistakken for dette projekt?",
@@ -273,6 +285,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst", "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
"prompt.context.removeFile": "Fjern fil fra kontekst", "prompt.context.removeFile": "Fjern fil fra kontekst",
"prompt.action.attachFile": "Vedhæft fil", "prompt.action.attachFile": "Vedhæft fil",
"prompt.menu.addImagesAndFiles": "Tilføj filer og mere",
"prompt.menu.imagesAndFiles": "Billeder og filer",
"prompt.menu.commands": "Kommandoer",
"prompt.menu.context": "Kontekst",
"prompt.menu.shellCommand": "Shell-kommando",
"prompt.attachment.remove": "Fjern vedhæftning", "prompt.attachment.remove": "Fjern vedhæftning",
"prompt.action.send": "Send", "prompt.action.send": "Send",
"prompt.action.stop": "Stop", "prompt.action.stop": "Stop",
@@ -340,6 +357,75 @@ export const dict = {
"dialog.server.menu.delete": "Slet", "dialog.server.menu.delete": "Slet",
"dialog.server.current": "Nuværende server", "dialog.server.current": "Nuværende server",
"dialog.server.status.default": "Standard", "dialog.server.status.default": "Standard",
"wsl.server.add": "Tilføj WSL-server",
"wsl.server.addShort": "Tilføj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-server",
"wsl.server.retryStart": "Prøv at starte igen",
"wsl.server.updating": "Opdaterer...",
"wsl.onboarding.step.distro": "Vælg distribution",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Tjekker WSL...",
"wsl.onboarding.restartRequired": "Windows skal genstartes for at fuldføre installationen af WSL.",
"wsl.onboarding.ready": "WSL er klar.",
"wsl.onboarding.required": "WSL er påkrævet for at fortsætte.",
"wsl.onboarding.checkingDistros": "Tjekker distributioner...",
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
"wsl.onboarding.checkingDistro": "Tjekker {{distro}}...",
"wsl.onboarding.listingDistros": "Henter liste over distributioner...",
"wsl.onboarding.distroReady": "{{distro}} er klar.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installeret endnu.",
"wsl.onboarding.openDistroOnce": "Åbn {{distro}} én gang for at fuldføre opsætningen.",
"wsl.onboarding.finishingDistro": "Fuldfører opsætningen af {{distro}}.",
"wsl.onboarding.pickDistro": "Vælg en distribution, eller installer en nedenfor.",
"wsl.onboarding.checkingOpencode": "Tjekker OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}...",
"wsl.onboarding.updatingOpencode": "Opdaterer OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Opdater OpenCode i {{distro}}.",
"wsl.onboarding.updateOpencode": "Opdater OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Vælg først en distribution.",
"wsl.onboarding.loadFailed": "Kunne ikke indlæse WSL-status.",
"wsl.onboarding.loading": "Indlæser...",
"wsl.onboarding.installedDistros": "Installerede distributioner",
"wsl.onboarding.checkAgain": "Tjek igen",
"wsl.onboarding.distroStatus.ready": "Klar",
"wsl.onboarding.distroStatus.checking": "Tjekker...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installeret",
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ikke understøttet · Brug WSL 2",
"wsl.onboarding.needAnotherDistro": "Har du brug for en anden distribution?",
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribution fra WSL-kataloget",
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installeret",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) er påkrævet, før OpenCode kan tilføje en WSL-server",
"wsl.onboarding.wslUnavailable.title": "WSL er ikke tilgængelig",
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekræfte WSL på denne maskine.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Genstart Windows for at fuldføre installationen af WSL, og åbn derefter OpenCode igen.",
"wsl.onboarding.next": "Næste",
"wsl.onboarding.refresh": "Opdater",
"wsl.onboarding.allDistrosAdded": "Alle installerede distributioner er allerede tilføjet.",
"wsl.onboarding.noDistros": "Ingen distributioner fundet endnu.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installerer...",
"wsl.onboarding.installDistro": "Installer distribution",
"wsl.onboarding.searchDistros": "Søg efter distributioner",
"wsl.onboarding.wsl2Required": "WSL 2 er påkrævet.",
"wsl.onboarding.toolsRequired": "Denne distribution kræver bash og curl.",
"wsl.onboarding.openTerminal": "Åbn terminal",
"wsl.onboarding.path": "Sti: {{path}}",
"wsl.onboarding.notFound": "ikke fundet",
"wsl.onboarding.version": "Version: {{version}}",
"wsl.onboarding.unknown": "ukendt",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "Den installerede version matcher ikke desktopappens version.",
"wsl.onboarding.adding": "Tilføjer...",
"dialog.project.edit.title": "Rediger projekt", "dialog.project.edit.title": "Rediger projekt",
"dialog.project.edit.name": "Navn", "dialog.project.edit.name": "Navn",
@@ -403,6 +489,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -450,10 +537,12 @@ export const dict = {
"error.page.title": "Noget gik galt", "error.page.title": "Noget gik galt",
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.", "error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
"error.page.description.localServerStartup": "Der opstod en fejl under start af den lokale server.",
"error.page.details.label": "Fejldetaljer", "error.page.details.label": "Fejldetaljer",
"error.page.action.restart": "Genstart", "error.page.action.restart": "Genstart",
"error.page.action.report": "Rapportér fejl", "error.page.action.report": "Rapportér fejl",
"error.page.action.reported": "Fejl rapporteret", "error.page.action.reported": "Fejl rapporteret",
"error.page.action.exportLogs": "Eksportér logfiler",
"error.page.action.checking": "Tjekker...", "error.page.action.checking": "Tjekker...",
"error.page.action.checkUpdates": "Tjek for opdateringer", "error.page.action.checkUpdates": "Tjek for opdateringer",
"error.page.action.updateTo": "Opdater til {{version}}", "error.page.action.updateTo": "Opdater til {{version}}",
@@ -501,11 +590,33 @@ export const dict = {
"home.recentProjects": "Seneste projekter", "home.recentProjects": "Seneste projekter",
"home.empty.title": "Ingen seneste projekter", "home.empty.title": "Ingen seneste projekter",
"home.empty.description": "Kom i gang ved at åbne et lokalt projekt", "home.empty.description": "Kom i gang ved at åbne et lokalt projekt",
"home.title": "Hjem",
"home.projects": "Projekter",
"home.project.add": "Tilføj projekt",
"home.recentlyClosed": "Lukket for nylig",
"home.server.collapse": "Skjul serverprojekter",
"home.server.expand": "Udvid serverprojekter",
"home.sessions.search.placeholder": "Søg i sessioner",
"home.sessions.search.placeholder.scoped": "Søg i sessioner i {{scope}}",
"home.sessions.search.sessions": "Sessioner",
"home.sessions.search.noResults": "Ingen sessioner fundet for {{query}}",
"home.sessions.empty": "Intet her endnu",
"home.sessions.empty.description": "Opret en session for at komme i gang",
"home.sessions.group.today": "I dag",
"home.sessions.group.yesterday": "I går",
"home.sessions.group.older": "Ældre",
"home.providerTip":
"Opret forbindelse til mere end 75 udbydere for at bruge andre modeller, herunder Claude, GPT, Gemini og flere",
"session.tab.session": "Session", "session.tab.session": "Session",
"session.tab.review": "Gennemgang", "session.tab.review": "Gennemgang",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Ukendt session",
"session.panel.reviewAndFiles": "Gennemgang og filer", "session.panel.reviewAndFiles": "Gennemgang og filer",
"session.error.notFound": "Denne session kan ikke findes",
"session.error.notFound.description": "Denne fane henviser til en session, der ikke længere findes på denne server.",
"session.error.notFound.closeTab": "Luk fane",
"session.error.serverConnection": "Kan ikke oprette forbindelse til denne server",
"session.review.filesChanged": "{{count}} Filer ændret", "session.review.filesChanged": "{{count}} Filer ændret",
"session.review.change.one": "Ændring", "session.review.change.one": "Ændring",
"session.review.change.other": "Ændringer", "session.review.change.other": "Ændringer",
@@ -515,6 +626,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige", "Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
"session.review.noChanges": "Ingen ændringer", "session.review.noChanges": "Ingen ændringer",
"session.review.noUncommittedChanges": "Ingen ændringer uden commit endnu",
"session.review.noBranchChanges": "Ingen grenændringer endnu",
"session.files.selectToOpen": "Vælg en fil at åbne", "session.files.selectToOpen": "Vælg en fil at åbne",
"session.files.all": "Alle filer", "session.files.all": "Alle filer",
"session.files.empty": "Ingen filer", "session.files.empty": "Ingen filer",
@@ -526,9 +639,10 @@ export const dict = {
"session.messages.jumpToLatest": "Gå til seneste", "session.messages.jumpToLatest": "Gå til seneste",
"session.context.addToContext": "Tilføj {{selection}} til kontekst", "session.context.addToContext": "Tilføj {{selection}} til kontekst",
"session.todo.title": "Opgaver", "session.question.minimize": "Minimer spørgsmål",
"session.todo.collapse": "Skjul", "session.question.restore": "Gendan spørgsmål",
"session.todo.expand": "Udvid", "session.question.pending.one": "{{count}} afventende spørgsmål",
"session.question.pending.other": "{{count}} afventende spørgsmål",
"session.followupDock.summary.one": "{{count}} besked i kø", "session.followupDock.summary.one": "{{count}} besked i kø",
"session.followupDock.summary.other": "{{count}} beskeder i kø", "session.followupDock.summary.other": "{{count}} beskeder i kø",
"session.followupDock.sendNow": "Send nu", "session.followupDock.sendNow": "Send nu",
@@ -542,9 +656,16 @@ export const dict = {
"session.revertDock.restore": "Gendan besked", "session.revertDock.restore": "Gendan besked",
"session.new.title": "Byg hvad som helst", "session.new.title": "Byg hvad som helst",
"session.new.project.new": "Nyt projekt",
"session.new.project.search": "Søg efter projekter",
"session.new.project.add": "Tilføj projekt",
"session.new.worktree.main": "Hovedgren", "session.new.worktree.main": "Hovedgren",
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})", "session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
"session.new.worktree.create": "Opret nyt worktree", "session.new.worktree.create": "Opret nyt worktree",
"session.new.workspace.runIn": "Kør session i",
"session.new.workspace.triggerLocal": "Lokal",
"session.new.workspace.local": "Lokalt repository",
"session.new.workspace.existing": "Arbejdsområde…",
"session.new.lastModified": "Sidst ændret", "session.new.lastModified": "Sidst ændret",
"session.header.search.placeholder": "Søg {{project}}", "session.header.search.placeholder": "Søg {{project}}",
@@ -614,6 +735,8 @@ export const dict = {
"sidebar.project.recentSessions": "Seneste sessioner", "sidebar.project.recentSessions": "Seneste sessioner",
"sidebar.project.viewAllSessions": "Vis alle sessioner", "sidebar.project.viewAllSessions": "Vis alle sessioner",
"sidebar.project.clearNotifications": "Ryd notifikationer", "sidebar.project.clearNotifications": "Ryd notifikationer",
"sidebar.empty.title": "Ingen åbne projekter",
"sidebar.empty.description": "Åbn et projekt for at komme i gang",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
@@ -625,6 +748,7 @@ export const dict = {
"settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.", "settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.",
"settings.general.section.appearance": "Udseende", "settings.general.section.appearance": "Udseende",
"settings.general.section.advanced": "Avanceret",
"settings.general.section.notifications": "Systemmeddelelser", "settings.general.section.notifications": "Systemmeddelelser",
"settings.general.section.updates": "Opdateringer", "settings.general.section.updates": "Opdateringer",
"settings.general.section.sounds": "Lydeffekter", "settings.general.section.sounds": "Lydeffekter",
@@ -633,6 +757,11 @@ export const dict = {
"settings.general.row.language.title": "Sprog", "settings.general.row.language.title": "Sprog",
"settings.general.row.language.description": "Ændr visningssproget for OpenCode", "settings.general.row.language.description": "Ændr visningssproget for OpenCode",
"settings.general.row.shell.title": "Terminalshell",
"settings.general.row.shell.description":
"Vælg den shell, der bruges i din terminal. Kompatible shells bruges også til agentens værktøjskald.",
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
"settings.general.row.shell.terminalOnly": "kun terminal",
"settings.general.row.appearance.title": "Udseende", "settings.general.row.appearance.title": "Udseende",
"settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed", "settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
"settings.general.row.colorScheme.title": "Farveskema", "settings.general.row.colorScheme.title": "Farveskema",
@@ -641,14 +770,29 @@ export const dict = {
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.", "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
"settings.general.row.font.title": "Kode-skrifttype", "settings.general.row.font.title": "Kode-skrifttype",
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke", "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalskrifttype",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen",
"settings.general.row.uiFont.title": "UI-skrifttype", "settings.general.row.uiFont.title": "UI-skrifttype",
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen", "settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
"settings.general.row.followup.title": "Opfølgningsadfærd", "settings.general.row.followup.title": "Opfølgningsadfærd",
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø", "settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
"settings.general.row.followup.option.queue": "Kø", "settings.general.row.followup.option.queue": "Kø",
"settings.general.row.followup.option.steer": "Styr", "settings.general.row.followup.option.steer": "Styr",
"settings.general.row.showFileTree.title": "Filtræ",
"settings.general.row.showFileTree.description": "Vis filtræspanelet i sessioner",
"settings.general.row.showNavigation.title": "Navigationsknapper",
"settings.general.row.showNavigation.description": "Vis tilbage- og frem-knapperne i desktopappens titellinje",
"settings.general.row.showSearch.title": "Kommandopalette",
"settings.general.row.showSearch.description": "Vis knappen til søgning og kommandopaletten i titellinjen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation nederst",
"settings.general.row.mobileTitlebarBottom.description":
"Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder",
"settings.general.row.showCustomAgents.title": "Brugerdefinerede agenter",
"settings.general.row.showCustomAgents.description": "Vis agentvælgeren i promptfeltet",
"settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter", "settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter",
"settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen", "settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen",
@@ -657,6 +801,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele", "settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen", "Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
"settings.general.row.newLayoutDesigns.title": "Nyt layout og design",
"settings.general.row.newLayoutDesigns.description":
"Aktivér det nydesignede layout, startsiden, promptfeltet og sessionsbrugerfladen",
"settings.general.row.pinchZoom.title": "Knib for at zoome",
"settings.general.row.pinchZoom.description": "Tillad knibebevægelser på pegefeltet og Ctrl-rulning for at zoome",
"settings.general.row.wayland.title": "Brug native Wayland", "settings.general.row.wayland.title": "Brug native Wayland",
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.", "settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -671,6 +820,8 @@ export const dict = {
"settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig", "settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig",
"settings.updates.action.checkNow": "Tjek nu", "settings.updates.action.checkNow": "Tjek nu",
"settings.updates.action.checking": "Tjekker...", "settings.updates.action.checking": "Tjekker...",
"settings.updates.action.downloading": "Downloader...",
"settings.updates.action.installing": "Installerer...",
"settings.updates.toast.latest.title": "Du er opdateret", "settings.updates.toast.latest.title": "Du er opdateret",
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.", "settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
@@ -799,8 +950,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Indlæs en færdighed efter navn", "settings.permissions.tool.skill.description": "Indlæs en færdighed efter navn",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Kør sprogserverforespørgsler", "settings.permissions.tool.lsp.description": "Kør sprogserverforespørgsler",
"settings.permissions.tool.todowrite.title": "Skriv To-do",
"settings.permissions.tool.todowrite.description": "Opdater to-do listen",
"settings.permissions.tool.webfetch.title": "Webhentning", "settings.permissions.tool.webfetch.title": "Webhentning",
"settings.permissions.tool.webfetch.description": "Hent indhold fra en URL", "settings.permissions.tool.webfetch.description": "Hent indhold fra en URL",
"settings.permissions.tool.websearch.title": "Websøgning", "settings.permissions.tool.websearch.title": "Websøgning",
@@ -865,7 +1014,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt", "session.review.noVcs.createGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt",
"session.review.noVcs.createGit.actionLoading": "Opretter Git-repository...", "session.review.noVcs.createGit.actionLoading": "Opretter Git-repository...",
"session.review.noVcs.createGit.action": "Opret Git-repository", "session.review.noVcs.createGit.action": "Opret Git-repository",
"session.todo.progress": "{{done}} af {{total}} opgaver fuldført",
"session.question.progress": "{{current}} af {{total}} spørgsmål", "session.question.progress": "{{current}} af {{total}} spørgsmål",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Stifinder", "session.header.open.fileExplorer": "Stifinder",
+159 -8
View File
@@ -25,6 +25,9 @@ export const dict = {
"theme.scheme.dark": "Dunkel", "theme.scheme.dark": "Dunkel",
"command.sidebar.toggle": "Seitenleiste umschalten", "command.sidebar.toggle": "Seitenleiste umschalten",
"command.project.open": "Projekt öffnen", "command.project.open": "Projekt öffnen",
"command.project.previous": "Vorheriges Projekt",
"command.project.next": "Nächstes Projekt",
"command.project.index": "Zu Projekt {{index}} wechseln",
"command.provider.connect": "Anbieter verbinden", "command.provider.connect": "Anbieter verbinden",
"command.server.switch": "Server wechseln", "command.server.switch": "Server wechseln",
"command.settings.open": "Einstellungen öffnen", "command.settings.open": "Einstellungen öffnen",
@@ -43,6 +46,7 @@ export const dict = {
"command.session.new": "Neue Sitzung", "command.session.new": "Neue Sitzung",
"command.file.open": "Datei öffnen", "command.file.open": "Datei öffnen",
"command.tab.close": "Tab schließen", "command.tab.close": "Tab schließen",
"command.tab.reopenClosed": "Geschlossenen Tab wieder öffnen",
"command.context.addSelection": "Auswahl zum Kontext hinzufügen", "command.context.addSelection": "Auswahl zum Kontext hinzufügen",
"command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen", "command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen",
"command.input.focus": "Eingabefeld fokussieren", "command.input.focus": "Eingabefeld fokussieren",
@@ -198,6 +202,11 @@ export const dict = {
"model.input.video": "Video", "model.input.video": "Video",
"model.input.pdf": "PDF", "model.input.pdf": "PDF",
"model.tooltip.allows": "Erlaubt: {{inputs}}", "model.tooltip.allows": "Erlaubt: {{inputs}}",
"model.tooltip.context.label": "Kontext",
"model.tooltip.inputs": "Eingaben",
"model.tooltip.model": "Modell",
"model.tooltip.provider": "Anbieter",
"model.tooltip.reasoning": "Reasoning",
"model.tooltip.reasoning.allowed": "Erlaubt Reasoning", "model.tooltip.reasoning.allowed": "Erlaubt Reasoning",
"model.tooltip.reasoning.none": "Kein Reasoning", "model.tooltip.reasoning.none": "Kein Reasoning",
"model.tooltip.context": "Kontextlimit {{limit}}", "model.tooltip.context": "Kontextlimit {{limit}}",
@@ -207,6 +216,7 @@ export const dict = {
"common.loading": "Laden", "common.loading": "Laden",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Abbrechen", "common.cancel": "Abbrechen",
"common.clear": "Leeren",
"common.connect": "Verbinden", "common.connect": "Verbinden",
"common.disconnect": "Trennen", "common.disconnect": "Trennen",
"common.continue": "Absenden", "common.continue": "Absenden",
@@ -223,6 +233,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc zum Verlassen", "prompt.mode.shell.exit": "esc zum Verlassen",
"session.child.promptDisabled": "Unteragenten-Sitzungen können keine Prompts erhalten.",
"session.child.backToParent": "Zurück zur Hauptsitzung.",
"prompt.example.1": "Ein TODO in der Codebasis beheben", "prompt.example.1": "Ein TODO in der Codebasis beheben",
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?", "prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
"prompt.example.3": "Fehlerhafte Tests beheben", "prompt.example.3": "Fehlerhafte Tests beheben",
@@ -260,6 +272,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen", "prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
"prompt.context.removeFile": "Datei aus dem Kontext entfernen", "prompt.context.removeFile": "Datei aus dem Kontext entfernen",
"prompt.action.attachFile": "Datei anhängen", "prompt.action.attachFile": "Datei anhängen",
"prompt.menu.addImagesAndFiles": "Dateien und mehr hinzufügen",
"prompt.menu.imagesAndFiles": "Bilder und Dateien",
"prompt.menu.commands": "Befehle",
"prompt.menu.context": "Kontext",
"prompt.menu.shellCommand": "Shell-Befehl",
"prompt.attachment.remove": "Anhang entfernen", "prompt.attachment.remove": "Anhang entfernen",
"prompt.action.send": "Senden", "prompt.action.send": "Senden",
"prompt.action.stop": "Stopp", "prompt.action.stop": "Stopp",
@@ -320,6 +337,75 @@ export const dict = {
"dialog.server.menu.delete": "Löschen", "dialog.server.menu.delete": "Löschen",
"dialog.server.current": "Aktueller Server", "dialog.server.current": "Aktueller Server",
"dialog.server.status.default": "Standard", "dialog.server.status.default": "Standard",
"wsl.server.add": "WSL-Server hinzufügen",
"wsl.server.addShort": "WSL hinzufügen",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-Server",
"wsl.server.retryStart": "Start erneut versuchen",
"wsl.server.updating": "Wird aktualisiert...",
"wsl.onboarding.step.distro": "Distribution auswählen",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL wird geprüft...",
"wsl.onboarding.restartRequired": "Windows muss neu gestartet werden, um die WSL-Installation abzuschließen.",
"wsl.onboarding.ready": "WSL ist bereit.",
"wsl.onboarding.required": "WSL ist erforderlich, um fortzufahren.",
"wsl.onboarding.checkingDistros": "Distributionen werden geprüft...",
"wsl.onboarding.installingDistro": "{{distro}} wird installiert...",
"wsl.onboarding.checkingDistro": "{{distro}} wird geprüft...",
"wsl.onboarding.listingDistros": "Distributionen werden aufgelistet...",
"wsl.onboarding.distroReady": "{{distro}} ist bereit.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ist noch nicht installiert.",
"wsl.onboarding.openDistroOnce": "Öffnen Sie {{distro}} einmal, um die Einrichtung abzuschließen.",
"wsl.onboarding.finishingDistro": "Einrichtung von {{distro}} wird abgeschlossen.",
"wsl.onboarding.pickDistro": "Wählen Sie eine Distribution aus oder installieren Sie unten eine.",
"wsl.onboarding.checkingOpencode": "OpenCode wird geprüft...",
"wsl.onboarding.checkingOpencodeIn": "OpenCode in {{distro}} wird geprüft...",
"wsl.onboarding.updatingOpencode": "OpenCode wird aktualisiert...",
"wsl.onboarding.updatingOpencodeIn": "OpenCode in {{distro}} wird aktualisiert...",
"wsl.onboarding.updateOpencodeIn": "Aktualisieren Sie OpenCode in {{distro}}.",
"wsl.onboarding.updateOpencode": "OpenCode aktualisieren",
"wsl.onboarding.opencodeReadyIn": "OpenCode ist in {{distro}} bereit.",
"wsl.onboarding.opencodeReady": "OpenCode ist bereit.",
"wsl.onboarding.installOpencodeIn": "Installieren Sie OpenCode in {{distro}}.",
"wsl.onboarding.installOpencode": "OpenCode installieren",
"wsl.onboarding.chooseDistroFirst": "Wählen Sie zuerst eine Distribution aus.",
"wsl.onboarding.loadFailed": "WSL-Status konnte nicht geladen werden.",
"wsl.onboarding.loading": "Wird geladen...",
"wsl.onboarding.installedDistros": "Installierte Distributionen",
"wsl.onboarding.checkAgain": "Erneut prüfen",
"wsl.onboarding.distroStatus.ready": "Bereit",
"wsl.onboarding.distroStatus.checking": "Wird geprüft...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nicht installiert",
"wsl.onboarding.distroStatus.missingTools": "Fehlend: bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Nicht unterstützt · WSL 2 verwenden",
"wsl.onboarding.needAnotherDistro": "Andere Distribution benötigt?",
"wsl.onboarding.needAnotherDistroHint": "Installieren Sie eine Linux-Distribution aus dem WSL-Katalog",
"wsl.onboarding.wslNotInstalled.title": "WSL nicht installiert",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) ist erforderlich, bevor OpenCode einen WSL-Server hinzufügen kann",
"wsl.onboarding.wslUnavailable.title": "WSL nicht verfügbar",
"wsl.onboarding.wslUnavailable.description": "OpenCode konnte WSL auf diesem Computer nicht überprüfen.",
"wsl.onboarding.installWsl": "WSL installieren",
"wsl.onboarding.windowsRestartRequired":
"Starten Sie Windows neu, um die WSL-Installation abzuschließen, und öffnen Sie dann OpenCode erneut.",
"wsl.onboarding.next": "Weiter",
"wsl.onboarding.refresh": "Aktualisieren",
"wsl.onboarding.allDistrosAdded": "Alle installierten Distributionen wurden bereits hinzugefügt.",
"wsl.onboarding.noDistros": "Noch keine Distributionen erkannt.",
"wsl.onboarding.install": "Installieren",
"wsl.onboarding.installing": "Wird installiert...",
"wsl.onboarding.installDistro": "Distribution installieren",
"wsl.onboarding.searchDistros": "Distributionen durchsuchen",
"wsl.onboarding.wsl2Required": "WSL 2 ist erforderlich.",
"wsl.onboarding.toolsRequired": "Diese Distribution benötigt bash und curl.",
"wsl.onboarding.openTerminal": "Terminal öffnen",
"wsl.onboarding.path": "Pfad: {{path}}",
"wsl.onboarding.notFound": "nicht gefunden",
"wsl.onboarding.version": "Version: {{version}}",
"wsl.onboarding.unknown": "unbekannt",
"wsl.onboarding.desktopVersion": "Desktop {{version}}",
"wsl.onboarding.versionMismatch": "Die installierte Version entspricht nicht der Version der Desktop-App.",
"wsl.onboarding.adding": "Wird hinzugefügt...",
"dialog.project.edit.title": "Projekt bearbeiten", "dialog.project.edit.title": "Projekt bearbeiten",
"dialog.project.edit.name": "Name", "dialog.project.edit.name": "Name",
"dialog.project.edit.icon": "Icon", "dialog.project.edit.icon": "Icon",
@@ -378,6 +464,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Sprache", "toast.language.title": "Sprache",
@@ -414,10 +501,12 @@ export const dict = {
"toast.update.action.notYet": "Noch nicht", "toast.update.action.notYet": "Noch nicht",
"error.page.title": "Etwas ist schiefgelaufen", "error.page.title": "Etwas ist schiefgelaufen",
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.", "error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
"error.page.description.localServerStartup": "Beim Starten des lokalen Servers ist ein Fehler aufgetreten.",
"error.page.details.label": "Fehlerdetails", "error.page.details.label": "Fehlerdetails",
"error.page.action.restart": "Neustart", "error.page.action.restart": "Neustart",
"error.page.action.report": "Fehler melden", "error.page.action.report": "Fehler melden",
"error.page.action.reported": "Fehler gemeldet", "error.page.action.reported": "Fehler gemeldet",
"error.page.action.exportLogs": "Protokolle exportieren",
"error.page.action.checking": "Prüfen...", "error.page.action.checking": "Prüfen...",
"error.page.action.checkUpdates": "Nach Updates suchen", "error.page.action.checkUpdates": "Nach Updates suchen",
"error.page.action.updateTo": "Auf {{version}} aktualisieren", "error.page.action.updateTo": "Auf {{version}} aktualisieren",
@@ -461,10 +550,33 @@ export const dict = {
"home.recentProjects": "Letzte Projekte", "home.recentProjects": "Letzte Projekte",
"home.empty.title": "Keine letzten Projekte", "home.empty.title": "Keine letzten Projekte",
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen", "home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
"home.title": "Startseite",
"home.projects": "Projekte",
"home.project.add": "Projekt hinzufügen",
"home.recentlyClosed": "Kürzlich geschlossen",
"home.server.collapse": "Serverprojekte einklappen",
"home.server.expand": "Serverprojekte ausklappen",
"home.sessions.search.placeholder": "Sitzungen durchsuchen",
"home.sessions.search.placeholder.scoped": "Sitzungen in {{scope}} durchsuchen",
"home.sessions.search.sessions": "Sitzungen",
"home.sessions.search.noResults": "Keine Sitzungen für {{query}} gefunden",
"home.sessions.empty": "Hier ist noch nichts",
"home.sessions.empty.description": "Erstellen Sie eine Sitzung, um loszulegen",
"home.sessions.group.today": "Heute",
"home.sessions.group.yesterday": "Gestern",
"home.sessions.group.older": "Älter",
"home.providerTip":
"Verbinde dich mit über 75 Anbietern, um weitere Modelle wie Claude, GPT, Gemini und andere zu nutzen",
"session.tab.session": "Sitzung", "session.tab.session": "Sitzung",
"session.tab.review": "Überprüfung", "session.tab.review": "Überprüfung",
"session.tab.context": "Kontext", "session.tab.context": "Kontext",
"session.tab.unknown": "Unbekannte Sitzung",
"session.panel.reviewAndFiles": "Überprüfung und Dateien", "session.panel.reviewAndFiles": "Überprüfung und Dateien",
"session.error.notFound": "Diese Sitzung wurde nicht gefunden",
"session.error.notFound.description":
"Dieser Tab verweist auf eine Sitzung, die auf diesem Server nicht mehr existiert.",
"session.error.notFound.closeTab": "Tab schließen",
"session.error.serverConnection": "Verbindung zu diesem Server nicht möglich",
"session.review.filesChanged": "{{count}} Dateien geändert", "session.review.filesChanged": "{{count}} Dateien geändert",
"session.review.change.one": "Änderung", "session.review.change.one": "Änderung",
"session.review.change.other": "Änderungen", "session.review.change.other": "Änderungen",
@@ -474,6 +586,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar", "Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
"session.review.noChanges": "Keine Änderungen", "session.review.noChanges": "Keine Änderungen",
"session.review.noUncommittedChanges": "Noch keine nicht committeten Änderungen",
"session.review.noBranchChanges": "Noch keine Branch-Änderungen",
"session.files.selectToOpen": "Datei zum Öffnen auswählen", "session.files.selectToOpen": "Datei zum Öffnen auswählen",
"session.files.all": "Alle Dateien", "session.files.all": "Alle Dateien",
"session.files.empty": "Keine Dateien", "session.files.empty": "Keine Dateien",
@@ -484,9 +598,10 @@ export const dict = {
"session.messages.loading": "Lade Nachrichten...", "session.messages.loading": "Lade Nachrichten...",
"session.messages.jumpToLatest": "Zum neuesten springen", "session.messages.jumpToLatest": "Zum neuesten springen",
"session.context.addToContext": "{{selection}} zum Kontext hinzufügen", "session.context.addToContext": "{{selection}} zum Kontext hinzufügen",
"session.todo.title": "Aufgaben", "session.question.minimize": "Frage minimieren",
"session.todo.collapse": "Einklappen", "session.question.restore": "Frage wiederherstellen",
"session.todo.expand": "Ausklappen", "session.question.pending.one": "{{count}} ausstehende Frage",
"session.question.pending.other": "{{count}} ausstehende Fragen",
"session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange", "session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange",
"session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange", "session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange",
"session.followupDock.sendNow": "Jetzt senden", "session.followupDock.sendNow": "Jetzt senden",
@@ -499,9 +614,16 @@ export const dict = {
"session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen", "session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
"session.revertDock.restore": "Nachricht wiederherstellen", "session.revertDock.restore": "Nachricht wiederherstellen",
"session.new.title": "Baue, was du willst", "session.new.title": "Baue, was du willst",
"session.new.project.new": "Neues Projekt",
"session.new.project.search": "Projekte durchsuchen",
"session.new.project.add": "Projekt hinzufügen",
"session.new.worktree.main": "Haupt-Branch", "session.new.worktree.main": "Haupt-Branch",
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})", "session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
"session.new.worktree.create": "Neuen Worktree erstellen", "session.new.worktree.create": "Neuen Worktree erstellen",
"session.new.workspace.runIn": "Sitzung ausführen in",
"session.new.workspace.triggerLocal": "Lokal",
"session.new.workspace.local": "Lokales Repository",
"session.new.workspace.existing": "Arbeitsbereich…",
"session.new.lastModified": "Zuletzt geändert", "session.new.lastModified": "Zuletzt geändert",
"session.header.search.placeholder": "{{project}} durchsuchen", "session.header.search.placeholder": "{{project}} durchsuchen",
"session.header.searchFiles": "Dateien suchen", "session.header.searchFiles": "Dateien suchen",
@@ -566,6 +688,8 @@ export const dict = {
"sidebar.project.recentSessions": "Letzte Sitzungen", "sidebar.project.recentSessions": "Letzte Sitzungen",
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen", "sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
"sidebar.project.clearNotifications": "Benachrichtigungen löschen", "sidebar.project.clearNotifications": "Benachrichtigungen löschen",
"sidebar.empty.title": "Keine Projekte geöffnet",
"sidebar.empty.description": "Öffnen Sie ein Projekt, um loszulegen",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Desktop", "settings.section.desktop": "Desktop",
"settings.section.server": "Server", "settings.section.server": "Server",
@@ -575,6 +699,7 @@ export const dict = {
"settings.desktop.wsl.title": "WSL-Integration", "settings.desktop.wsl.title": "WSL-Integration",
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.", "settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
"settings.general.section.appearance": "Erscheinungsbild", "settings.general.section.appearance": "Erscheinungsbild",
"settings.general.section.advanced": "Erweitert",
"settings.general.section.notifications": "Systembenachrichtigungen", "settings.general.section.notifications": "Systembenachrichtigungen",
"settings.general.section.updates": "Updates", "settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Soundeffekte", "settings.general.section.sounds": "Soundeffekte",
@@ -582,6 +707,11 @@ export const dict = {
"settings.general.section.display": "Anzeige", "settings.general.section.display": "Anzeige",
"settings.general.row.language.title": "Sprache", "settings.general.row.language.title": "Sprache",
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern", "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
"settings.general.row.shell.title": "Terminal-Shell",
"settings.general.row.shell.description":
"Wählen Sie die Shell für Ihr Terminal aus. Kompatible Shells werden auch für Tool-Aufrufe von Agenten verwendet.",
"settings.general.row.shell.autoDefault": "Automatisch (Standard)",
"settings.general.row.shell.terminalOnly": "nur Terminal",
"settings.general.row.appearance.title": "Erscheinungsbild", "settings.general.row.appearance.title": "Erscheinungsbild",
"settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht", "settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
"settings.general.row.colorScheme.title": "Farbschema", "settings.general.row.colorScheme.title": "Farbschema",
@@ -591,8 +721,8 @@ export const dict = {
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.", "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
"settings.general.row.font.title": "Code-Schriftart", "settings.general.row.font.title": "Code-Schriftart",
"settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen", "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalschriftart",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an",
"settings.general.row.uiFont.title": "UI-Schriftart", "settings.general.row.uiFont.title": "UI-Schriftart",
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen", "settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
"settings.general.row.followup.title": "Verhalten bei Folgefragen", "settings.general.row.followup.title": "Verhalten bei Folgefragen",
@@ -600,6 +730,23 @@ export const dict = {
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten", "Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
"settings.general.row.followup.option.queue": "Warteschlange", "settings.general.row.followup.option.queue": "Warteschlange",
"settings.general.row.followup.option.steer": "Steuern", "settings.general.row.followup.option.steer": "Steuern",
"settings.general.row.showFileTree.title": "Dateibaum",
"settings.general.row.showFileTree.description": "Dateibaum in Sitzungen anzeigen",
"settings.general.row.showNavigation.title": "Navigationssteuerung",
"settings.general.row.showNavigation.description":
"Schaltflächen zum Vor- und Zurücknavigieren in der Desktop-Titelleiste anzeigen",
"settings.general.row.showSearch.title": "Befehlspalette",
"settings.general.row.showSearch.description":
"Schaltfläche für Suche und Befehlspalette in der Titelleiste anzeigen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen",
"settings.general.row.mobileTitlebarBottom.title": "Navigation unten",
"settings.general.row.mobileTitlebarBottom.description":
"Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren",
"settings.general.row.showCustomAgents.title": "Benutzerdefinierte Agenten",
"settings.general.row.showCustomAgents.description": "Agentenauswahl im Eingabebereich anzeigen",
"settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen", "settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen", "Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen",
@@ -609,6 +756,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen", "settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen", "Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
"settings.general.row.newLayoutDesigns.title": "Neues Layout und Design",
"settings.general.row.newLayoutDesigns.description":
"Neu gestaltetes Layout, Startseite, Eingabebereich und Sitzungsoberfläche aktivieren",
"settings.general.row.pinchZoom.title": "Zoom per Fingergeste",
"settings.general.row.pinchZoom.description": "Zoomen per Zwei-Finger- und Ctrl-Scroll-Geste erlauben",
"settings.general.row.wayland.title": "Natives Wayland verwenden", "settings.general.row.wayland.title": "Natives Wayland verwenden",
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.", "settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -621,6 +773,8 @@ export const dict = {
"settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar", "settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar",
"settings.updates.action.checkNow": "Jetzt prüfen", "settings.updates.action.checkNow": "Jetzt prüfen",
"settings.updates.action.checking": "Wird geprüft...", "settings.updates.action.checking": "Wird geprüft...",
"settings.updates.action.downloading": "Wird heruntergeladen...",
"settings.updates.action.installing": "Wird installiert...",
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand", "settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.", "settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
"sound.option.none": "Keine", "sound.option.none": "Keine",
@@ -742,8 +896,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Eine Fähigkeit nach Namen laden", "settings.permissions.tool.skill.description": "Eine Fähigkeit nach Namen laden",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Language-Server-Abfragen ausführen", "settings.permissions.tool.lsp.description": "Language-Server-Abfragen ausführen",
"settings.permissions.tool.todowrite.title": "Todo schreiben",
"settings.permissions.tool.todowrite.description": "Die Todo-Liste aktualisieren",
"settings.permissions.tool.webfetch.title": "Web-Abruf", "settings.permissions.tool.webfetch.title": "Web-Abruf",
"settings.permissions.tool.webfetch.description": "Inhalt von einer URL abrufen", "settings.permissions.tool.webfetch.description": "Inhalt von einer URL abrufen",
"settings.permissions.tool.websearch.title": "Web-Suche", "settings.permissions.tool.websearch.title": "Web-Suche",
@@ -807,7 +959,6 @@ export const dict = {
"Änderungen in diesem Projekt verfolgen, überprüfen und rückgängig machen", "Änderungen in diesem Projekt verfolgen, überprüfen und rückgängig machen",
"session.review.noVcs.createGit.actionLoading": "Git-Repository wird erstellt...", "session.review.noVcs.createGit.actionLoading": "Git-Repository wird erstellt...",
"session.review.noVcs.createGit.action": "Git-Repository erstellen", "session.review.noVcs.createGit.action": "Git-Repository erstellen",
"session.todo.progress": "{{done}} von {{total}} Aufgaben erledigt",
"session.question.progress": "{{current}} von {{total}} Fragen", "session.question.progress": "{{current}} von {{total}} Fragen",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Datei-Explorer", "session.header.open.fileExplorer": "Datei-Explorer",
+14 -6
View File
@@ -212,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Allows: {{inputs}}", "model.tooltip.allows": "Allows: {{inputs}}",
"model.tooltip.context.label": "Context",
"model.tooltip.inputs": "Inputs",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Provider",
"model.tooltip.reasoning": "Reasoning",
"model.tooltip.reasoning.allowed": "Allows reasoning", "model.tooltip.reasoning.allowed": "Allows reasoning",
"model.tooltip.reasoning.none": "No reasoning", "model.tooltip.reasoning.none": "No reasoning",
"model.tooltip.context": "Context limit {{limit}}", "model.tooltip.context": "Context limit {{limit}}",
@@ -282,6 +287,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Remove active file from context", "prompt.context.removeActiveFile": "Remove active file from context",
"prompt.context.removeFile": "Remove file from context", "prompt.context.removeFile": "Remove file from context",
"prompt.action.attachFile": "Add files", "prompt.action.attachFile": "Add files",
"prompt.menu.addImagesAndFiles": "Add files and more",
"prompt.menu.imagesAndFiles": "Images and files",
"prompt.menu.commands": "Commands",
"prompt.menu.context": "Context",
"prompt.menu.shellCommand": "Shell command",
"prompt.attachment.remove": "Remove attachment", "prompt.attachment.remove": "Remove attachment",
"prompt.action.send": "Send", "prompt.action.send": "Send",
"prompt.action.stop": "Stop", "prompt.action.stop": "Stop",
@@ -617,6 +627,7 @@ export const dict = {
"home.sessions.group.today": "Today", "home.sessions.group.today": "Today",
"home.sessions.group.yesterday": "Yesterday", "home.sessions.group.yesterday": "Yesterday",
"home.sessions.group.older": "Older", "home.sessions.group.older": "Older",
"home.providerTip": "Connect to 75+ providers to use other models, including Claude, GPT, Gemini, etc",
"session.tab.session": "Session", "session.tab.session": "Session",
"session.tab.review": "Review", "session.tab.review": "Review",
@@ -654,10 +665,6 @@ export const dict = {
"session.messages.jumpToLatest": "Jump to latest", "session.messages.jumpToLatest": "Jump to latest",
"session.context.addToContext": "Add {{selection}} to context", "session.context.addToContext": "Add {{selection}} to context",
"session.todo.title": "Todos",
"session.todo.collapse": "Collapse",
"session.todo.expand": "Expand",
"session.todo.progress": "{{done}} of {{total}} todos completed",
"session.question.progress": "{{current}} of {{total}} questions", "session.question.progress": "{{current}} of {{total}} questions",
"session.question.minimize": "Minimize question", "session.question.minimize": "Minimize question",
"session.question.restore": "Restore question", "session.question.restore": "Restore question",
@@ -698,6 +705,9 @@ export const dict = {
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "File Explorer", "session.header.open.fileExplorer": "File Explorer",
"session.header.open.fileManager": "File Manager", "session.header.open.fileManager": "File Manager",
"session.header.reveal.finder": "Reveal in Finder",
"session.header.reveal.fileExplorer": "Reveal in File Explorer",
"session.header.reveal.containingFolder": "Open containing folder",
"session.header.open.app.vscode": "VS Code", "session.header.open.app.vscode": "VS Code",
"session.header.open.app.cursor": "Cursor", "session.header.open.app.cursor": "Cursor",
"session.header.open.app.zed": "Zed", "session.header.open.app.zed": "Zed",
@@ -1035,8 +1045,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Load a skill by name", "settings.permissions.tool.skill.description": "Load a skill by name",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Run language server queries", "settings.permissions.tool.lsp.description": "Run language server queries",
"settings.permissions.tool.todowrite.title": "Todo Write",
"settings.permissions.tool.todowrite.description": "Update the todo list",
"settings.permissions.tool.webfetch.title": "Web Fetch", "settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Fetch content from a URL", "settings.permissions.tool.webfetch.description": "Fetch content from a URL",
"settings.permissions.tool.websearch.title": "Web Search", "settings.permissions.tool.websearch.title": "Web Search",
+159 -8
View File
@@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Alternar barra lateral", "command.sidebar.toggle": "Alternar barra lateral",
"command.project.open": "Abrir proyecto", "command.project.open": "Abrir proyecto",
"command.project.previous": "Proyecto anterior",
"command.project.next": "Siguiente proyecto",
"command.project.index": "Cambiar al proyecto {{index}}",
"command.provider.connect": "Conectar proveedor", "command.provider.connect": "Conectar proveedor",
"command.server.switch": "Cambiar servidor", "command.server.switch": "Cambiar servidor",
"command.settings.open": "Abrir ajustes", "command.settings.open": "Abrir ajustes",
@@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Nueva sesión", "command.session.new": "Nueva sesión",
"command.file.open": "Abrir archivo", "command.file.open": "Abrir archivo",
"command.tab.close": "Cerrar pestaña", "command.tab.close": "Cerrar pestaña",
"command.tab.reopenClosed": "Reabrir pestaña cerrada",
"command.context.addSelection": "Añadir selección al contexto", "command.context.addSelection": "Añadir selección al contexto",
"command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual", "command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual",
"command.input.focus": "Enfocar entrada", "command.input.focus": "Enfocar entrada",
@@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Permite: {{inputs}}", "model.tooltip.allows": "Permite: {{inputs}}",
"model.tooltip.context.label": "Contexto",
"model.tooltip.inputs": "Entradas",
"model.tooltip.model": "Modelo",
"model.tooltip.provider": "Proveedor",
"model.tooltip.reasoning": "Razonamiento",
"model.tooltip.reasoning.allowed": "Permite razonamiento", "model.tooltip.reasoning.allowed": "Permite razonamiento",
"model.tooltip.reasoning.none": "Sin razonamiento", "model.tooltip.reasoning.none": "Sin razonamiento",
"model.tooltip.context": "Límite de contexto {{limit}}", "model.tooltip.context": "Límite de contexto {{limit}}",
@@ -218,6 +227,7 @@ export const dict = {
"common.loading": "Cargando", "common.loading": "Cargando",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Cancelar", "common.cancel": "Cancelar",
"common.clear": "Limpiar",
"common.connect": "Conectar", "common.connect": "Conectar",
"common.disconnect": "Desconectar", "common.disconnect": "Desconectar",
"common.continue": "Enviar", "common.continue": "Enviar",
@@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc para salir", "prompt.mode.shell.exit": "esc para salir",
"session.child.promptDisabled": "No se pueden enviar prompts a las sesiones de subagentes.",
"session.child.backToParent": "Volver a la sesión principal.",
"prompt.example.1": "Arreglar un TODO en el código", "prompt.example.1": "Arreglar un TODO en el código",
"prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?", "prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?",
@@ -274,6 +286,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Eliminar archivo activo del contexto", "prompt.context.removeActiveFile": "Eliminar archivo activo del contexto",
"prompt.context.removeFile": "Eliminar archivo del contexto", "prompt.context.removeFile": "Eliminar archivo del contexto",
"prompt.action.attachFile": "Adjuntar archivo", "prompt.action.attachFile": "Adjuntar archivo",
"prompt.menu.addImagesAndFiles": "Añadir archivos y más",
"prompt.menu.imagesAndFiles": "Imágenes y archivos",
"prompt.menu.commands": "Comandos",
"prompt.menu.context": "Contexto",
"prompt.menu.shellCommand": "Comando de shell",
"prompt.attachment.remove": "Eliminar adjunto", "prompt.attachment.remove": "Eliminar adjunto",
"prompt.action.send": "Enviar", "prompt.action.send": "Enviar",
"prompt.action.stop": "Detener", "prompt.action.stop": "Detener",
@@ -341,6 +358,75 @@ export const dict = {
"dialog.server.menu.delete": "Eliminar", "dialog.server.menu.delete": "Eliminar",
"dialog.server.current": "Servidor actual", "dialog.server.current": "Servidor actual",
"dialog.server.status.default": "Predeterminado", "dialog.server.status.default": "Predeterminado",
"wsl.server.add": "Añadir servidor WSL",
"wsl.server.addShort": "Añadir WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Servidor WSL",
"wsl.server.retryStart": "Reintentar el inicio",
"wsl.server.updating": "Actualizando...",
"wsl.onboarding.step.distro": "Elegir distribución",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Comprobando WSL...",
"wsl.onboarding.restartRequired": "Es necesario reiniciar Windows para terminar de instalar WSL.",
"wsl.onboarding.ready": "WSL está listo.",
"wsl.onboarding.required": "Se necesita WSL para continuar.",
"wsl.onboarding.checkingDistros": "Comprobando distribuciones...",
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
"wsl.onboarding.checkingDistro": "Comprobando {{distro}}...",
"wsl.onboarding.listingDistros": "Listando distribuciones...",
"wsl.onboarding.distroReady": "La distribución {{distro}} está lista.",
"wsl.onboarding.distroNotInstalled": "La distribución {{distro}} aún no está instalada.",
"wsl.onboarding.openDistroOnce": "Abre {{distro}} una vez para completar la configuración.",
"wsl.onboarding.finishingDistro": "Completando la configuración de {{distro}}.",
"wsl.onboarding.pickDistro": "Elige una distribución o instala una abajo.",
"wsl.onboarding.checkingOpencode": "Comprobando OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}...",
"wsl.onboarding.updatingOpencode": "Actualizando OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Actualiza OpenCode en {{distro}}.",
"wsl.onboarding.updateOpencode": "Actualizar OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode está listo en {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode está listo.",
"wsl.onboarding.installOpencodeIn": "Instala OpenCode en {{distro}}.",
"wsl.onboarding.installOpencode": "Instalar OpenCode",
"wsl.onboarding.chooseDistroFirst": "Elige primero una distribución.",
"wsl.onboarding.loadFailed": "No se pudo cargar el estado de WSL.",
"wsl.onboarding.loading": "Cargando...",
"wsl.onboarding.installedDistros": "Distribuciones instaladas",
"wsl.onboarding.checkAgain": "Comprobar de nuevo",
"wsl.onboarding.distroStatus.ready": "Lista",
"wsl.onboarding.distroStatus.checking": "Comprobando...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no está instalado",
"wsl.onboarding.distroStatus.missingTools": "Faltan bash y curl",
"wsl.onboarding.distroStatus.unsupported": "No compatible · Usa WSL 2",
"wsl.onboarding.needAnotherDistro": "¿Necesitas otra distribución?",
"wsl.onboarding.needAnotherDistroHint": "Instala una distribución de Linux desde el catálogo de WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL no está instalado",
"wsl.onboarding.wslNotInstalled.description":
"Se necesita WSL (Subsistema de Windows para Linux) para que OpenCode pueda añadir un servidor WSL",
"wsl.onboarding.wslUnavailable.title": "WSL no disponible",
"wsl.onboarding.wslUnavailable.description": "OpenCode no pudo comprobar WSL en este equipo.",
"wsl.onboarding.installWsl": "Instalar WSL",
"wsl.onboarding.windowsRestartRequired":
"Reinicia Windows para terminar de instalar WSL y, después, vuelve a abrir OpenCode.",
"wsl.onboarding.next": "Siguiente",
"wsl.onboarding.refresh": "Actualizar",
"wsl.onboarding.allDistrosAdded": "Ya se han añadido todas las distribuciones instaladas.",
"wsl.onboarding.noDistros": "Aún no se han detectado distribuciones.",
"wsl.onboarding.install": "Instalar",
"wsl.onboarding.installing": "Instalando...",
"wsl.onboarding.installDistro": "Instalar distribución",
"wsl.onboarding.searchDistros": "Buscar distribuciones",
"wsl.onboarding.wsl2Required": "Se necesita WSL 2.",
"wsl.onboarding.toolsRequired": "Esta distribución necesita bash y curl.",
"wsl.onboarding.openTerminal": "Abrir terminal",
"wsl.onboarding.path": "Ruta: {{path}}",
"wsl.onboarding.notFound": "no encontrado",
"wsl.onboarding.version": "Versión: {{version}}",
"wsl.onboarding.unknown": "desconocida",
"wsl.onboarding.desktopVersion": "escritorio {{version}}",
"wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.",
"wsl.onboarding.adding": "Añadiendo...",
"dialog.project.edit.title": "Editar proyecto", "dialog.project.edit.title": "Editar proyecto",
"dialog.project.edit.name": "Nombre", "dialog.project.edit.name": "Nombre",
@@ -405,6 +491,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -453,10 +540,12 @@ export const dict = {
"error.page.title": "Algo salió mal", "error.page.title": "Algo salió mal",
"error.page.description": "Ocurrió un error al cargar la aplicación.", "error.page.description": "Ocurrió un error al cargar la aplicación.",
"error.page.description.localServerStartup": "Ocurrió un error al iniciar el servidor local.",
"error.page.details.label": "Detalles del error", "error.page.details.label": "Detalles del error",
"error.page.action.restart": "Reiniciar", "error.page.action.restart": "Reiniciar",
"error.page.action.report": "Informar error", "error.page.action.report": "Informar error",
"error.page.action.reported": "Error informado", "error.page.action.reported": "Error informado",
"error.page.action.exportLogs": "Exportar registros",
"error.page.action.checking": "Comprobando...", "error.page.action.checking": "Comprobando...",
"error.page.action.checkUpdates": "Buscar actualizaciones", "error.page.action.checkUpdates": "Buscar actualizaciones",
"error.page.action.updateTo": "Actualizar a {{version}}", "error.page.action.updateTo": "Actualizar a {{version}}",
@@ -505,11 +594,33 @@ export const dict = {
"home.recentProjects": "Proyectos recientes", "home.recentProjects": "Proyectos recientes",
"home.empty.title": "Sin proyectos recientes", "home.empty.title": "Sin proyectos recientes",
"home.empty.description": "Empieza abriendo un proyecto local", "home.empty.description": "Empieza abriendo un proyecto local",
"home.title": "Inicio",
"home.projects": "Proyectos",
"home.project.add": "Añadir proyecto",
"home.recentlyClosed": "Proyectos cerrados recientemente",
"home.server.collapse": "Contraer proyectos del servidor",
"home.server.expand": "Expandir proyectos del servidor",
"home.sessions.search.placeholder": "Buscar sesiones",
"home.sessions.search.placeholder.scoped": "Buscar sesiones en {{scope}}",
"home.sessions.search.sessions": "Sesiones",
"home.sessions.search.noResults": "No se encontraron sesiones para {{query}}",
"home.sessions.empty": "Aún no hay nada",
"home.sessions.empty.description": "Crea una sesión para empezar",
"home.sessions.group.today": "Hoy",
"home.sessions.group.yesterday": "Ayer",
"home.sessions.group.older": "Anteriores",
"home.providerTip":
"Conéctate a más de 75 proveedores para usar otros modelos, como Claude, GPT, Gemini y muchos más",
"session.tab.session": "Sesión", "session.tab.session": "Sesión",
"session.tab.review": "Revisión", "session.tab.review": "Revisión",
"session.tab.context": "Contexto", "session.tab.context": "Contexto",
"session.tab.unknown": "Sesión desconocida",
"session.panel.reviewAndFiles": "Revisión y archivos", "session.panel.reviewAndFiles": "Revisión y archivos",
"session.error.notFound": "No se encuentra esta sesión",
"session.error.notFound.description": "Esta pestaña apunta a una sesión que ya no existe en este servidor.",
"session.error.notFound.closeTab": "Cerrar pestaña",
"session.error.serverConnection": "No se puede conectar a este servidor",
"session.review.filesChanged": "{{count}} Archivos Cambiados", "session.review.filesChanged": "{{count}} Archivos Cambiados",
"session.review.change.one": "Cambio", "session.review.change.one": "Cambio",
"session.review.change.other": "Cambios", "session.review.change.other": "Cambios",
@@ -519,6 +630,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles", "El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles",
"session.review.noChanges": "Sin cambios", "session.review.noChanges": "Sin cambios",
"session.review.noUncommittedChanges": "Aún no hay cambios sin confirmar",
"session.review.noBranchChanges": "Aún no hay cambios en la rama",
"session.files.selectToOpen": "Selecciona un archivo para abrir", "session.files.selectToOpen": "Selecciona un archivo para abrir",
"session.files.all": "Todos los archivos", "session.files.all": "Todos los archivos",
@@ -532,9 +645,10 @@ export const dict = {
"session.messages.jumpToLatest": "Ir al último", "session.messages.jumpToLatest": "Ir al último",
"session.context.addToContext": "Añadir {{selection}} al contexto", "session.context.addToContext": "Añadir {{selection}} al contexto",
"session.todo.title": "Tareas", "session.question.minimize": "Minimizar pregunta",
"session.todo.collapse": "Contraer", "session.question.restore": "Restaurar pregunta",
"session.todo.expand": "Expandir", "session.question.pending.one": "{{count}} pregunta pendiente",
"session.question.pending.other": "{{count}} preguntas pendientes",
"session.followupDock.summary.one": "{{count}} mensaje en cola", "session.followupDock.summary.one": "{{count}} mensaje en cola",
"session.followupDock.summary.other": "{{count}} mensajes en cola", "session.followupDock.summary.other": "{{count}} mensajes en cola",
"session.followupDock.sendNow": "Enviar ahora", "session.followupDock.sendNow": "Enviar ahora",
@@ -548,9 +662,16 @@ export const dict = {
"session.revertDock.restore": "Restaurar mensaje", "session.revertDock.restore": "Restaurar mensaje",
"session.new.title": "Construye lo que quieras", "session.new.title": "Construye lo que quieras",
"session.new.project.new": "Nuevo proyecto",
"session.new.project.search": "Buscar proyectos",
"session.new.project.add": "Añadir proyecto",
"session.new.worktree.main": "Rama principal", "session.new.worktree.main": "Rama principal",
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})", "session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
"session.new.worktree.create": "Crear nuevo árbol de trabajo", "session.new.worktree.create": "Crear nuevo árbol de trabajo",
"session.new.workspace.runIn": "Ejecutar sesión en",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Repositorio local",
"session.new.workspace.existing": "Espacio de trabajo…",
"session.new.lastModified": "Última modificación", "session.new.lastModified": "Última modificación",
"session.header.search.placeholder": "Buscar {{project}}", "session.header.search.placeholder": "Buscar {{project}}",
@@ -621,6 +742,8 @@ export const dict = {
"sidebar.project.recentSessions": "Sesiones recientes", "sidebar.project.recentSessions": "Sesiones recientes",
"sidebar.project.viewAllSessions": "Ver todas las sesiones", "sidebar.project.viewAllSessions": "Ver todas las sesiones",
"sidebar.project.clearNotifications": "Borrar notificaciones", "sidebar.project.clearNotifications": "Borrar notificaciones",
"sidebar.empty.title": "No hay proyectos abiertos",
"sidebar.empty.description": "Abre un proyecto para empezar",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@@ -633,6 +756,7 @@ export const dict = {
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.", "settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
"settings.general.section.appearance": "Apariencia", "settings.general.section.appearance": "Apariencia",
"settings.general.section.advanced": "Avanzado",
"settings.general.section.notifications": "Notificaciones del sistema", "settings.general.section.notifications": "Notificaciones del sistema",
"settings.general.section.updates": "Actualizaciones", "settings.general.section.updates": "Actualizaciones",
"settings.general.section.sounds": "Efectos de sonido", "settings.general.section.sounds": "Efectos de sonido",
@@ -641,6 +765,11 @@ export const dict = {
"settings.general.row.language.title": "Idioma", "settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode", "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
"settings.general.row.shell.title": "Shell del terminal",
"settings.general.row.shell.description":
"Elige el shell que se usará en el terminal. Los shells compatibles también se usarán para las llamadas a herramientas del agente.",
"settings.general.row.shell.autoDefault": "Automático (Predeterminado)",
"settings.general.row.shell.terminalOnly": "solo en el terminal",
"settings.general.row.appearance.title": "Apariencia", "settings.general.row.appearance.title": "Apariencia",
"settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo", "settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
"settings.general.row.colorScheme.title": "Esquema de color", "settings.general.row.colorScheme.title": "Esquema de color",
@@ -649,8 +778,8 @@ export const dict = {
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.", "settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
"settings.general.row.font.title": "Fuente de código", "settings.general.row.font.title": "Fuente de código",
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código", "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Fuente del terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personaliza la fuente utilizada en el terminal",
"settings.general.row.uiFont.title": "Fuente de la interfaz", "settings.general.row.uiFont.title": "Fuente de la interfaz",
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz", "settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
"settings.general.row.followup.title": "Comportamiento de seguimiento", "settings.general.row.followup.title": "Comportamiento de seguimiento",
@@ -658,6 +787,23 @@ export const dict = {
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola", "Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
"settings.general.row.followup.option.queue": "Cola", "settings.general.row.followup.option.queue": "Cola",
"settings.general.row.followup.option.steer": "Dirigir", "settings.general.row.followup.option.steer": "Dirigir",
"settings.general.row.showFileTree.title": "Árbol de archivos",
"settings.general.row.showFileTree.description": "Mostrar el panel del árbol de archivos en las sesiones",
"settings.general.row.showNavigation.title": "Controles de navegación",
"settings.general.row.showNavigation.description":
"Mostrar los botones para retroceder y avanzar en la barra de título de la aplicación de escritorio",
"settings.general.row.showSearch.title": "Paleta de comandos",
"settings.general.row.showSearch.description":
"Mostrar el botón de búsqueda y paleta de comandos en la barra de título",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título",
"settings.general.row.showStatus.title": "Estado del servidor",
"settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título",
"settings.general.row.mobileTitlebarBottom.title": "Navegación inferior",
"settings.general.row.mobileTitlebarBottom.description":
"Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles",
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
"settings.general.row.showCustomAgents.description": "Mostrar el selector de agentes en el editor",
"settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento", "settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Mostrar resúmenes del razonamiento del modelo en la línea de tiempo", "Mostrar resúmenes del razonamiento del modelo en la línea de tiempo",
@@ -667,6 +813,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición", "settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo", "Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
"settings.general.row.newLayoutDesigns.title": "Nuevo diseño y estilos",
"settings.general.row.newLayoutDesigns.description":
"Activar el nuevo diseño de la interfaz, la página de inicio, el editor y las sesiones",
"settings.general.row.pinchZoom.title": "Pellizcar para ampliar",
"settings.general.row.pinchZoom.description":
"Permitir ampliar con el gesto de pellizco del panel táctil y con Ctrl-scroll",
"settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.title": "Usar Wayland nativo",
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.", "settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -682,6 +834,8 @@ export const dict = {
"settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna", "settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna",
"settings.updates.action.checkNow": "Buscar ahora", "settings.updates.action.checkNow": "Buscar ahora",
"settings.updates.action.checking": "Buscando...", "settings.updates.action.checking": "Buscando...",
"settings.updates.action.downloading": "Descargando...",
"settings.updates.action.installing": "Instalando...",
"settings.updates.toast.latest.title": "Estás al día", "settings.updates.toast.latest.title": "Estás al día",
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.", "settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
"sound.option.none": "Ninguno", "sound.option.none": "Ninguno",
@@ -812,8 +966,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Cargar una habilidad por nombre", "settings.permissions.tool.skill.description": "Cargar una habilidad por nombre",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Ejecutar consultas de servidor de lenguaje", "settings.permissions.tool.lsp.description": "Ejecutar consultas de servidor de lenguaje",
"settings.permissions.tool.todowrite.title": "Escribir Todo",
"settings.permissions.tool.todowrite.description": "Actualizar la lista de tareas",
"settings.permissions.tool.webfetch.title": "Web Fetch", "settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Obtener contenido de una URL", "settings.permissions.tool.webfetch.description": "Obtener contenido de una URL",
"settings.permissions.tool.websearch.title": "Búsqueda Web", "settings.permissions.tool.websearch.title": "Búsqueda Web",
@@ -878,7 +1030,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Rastrea, revisa y deshaz cambios en este proyecto", "session.review.noVcs.createGit.description": "Rastrea, revisa y deshaz cambios en este proyecto",
"session.review.noVcs.createGit.actionLoading": "Creando repositorio Git...", "session.review.noVcs.createGit.actionLoading": "Creando repositorio Git...",
"session.review.noVcs.createGit.action": "Crear repositorio Git", "session.review.noVcs.createGit.action": "Crear repositorio Git",
"session.todo.progress": "{{done}} de {{total}} tareas completadas",
"session.question.progress": "{{current}} de {{total}} preguntas", "session.question.progress": "{{current}} de {{total}} preguntas",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Explorador de archivos", "session.header.open.fileExplorer": "Explorador de archivos",
+160 -8
View File
@@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Sombre", "theme.scheme.dark": "Sombre",
"command.sidebar.toggle": "Basculer la barre latérale", "command.sidebar.toggle": "Basculer la barre latérale",
"command.project.open": "Ouvrir un projet", "command.project.open": "Ouvrir un projet",
"command.project.previous": "Projet précédent",
"command.project.next": "Projet suivant",
"command.project.index": "Passer au projet {{index}}",
"command.provider.connect": "Connecter un fournisseur", "command.provider.connect": "Connecter un fournisseur",
"command.server.switch": "Changer de serveur", "command.server.switch": "Changer de serveur",
"command.settings.open": "Ouvrir les paramètres", "command.settings.open": "Ouvrir les paramètres",
@@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nouvelle session", "command.session.new": "Nouvelle session",
"command.file.open": "Ouvrir un fichier", "command.file.open": "Ouvrir un fichier",
"command.tab.close": "Fermer l'onglet", "command.tab.close": "Fermer l'onglet",
"command.tab.reopenClosed": "Rouvrir l'onglet fermé",
"command.context.addSelection": "Ajouter la sélection au contexte", "command.context.addSelection": "Ajouter la sélection au contexte",
"command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel", "command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel",
"command.input.focus": "Focus sur l'entrée", "command.input.focus": "Focus sur l'entrée",
@@ -193,6 +197,11 @@ export const dict = {
"model.input.video": "vidéo", "model.input.video": "vidéo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Autorise : {{inputs}}", "model.tooltip.allows": "Autorise : {{inputs}}",
"model.tooltip.context.label": "Contexte",
"model.tooltip.inputs": "Entrées",
"model.tooltip.model": "Modèle",
"model.tooltip.provider": "Fournisseur",
"model.tooltip.reasoning": "Raisonnement",
"model.tooltip.reasoning.allowed": "Autorise le raisonnement", "model.tooltip.reasoning.allowed": "Autorise le raisonnement",
"model.tooltip.reasoning.none": "Sans raisonnement", "model.tooltip.reasoning.none": "Sans raisonnement",
"model.tooltip.context": "Limite de contexte {{limit}}", "model.tooltip.context": "Limite de contexte {{limit}}",
@@ -202,6 +211,7 @@ export const dict = {
"common.loading": "Chargement", "common.loading": "Chargement",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Annuler", "common.cancel": "Annuler",
"common.clear": "Effacer",
"common.connect": "Connecter", "common.connect": "Connecter",
"common.disconnect": "Déconnecter", "common.disconnect": "Déconnecter",
"common.continue": "Soumettre", "common.continue": "Soumettre",
@@ -218,6 +228,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc pour quitter", "prompt.mode.shell.exit": "esc pour quitter",
"session.child.promptDisabled": "Les sessions de sous-agents ne peuvent pas recevoir de messages.",
"session.child.backToParent": "Revenir à la session principale.",
"prompt.example.1": "Corriger un TODO dans la base de code", "prompt.example.1": "Corriger un TODO dans la base de code",
"prompt.example.2": "Quelle est la pile technique de ce projet ?", "prompt.example.2": "Quelle est la pile technique de ce projet ?",
"prompt.example.3": "Réparer les tests échoués", "prompt.example.3": "Réparer les tests échoués",
@@ -255,6 +267,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Retirer le fichier actif du contexte", "prompt.context.removeActiveFile": "Retirer le fichier actif du contexte",
"prompt.context.removeFile": "Retirer le fichier du contexte", "prompt.context.removeFile": "Retirer le fichier du contexte",
"prompt.action.attachFile": "Joindre un fichier", "prompt.action.attachFile": "Joindre un fichier",
"prompt.menu.addImagesAndFiles": "Ajouter des fichiers et plus encore",
"prompt.menu.imagesAndFiles": "Images et fichiers",
"prompt.menu.commands": "Commandes",
"prompt.menu.context": "Contexte",
"prompt.menu.shellCommand": "Commande shell",
"prompt.attachment.remove": "Supprimer la pièce jointe", "prompt.attachment.remove": "Supprimer la pièce jointe",
"prompt.action.send": "Envoyer", "prompt.action.send": "Envoyer",
"prompt.action.stop": "Arrêter", "prompt.action.stop": "Arrêter",
@@ -315,6 +332,75 @@ export const dict = {
"dialog.server.menu.delete": "Supprimer", "dialog.server.menu.delete": "Supprimer",
"dialog.server.current": "Serveur actuel", "dialog.server.current": "Serveur actuel",
"dialog.server.status.default": "Défaut", "dialog.server.status.default": "Défaut",
"wsl.server.add": "Ajouter un serveur WSL",
"wsl.server.addShort": "Ajouter WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Serveur WSL",
"wsl.server.retryStart": "Réessayer de démarrer",
"wsl.server.updating": "Mise à jour...",
"wsl.onboarding.step.distro": "Choisir une distribution",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Vérification de WSL...",
"wsl.onboarding.restartRequired": "Windows doit redémarrer pour terminer l'installation de WSL.",
"wsl.onboarding.ready": "WSL est prêt.",
"wsl.onboarding.required": "WSL est requis pour continuer.",
"wsl.onboarding.checkingDistros": "Vérification des distributions...",
"wsl.onboarding.installingDistro": "Installation de {{distro}}...",
"wsl.onboarding.checkingDistro": "Vérification de {{distro}}...",
"wsl.onboarding.listingDistros": "Récupération de la liste des distributions...",
"wsl.onboarding.distroReady": "{{distro}} est prête.",
"wsl.onboarding.distroNotInstalled": "{{distro}} n'est pas encore installée.",
"wsl.onboarding.openDistroOnce": "Ouvrez {{distro}} une fois pour terminer la configuration.",
"wsl.onboarding.finishingDistro": "Finalisation de la configuration de {{distro}}.",
"wsl.onboarding.pickDistro": "Choisissez une distribution ou installez-en une ci-dessous.",
"wsl.onboarding.checkingOpencode": "Vérification d'OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Vérification d'OpenCode dans {{distro}}...",
"wsl.onboarding.updatingOpencode": "Mise à jour d'OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Mise à jour d'OpenCode dans {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Mettez à jour OpenCode dans {{distro}}.",
"wsl.onboarding.updateOpencode": "Mettre à jour OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode est prêt dans {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode est prêt.",
"wsl.onboarding.installOpencodeIn": "Installez OpenCode dans {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Choisissez d'abord une distribution.",
"wsl.onboarding.loadFailed": "Impossible de charger l'état de WSL.",
"wsl.onboarding.loading": "Chargement...",
"wsl.onboarding.installedDistros": "Distributions installées",
"wsl.onboarding.checkAgain": "Vérifier à nouveau",
"wsl.onboarding.distroStatus.ready": "Prête",
"wsl.onboarding.distroStatus.checking": "Vérification...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode non installé",
"wsl.onboarding.distroStatus.missingTools": "bash et curl manquants",
"wsl.onboarding.distroStatus.unsupported": "Non prise en charge · Utilisez WSL 2",
"wsl.onboarding.needAnotherDistro": "Besoin d'une autre distribution ?",
"wsl.onboarding.needAnotherDistroHint": "Installez une distribution Linux depuis le catalogue WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL n'est pas installé",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) est requis avant qu'OpenCode puisse ajouter un serveur WSL",
"wsl.onboarding.wslUnavailable.title": "WSL indisponible",
"wsl.onboarding.wslUnavailable.description": "OpenCode n'a pas pu vérifier WSL sur cet appareil.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Redémarrez Windows pour terminer l'installation de WSL, puis rouvrez OpenCode.",
"wsl.onboarding.next": "Suivant",
"wsl.onboarding.refresh": "Actualiser",
"wsl.onboarding.allDistrosAdded": "Toutes les distributions installées ont déjà été ajoutées.",
"wsl.onboarding.noDistros": "Aucune distribution détectée pour l'instant.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installation...",
"wsl.onboarding.installDistro": "Installer une distribution",
"wsl.onboarding.searchDistros": "Rechercher des distributions",
"wsl.onboarding.wsl2Required": "WSL 2 est requis.",
"wsl.onboarding.toolsRequired": "Cette distribution nécessite bash et curl.",
"wsl.onboarding.openTerminal": "Ouvrir le terminal",
"wsl.onboarding.path": "Chemin : {{path}}",
"wsl.onboarding.notFound": "introuvable",
"wsl.onboarding.version": "Version : {{version}}",
"wsl.onboarding.unknown": "inconnue",
"wsl.onboarding.desktopVersion": "desktop {{version}}",
"wsl.onboarding.versionMismatch": "La version installée ne correspond pas à celle de l'application desktop.",
"wsl.onboarding.adding": "Ajout...",
"dialog.project.edit.title": "Modifier le projet", "dialog.project.edit.title": "Modifier le projet",
"dialog.project.edit.name": "Nom", "dialog.project.edit.name": "Nom",
"dialog.project.edit.icon": "Icône", "dialog.project.edit.icon": "Icône",
@@ -373,6 +459,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Langue", "toast.language.title": "Langue",
@@ -410,10 +497,12 @@ export const dict = {
"toast.update.action.notYet": "Pas encore", "toast.update.action.notYet": "Pas encore",
"error.page.title": "Quelque chose s'est mal passé", "error.page.title": "Quelque chose s'est mal passé",
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.", "error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
"error.page.description.localServerStartup": "Une erreur s'est produite lors du démarrage du serveur local.",
"error.page.details.label": "Détails de l'erreur", "error.page.details.label": "Détails de l'erreur",
"error.page.action.restart": "Redémarrer", "error.page.action.restart": "Redémarrer",
"error.page.action.report": "Signaler l'erreur", "error.page.action.report": "Signaler l'erreur",
"error.page.action.reported": "Erreur signalée", "error.page.action.reported": "Erreur signalée",
"error.page.action.exportLogs": "Exporter les journaux",
"error.page.action.checking": "Vérification...", "error.page.action.checking": "Vérification...",
"error.page.action.checkUpdates": "Vérifier les mises à jour", "error.page.action.checkUpdates": "Vérifier les mises à jour",
"error.page.action.updateTo": "Mettre à jour vers {{version}}", "error.page.action.updateTo": "Mettre à jour vers {{version}}",
@@ -458,16 +547,40 @@ export const dict = {
"home.recentProjects": "Projets récents", "home.recentProjects": "Projets récents",
"home.empty.title": "Aucun projet récent", "home.empty.title": "Aucun projet récent",
"home.empty.description": "Commencez par ouvrir un projet local", "home.empty.description": "Commencez par ouvrir un projet local",
"home.title": "Accueil",
"home.projects": "Projets",
"home.project.add": "Ajouter un projet",
"home.recentlyClosed": "Récemment fermés",
"home.server.collapse": "Réduire les projets du serveur",
"home.server.expand": "Développer les projets du serveur",
"home.sessions.search.placeholder": "Rechercher des sessions",
"home.sessions.search.placeholder.scoped": "Rechercher les sessions dans {{scope}}",
"home.sessions.search.sessions": "Sessions",
"home.sessions.search.noResults": "Aucune session trouvée pour {{query}}",
"home.sessions.empty": "Rien ici pour l'instant",
"home.sessions.empty.description": "Créez une session pour commencer",
"home.sessions.group.today": "Aujourd'hui",
"home.sessions.group.yesterday": "Hier",
"home.sessions.group.older": "Plus anciennes",
"home.providerTip":
"Connectez-vous à plus de 75 fournisseurs pour utiliser dautres modèles, notamment Claude, GPT, Gemini, etc.",
"session.tab.session": "Session", "session.tab.session": "Session",
"session.tab.review": "Revue", "session.tab.review": "Revue",
"session.tab.context": "Contexte", "session.tab.context": "Contexte",
"session.tab.unknown": "Session inconnue",
"session.panel.reviewAndFiles": "Revue et fichiers", "session.panel.reviewAndFiles": "Revue et fichiers",
"session.error.notFound": "Cette session est introuvable",
"session.error.notFound.description": "Cet onglet pointe vers une session qui n'existe plus sur ce serveur.",
"session.error.notFound.closeTab": "Fermer l'onglet",
"session.error.serverConnection": "Impossible de se connecter à ce serveur",
"session.review.filesChanged": "{{count}} fichiers modifiés", "session.review.filesChanged": "{{count}} fichiers modifiés",
"session.review.change.one": "Modification", "session.review.change.one": "Modification",
"session.review.change.other": "Modifications", "session.review.change.other": "Modifications",
"session.review.loadingChanges": "Chargement des modifications...", "session.review.loadingChanges": "Chargement des modifications...",
"session.review.empty": "Aucune modification dans cette session pour l'instant", "session.review.empty": "Aucune modification dans cette session pour l'instant",
"session.review.noChanges": "Aucune modification", "session.review.noChanges": "Aucune modification",
"session.review.noUncommittedChanges": "Aucune modification non validée pour l'instant",
"session.review.noBranchChanges": "Aucune modification de branche pour l'instant",
"session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées", "session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées",
"session.review.noSnapshot": "session.review.noSnapshot":
"Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles", "Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles",
@@ -481,9 +594,10 @@ export const dict = {
"session.messages.loading": "Chargement des messages...", "session.messages.loading": "Chargement des messages...",
"session.messages.jumpToLatest": "Aller au dernier", "session.messages.jumpToLatest": "Aller au dernier",
"session.context.addToContext": "Ajouter {{selection}} au contexte", "session.context.addToContext": "Ajouter {{selection}} au contexte",
"session.todo.title": "Tâches", "session.question.minimize": "Réduire la question",
"session.todo.collapse": "Réduire", "session.question.restore": "Restaurer la question",
"session.todo.expand": "Développer", "session.question.pending.one": "{{count}} question en attente",
"session.question.pending.other": "{{count}} questions en attente",
"session.followupDock.summary.one": "{{count}} message en file d'attente", "session.followupDock.summary.one": "{{count}} message en file d'attente",
"session.followupDock.summary.other": "{{count}} messages en file d'attente", "session.followupDock.summary.other": "{{count}} messages en file d'attente",
"session.followupDock.sendNow": "Envoyer maintenant", "session.followupDock.sendNow": "Envoyer maintenant",
@@ -496,9 +610,16 @@ export const dict = {
"session.revertDock.expand": "Développer les messages annulés", "session.revertDock.expand": "Développer les messages annulés",
"session.revertDock.restore": "Restaurer le message", "session.revertDock.restore": "Restaurer le message",
"session.new.title": "Créez ce que vous voulez", "session.new.title": "Créez ce que vous voulez",
"session.new.project.new": "Nouveau projet",
"session.new.project.search": "Rechercher des projets",
"session.new.project.add": "Ajouter un projet",
"session.new.worktree.main": "Branche principale", "session.new.worktree.main": "Branche principale",
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})", "session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
"session.new.worktree.create": "Créer un nouvel arbre de travail", "session.new.worktree.create": "Créer un nouvel arbre de travail",
"session.new.workspace.runIn": "Exécuter la session dans",
"session.new.workspace.triggerLocal": "Local",
"session.new.workspace.local": "Dépôt local",
"session.new.workspace.existing": "Espace de travail…",
"session.new.lastModified": "Dernière modification", "session.new.lastModified": "Dernière modification",
"session.header.search.placeholder": "Rechercher {{project}}", "session.header.search.placeholder": "Rechercher {{project}}",
"session.header.searchFiles": "Rechercher des fichiers", "session.header.searchFiles": "Rechercher des fichiers",
@@ -564,6 +685,8 @@ export const dict = {
"sidebar.project.recentSessions": "Sessions récentes", "sidebar.project.recentSessions": "Sessions récentes",
"sidebar.project.viewAllSessions": "Voir toutes les sessions", "sidebar.project.viewAllSessions": "Voir toutes les sessions",
"sidebar.project.clearNotifications": "Effacer les notifications", "sidebar.project.clearNotifications": "Effacer les notifications",
"sidebar.empty.title": "Aucun projet ouvert",
"sidebar.empty.description": "Ouvrez un projet pour commencer",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Bureau", "settings.section.desktop": "Bureau",
"settings.section.server": "Serveur", "settings.section.server": "Serveur",
@@ -573,6 +696,7 @@ export const dict = {
"settings.desktop.wsl.title": "Intégration WSL", "settings.desktop.wsl.title": "Intégration WSL",
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.", "settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
"settings.general.section.appearance": "Apparence", "settings.general.section.appearance": "Apparence",
"settings.general.section.advanced": "Avancé",
"settings.general.section.notifications": "Notifications système", "settings.general.section.notifications": "Notifications système",
"settings.general.section.updates": "Mises à jour", "settings.general.section.updates": "Mises à jour",
"settings.general.section.sounds": "Effets sonores", "settings.general.section.sounds": "Effets sonores",
@@ -580,6 +704,11 @@ export const dict = {
"settings.general.section.display": "Affichage", "settings.general.section.display": "Affichage",
"settings.general.row.language.title": "Langue", "settings.general.row.language.title": "Langue",
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode", "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
"settings.general.row.shell.title": "Shell du terminal",
"settings.general.row.shell.description":
"Choisissez le shell utilisé par votre terminal. Les shells compatibles sont également utilisés pour les appels d'outils de l'agent.",
"settings.general.row.shell.autoDefault": "Auto (par défaut)",
"settings.general.row.shell.terminalOnly": "terminal uniquement",
"settings.general.row.appearance.title": "Apparence", "settings.general.row.appearance.title": "Apparence",
"settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil", "settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
"settings.general.row.colorScheme.title": "Schéma de couleurs", "settings.general.row.colorScheme.title": "Schéma de couleurs",
@@ -588,8 +717,8 @@ export const dict = {
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.", "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
"settings.general.row.font.title": "Police de code", "settings.general.row.font.title": "Police de code",
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code", "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Police du terminal",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Personnalisez la police utilisée dans le terminal",
"settings.general.row.uiFont.title": "Police de l'interface", "settings.general.row.uiFont.title": "Police de l'interface",
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface", "settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
"settings.general.row.followup.title": "Comportement de suivi", "settings.general.row.followup.title": "Comportement de suivi",
@@ -597,6 +726,24 @@ export const dict = {
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente", "Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
"settings.general.row.followup.option.queue": "File d'attente", "settings.general.row.followup.option.queue": "File d'attente",
"settings.general.row.followup.option.steer": "Diriger", "settings.general.row.followup.option.steer": "Diriger",
"settings.general.row.showFileTree.title": "Arborescence des fichiers",
"settings.general.row.showFileTree.description": "Afficher le panneau d'arborescence des fichiers dans les sessions",
"settings.general.row.showNavigation.title": "Commandes de navigation",
"settings.general.row.showNavigation.description":
"Afficher les boutons précédent et suivant dans la barre de titre de l'application desktop",
"settings.general.row.showSearch.title": "Palette de commandes",
"settings.general.row.showSearch.description":
"Afficher le bouton de recherche et de palette de commandes dans la barre de titre",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description":
"Afficher le bouton du terminal dans la barre de titre de l'application desktop",
"settings.general.row.showStatus.title": "État du serveur",
"settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre",
"settings.general.row.mobileTitlebarBottom.title": "Navigation en bas",
"settings.general.row.mobileTitlebarBottom.description":
"Placer la barre de titre et les onglets de session en bas de l'écran sur mobile",
"settings.general.row.showCustomAgents.title": "Agents personnalisés",
"settings.general.row.showCustomAgents.description": "Afficher le sélecteur d'agent dans la zone de saisie",
"settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement", "settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement",
"settings.general.row.reasoningSummaries.description": "settings.general.row.reasoningSummaries.description":
"Afficher les résumés de raisonnement du modèle dans la chronologie", "Afficher les résumés de raisonnement du modèle dans la chronologie",
@@ -606,6 +753,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit", "settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie", "Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
"settings.general.row.newLayoutDesigns.title": "Nouvelle mise en page et nouveaux designs",
"settings.general.row.newLayoutDesigns.description":
"Activer la nouvelle mise en page et les nouvelles interfaces d'accueil, de saisie et de session",
"settings.general.row.pinchZoom.title": "Pincer pour zoomer",
"settings.general.row.pinchZoom.description":
"Autoriser les gestes de pincement du pavé tactile et Ctrl-défilement pour zoomer",
"settings.general.row.wayland.title": "Utiliser Wayland natif", "settings.general.row.wayland.title": "Utiliser Wayland natif",
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.", "settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -618,6 +771,8 @@ export const dict = {
"settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible", "settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible",
"settings.updates.action.checkNow": "Vérifier maintenant", "settings.updates.action.checkNow": "Vérifier maintenant",
"settings.updates.action.checking": "Vérification...", "settings.updates.action.checking": "Vérification...",
"settings.updates.action.downloading": "Téléchargement...",
"settings.updates.action.installing": "Installation...",
"settings.updates.toast.latest.title": "Vous êtes à jour", "settings.updates.toast.latest.title": "Vous êtes à jour",
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.", "settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
"sound.option.none": "Aucun", "sound.option.none": "Aucun",
@@ -740,8 +895,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Charger une compétence par son nom", "settings.permissions.tool.skill.description": "Charger une compétence par son nom",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Exécuter des requêtes de serveur de langage", "settings.permissions.tool.lsp.description": "Exécuter des requêtes de serveur de langage",
"settings.permissions.tool.todowrite.title": "Écrire Todo",
"settings.permissions.tool.todowrite.description": "Mettre à jour la liste de tâches",
"settings.permissions.tool.webfetch.title": "Récupération Web", "settings.permissions.tool.webfetch.title": "Récupération Web",
"settings.permissions.tool.webfetch.description": "Récupérer le contenu d'une URL", "settings.permissions.tool.webfetch.description": "Récupérer le contenu d'une URL",
"settings.permissions.tool.websearch.title": "Recherche Web", "settings.permissions.tool.websearch.title": "Recherche Web",
@@ -804,7 +957,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Suivre, examiner et annuler les modifications dans ce projet", "session.review.noVcs.createGit.description": "Suivre, examiner et annuler les modifications dans ce projet",
"session.review.noVcs.createGit.actionLoading": "Création du dépôt Git...", "session.review.noVcs.createGit.actionLoading": "Création du dépôt Git...",
"session.review.noVcs.createGit.action": "Créer un dépôt Git", "session.review.noVcs.createGit.action": "Créer un dépôt Git",
"session.todo.progress": "{{done}} tâches sur {{total}} terminées",
"session.question.progress": "{{current}} questions sur {{total}}", "session.question.progress": "{{current}} questions sur {{total}}",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Explorateur de fichiers", "session.header.open.fileExplorer": "Explorateur de fichiers",
+157 -9
View File
@@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "ダーク", "theme.scheme.dark": "ダーク",
"command.sidebar.toggle": "サイドバーの切り替え", "command.sidebar.toggle": "サイドバーの切り替え",
"command.project.open": "プロジェクトを開く", "command.project.open": "プロジェクトを開く",
"command.project.previous": "前のプロジェクト",
"command.project.next": "次のプロジェクト",
"command.project.index": "プロジェクト{{index}}に切り替え",
"command.provider.connect": "プロバイダーに接続", "command.provider.connect": "プロバイダーに接続",
"command.server.switch": "サーバーの切り替え", "command.server.switch": "サーバーの切り替え",
"command.settings.open": "設定を開く", "command.settings.open": "設定を開く",
@@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "新しいセッション", "command.session.new": "新しいセッション",
"command.file.open": "ファイルを開く", "command.file.open": "ファイルを開く",
"command.tab.close": "タブを閉じる", "command.tab.close": "タブを閉じる",
"command.tab.reopenClosed": "閉じたタブを再度開く",
"command.context.addSelection": "選択範囲をコンテキストに追加", "command.context.addSelection": "選択範囲をコンテキストに追加",
"command.context.addSelection.description": "現在のファイルから選択した行を追加", "command.context.addSelection.description": "現在のファイルから選択した行を追加",
"command.input.focus": "入力欄にフォーカス", "command.input.focus": "入力欄にフォーカス",
@@ -149,7 +153,7 @@ export const dict = {
"provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.placeholder": "myprovider",
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア", "provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
"provider.custom.field.name.label": "表示名", "provider.custom.field.name.label": "表示名",
"provider.custom.field.name.placeholder": "My AI Provider", "provider.custom.field.name.placeholder": "マイAIプロバイダー",
"provider.custom.field.baseURL.label": "ベースURL", "provider.custom.field.baseURL.label": "ベースURL",
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
"provider.custom.field.apiKey.label": "APIキー", "provider.custom.field.apiKey.label": "APIキー",
@@ -192,6 +196,11 @@ export const dict = {
"model.input.video": "動画", "model.input.video": "動画",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "対応: {{inputs}}", "model.tooltip.allows": "対応: {{inputs}}",
"model.tooltip.context.label": "コンテキスト",
"model.tooltip.inputs": "入力",
"model.tooltip.model": "モデル",
"model.tooltip.provider": "プロバイダー",
"model.tooltip.reasoning": "推論",
"model.tooltip.reasoning.allowed": "推論を許可", "model.tooltip.reasoning.allowed": "推論を許可",
"model.tooltip.reasoning.none": "推論なし", "model.tooltip.reasoning.none": "推論なし",
"model.tooltip.context": "コンテキスト上限 {{limit}}", "model.tooltip.context": "コンテキスト上限 {{limit}}",
@@ -201,6 +210,7 @@ export const dict = {
"common.loading": "読み込み中", "common.loading": "読み込み中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "キャンセル", "common.cancel": "キャンセル",
"common.clear": "クリア",
"common.connect": "接続", "common.connect": "接続",
"common.disconnect": "切断", "common.disconnect": "切断",
"common.continue": "送信", "common.continue": "送信",
@@ -217,6 +227,8 @@ export const dict = {
"prompt.mode.shell": "シェル", "prompt.mode.shell": "シェル",
"prompt.mode.normal": "プロンプト", "prompt.mode.normal": "プロンプト",
"prompt.mode.shell.exit": "escで終了", "prompt.mode.shell.exit": "escで終了",
"session.child.promptDisabled": "サブエージェントのセッションにはプロンプトを送信できません。",
"session.child.backToParent": "メインセッションに戻る。",
"prompt.example.1": "コードベースのTODOを修正", "prompt.example.1": "コードベースのTODOを修正",
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?", "prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
"prompt.example.3": "壊れたテストを修正", "prompt.example.3": "壊れたテストを修正",
@@ -254,6 +266,11 @@ export const dict = {
"prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除", "prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除",
"prompt.context.removeFile": "コンテキストからファイルを削除", "prompt.context.removeFile": "コンテキストからファイルを削除",
"prompt.action.attachFile": "ファイルを添付", "prompt.action.attachFile": "ファイルを添付",
"prompt.menu.addImagesAndFiles": "ファイルなどを追加",
"prompt.menu.imagesAndFiles": "画像とファイル",
"prompt.menu.commands": "コマンド",
"prompt.menu.context": "コンテキスト",
"prompt.menu.shellCommand": "シェルコマンド",
"prompt.attachment.remove": "添付ファイルを削除", "prompt.attachment.remove": "添付ファイルを削除",
"prompt.action.send": "送信", "prompt.action.send": "送信",
"prompt.action.stop": "停止", "prompt.action.stop": "停止",
@@ -313,6 +330,75 @@ export const dict = {
"dialog.server.menu.delete": "削除", "dialog.server.menu.delete": "削除",
"dialog.server.current": "現在のサーバー", "dialog.server.current": "現在のサーバー",
"dialog.server.status.default": "デフォルト", "dialog.server.status.default": "デフォルト",
"wsl.server.add": "WSLサーバーを追加",
"wsl.server.addShort": "WSLを追加",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSLサーバー",
"wsl.server.retryStart": "起動を再試行",
"wsl.server.updating": "更新中...",
"wsl.onboarding.step.distro": "ディストリビューションを選択",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSLを確認中...",
"wsl.onboarding.restartRequired": "WSLのインストールを完了するにはWindowsの再起動が必要です。",
"wsl.onboarding.ready": "WSLの準備ができました。",
"wsl.onboarding.required": "続行するにはWSLが必要です。",
"wsl.onboarding.checkingDistros": "ディストリビューションを確認中...",
"wsl.onboarding.installingDistro": "{{distro}}をインストール中...",
"wsl.onboarding.checkingDistro": "{{distro}}を確認中...",
"wsl.onboarding.listingDistros": "ディストリビューションの一覧を取得中...",
"wsl.onboarding.distroReady": "{{distro}}の準備ができました。",
"wsl.onboarding.distroNotInstalled": "{{distro}}はまだインストールされていません。",
"wsl.onboarding.openDistroOnce": "セットアップを完了するには、{{distro}}を一度開いてください。",
"wsl.onboarding.finishingDistro": "{{distro}}のセットアップを完了しています。",
"wsl.onboarding.pickDistro": "ディストリビューションを選択するか、以下からインストールしてください。",
"wsl.onboarding.checkingOpencode": "OpenCodeを確認中...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}内のOpenCodeを確認中...",
"wsl.onboarding.updatingOpencode": "OpenCodeを更新中...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}内のOpenCodeを更新中...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}内のOpenCodeを更新してください。",
"wsl.onboarding.updateOpencode": "OpenCodeを更新",
"wsl.onboarding.opencodeReadyIn": "{{distro}}内のOpenCodeの準備ができました。",
"wsl.onboarding.opencodeReady": "OpenCodeの準備ができました。",
"wsl.onboarding.installOpencodeIn": "{{distro}}にOpenCodeをインストールしてください。",
"wsl.onboarding.installOpencode": "OpenCodeをインストール",
"wsl.onboarding.chooseDistroFirst": "先にディストリビューションを選択してください。",
"wsl.onboarding.loadFailed": "WSLの状態を読み込めませんでした。",
"wsl.onboarding.loading": "読み込み中...",
"wsl.onboarding.installedDistros": "インストール済みのディストリビューション",
"wsl.onboarding.checkAgain": "再確認",
"wsl.onboarding.distroStatus.ready": "準備完了",
"wsl.onboarding.distroStatus.checking": "確認中...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCodeがインストールされていません",
"wsl.onboarding.distroStatus.missingTools": "bash、curlがありません",
"wsl.onboarding.distroStatus.unsupported": "非対応 · WSL 2を使用してください",
"wsl.onboarding.needAnotherDistro": "別のディストリビューションが必要ですか?",
"wsl.onboarding.needAnotherDistroHint": "WSLカタログからLinuxディストリビューションをインストール",
"wsl.onboarding.wslNotInstalled.title": "WSLがインストールされていません",
"wsl.onboarding.wslNotInstalled.description":
"OpenCodeでWSLサーバーを追加するには、WSL (Windows Subsystem for Linux) が必要です",
"wsl.onboarding.wslUnavailable.title": "WSLを利用できません",
"wsl.onboarding.wslUnavailable.description": "このマシンのWSLを確認できませんでした。",
"wsl.onboarding.installWsl": "WSLをインストール",
"wsl.onboarding.windowsRestartRequired":
"WSLのインストールを完了するにはWindowsを再起動し、OpenCodeをもう一度開いてください。",
"wsl.onboarding.next": "次へ",
"wsl.onboarding.refresh": "更新",
"wsl.onboarding.allDistrosAdded": "インストール済みのディストリビューションはすべて追加済みです。",
"wsl.onboarding.noDistros": "ディストリビューションはまだ検出されていません。",
"wsl.onboarding.install": "インストール",
"wsl.onboarding.installing": "インストール中...",
"wsl.onboarding.installDistro": "ディストリビューションをインストール",
"wsl.onboarding.searchDistros": "ディストリビューションを検索",
"wsl.onboarding.wsl2Required": "WSL 2が必要です。",
"wsl.onboarding.toolsRequired": "このディストリビューションにはbashとcurlが必要です。",
"wsl.onboarding.openTerminal": "ターミナルを開く",
"wsl.onboarding.path": "パス: {{path}}",
"wsl.onboarding.notFound": "見つかりません",
"wsl.onboarding.version": "バージョン: {{version}}",
"wsl.onboarding.unknown": "不明",
"wsl.onboarding.desktopVersion": "デスクトップ {{version}}",
"wsl.onboarding.versionMismatch": "インストールされているバージョンがデスクトップアプリのバージョンと一致しません。",
"wsl.onboarding.adding": "追加中...",
"dialog.project.edit.title": "プロジェクトを編集", "dialog.project.edit.title": "プロジェクトを編集",
"dialog.project.edit.name": "名前", "dialog.project.edit.name": "名前",
"dialog.project.edit.icon": "アイコン", "dialog.project.edit.icon": "アイコン",
@@ -370,6 +456,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "言語", "toast.language.title": "言語",
@@ -406,10 +493,12 @@ export const dict = {
"toast.update.action.notYet": "今はしない", "toast.update.action.notYet": "今はしない",
"error.page.title": "問題が発生しました", "error.page.title": "問題が発生しました",
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。", "error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
"error.page.description.localServerStartup": "ローカルサーバーの起動中にエラーが発生しました。",
"error.page.details.label": "エラー詳細", "error.page.details.label": "エラー詳細",
"error.page.action.restart": "再起動", "error.page.action.restart": "再起動",
"error.page.action.report": "エラーを報告", "error.page.action.report": "エラーを報告",
"error.page.action.reported": "エラーを報告しました", "error.page.action.reported": "エラーを報告しました",
"error.page.action.exportLogs": "ログをエクスポート",
"error.page.action.checking": "確認中...", "error.page.action.checking": "確認中...",
"error.page.action.checkUpdates": "アップデートを確認", "error.page.action.checkUpdates": "アップデートを確認",
"error.page.action.updateTo": "{{version}}にアップデート", "error.page.action.updateTo": "{{version}}にアップデート",
@@ -451,10 +540,31 @@ export const dict = {
"home.recentProjects": "最近のプロジェクト", "home.recentProjects": "最近のプロジェクト",
"home.empty.title": "最近のプロジェクトはありません", "home.empty.title": "最近のプロジェクトはありません",
"home.empty.description": "ローカルプロジェクトを開いて始めましょう", "home.empty.description": "ローカルプロジェクトを開いて始めましょう",
"home.title": "ホーム",
"home.projects": "プロジェクト",
"home.project.add": "プロジェクトを追加",
"home.recentlyClosed": "最近閉じたプロジェクト",
"home.server.collapse": "サーバーのプロジェクトを折りたたむ",
"home.server.expand": "サーバーのプロジェクトを展開",
"home.sessions.search.placeholder": "セッションを検索",
"home.sessions.search.placeholder.scoped": "{{scope}}のセッションを検索",
"home.sessions.search.sessions": "セッション",
"home.sessions.search.noResults": "{{query}}に一致するセッションが見つかりません",
"home.sessions.empty": "まだ何もありません",
"home.sessions.empty.description": "セッションを作成して始めましょう",
"home.sessions.group.today": "今日",
"home.sessions.group.yesterday": "昨日",
"home.sessions.group.older": "それ以前",
"home.providerTip": "75以上のプロバイダーに接続して、Claude、GPT、Geminiなどの他のモデルを利用できます",
"session.tab.session": "セッション", "session.tab.session": "セッション",
"session.tab.review": "レビュー", "session.tab.review": "レビュー",
"session.tab.context": "コンテキスト", "session.tab.context": "コンテキスト",
"session.tab.unknown": "不明なセッション",
"session.panel.reviewAndFiles": "レビューとファイル", "session.panel.reviewAndFiles": "レビューとファイル",
"session.error.notFound": "このセッションが見つかりません",
"session.error.notFound.description": "このタブが参照するセッションは、このサーバーには存在しません。",
"session.error.notFound.closeTab": "タブを閉じる",
"session.error.serverConnection": "このサーバーに接続できません",
"session.review.filesChanged": "{{count}} ファイル変更", "session.review.filesChanged": "{{count}} ファイル変更",
"session.review.change.one": "変更", "session.review.change.one": "変更",
"session.review.change.other": "変更", "session.review.change.other": "変更",
@@ -463,6 +573,8 @@ export const dict = {
"session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません", "session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
"session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません", "session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
"session.review.noChanges": "変更なし", "session.review.noChanges": "変更なし",
"session.review.noUncommittedChanges": "コミットされていない変更はまだありません",
"session.review.noBranchChanges": "ブランチの変更はまだありません",
"session.files.selectToOpen": "開くファイルを選択", "session.files.selectToOpen": "開くファイルを選択",
"session.files.all": "すべてのファイル", "session.files.all": "すべてのファイル",
"session.files.empty": "ファイルなし", "session.files.empty": "ファイルなし",
@@ -473,9 +585,10 @@ export const dict = {
"session.messages.loading": "メッセージを読み込み中...", "session.messages.loading": "メッセージを読み込み中...",
"session.messages.jumpToLatest": "最新へジャンプ", "session.messages.jumpToLatest": "最新へジャンプ",
"session.context.addToContext": "{{selection}}をコンテキストに追加", "session.context.addToContext": "{{selection}}をコンテキストに追加",
"session.todo.title": "ToDo", "session.question.minimize": "質問を最小化",
"session.todo.collapse": "折りたたむ", "session.question.restore": "質問を復元",
"session.todo.expand": "展開", "session.question.pending.one": "{{count}}件の保留中の質問",
"session.question.pending.other": "{{count}}件の保留中の質問",
"session.followupDock.summary.one": "{{count}} 件のメッセージが待機中", "session.followupDock.summary.one": "{{count}} 件のメッセージが待機中",
"session.followupDock.summary.other": "{{count}} 件のメッセージが待機中", "session.followupDock.summary.other": "{{count}} 件のメッセージが待機中",
"session.followupDock.sendNow": "今すぐ送信", "session.followupDock.sendNow": "今すぐ送信",
@@ -488,9 +601,16 @@ export const dict = {
"session.revertDock.expand": "ロールバックされたメッセージを展開", "session.revertDock.expand": "ロールバックされたメッセージを展開",
"session.revertDock.restore": "メッセージを復元", "session.revertDock.restore": "メッセージを復元",
"session.new.title": "何でも作る", "session.new.title": "何でも作る",
"session.new.project.new": "新しいプロジェクト",
"session.new.project.search": "プロジェクトを検索",
"session.new.project.add": "プロジェクトを追加",
"session.new.worktree.main": "メインブランチ", "session.new.worktree.main": "メインブランチ",
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})", "session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
"session.new.worktree.create": "新しいワークツリーを作成", "session.new.worktree.create": "新しいワークツリーを作成",
"session.new.workspace.runIn": "セッションの実行先",
"session.new.workspace.triggerLocal": "ローカル",
"session.new.workspace.local": "ローカルリポジトリ",
"session.new.workspace.existing": "ワークスペース…",
"session.new.lastModified": "最終更新", "session.new.lastModified": "最終更新",
"session.header.search.placeholder": "{{project}}を検索", "session.header.search.placeholder": "{{project}}を検索",
"session.header.searchFiles": "ファイルを検索", "session.header.searchFiles": "ファイルを検索",
@@ -554,6 +674,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近のセッション", "sidebar.project.recentSessions": "最近のセッション",
"sidebar.project.viewAllSessions": "すべてのセッションを表示", "sidebar.project.viewAllSessions": "すべてのセッションを表示",
"sidebar.project.clearNotifications": "通知をクリア", "sidebar.project.clearNotifications": "通知をクリア",
"sidebar.empty.title": "開いているプロジェクトはありません",
"sidebar.empty.description": "プロジェクトを開いて始めましょう",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "デスクトップ", "settings.section.desktop": "デスクトップ",
"settings.section.server": "サーバー", "settings.section.server": "サーバー",
@@ -563,6 +685,7 @@ export const dict = {
"settings.desktop.wsl.title": "WSL連携", "settings.desktop.wsl.title": "WSL連携",
"settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。", "settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。",
"settings.general.section.appearance": "外観", "settings.general.section.appearance": "外観",
"settings.general.section.advanced": "詳細設定",
"settings.general.section.notifications": "システム通知", "settings.general.section.notifications": "システム通知",
"settings.general.section.updates": "アップデート", "settings.general.section.updates": "アップデート",
"settings.general.section.sounds": "効果音", "settings.general.section.sounds": "効果音",
@@ -570,6 +693,11 @@ export const dict = {
"settings.general.section.display": "ディスプレイ", "settings.general.section.display": "ディスプレイ",
"settings.general.row.language.title": "言語", "settings.general.row.language.title": "言語",
"settings.general.row.language.description": "OpenCodeの表示言語を変更します", "settings.general.row.language.description": "OpenCodeの表示言語を変更します",
"settings.general.row.shell.title": "ターミナルシェル",
"settings.general.row.shell.description":
"ターミナルで使用するシェルを選択します。互換性のあるシェルはエージェントのツール呼び出しにも使用されます。",
"settings.general.row.shell.autoDefault": "自動 (デフォルト)",
"settings.general.row.shell.terminalOnly": "ターミナルのみ",
"settings.general.row.appearance.title": "外観", "settings.general.row.appearance.title": "外観",
"settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします", "settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
"settings.general.row.colorScheme.title": "配色", "settings.general.row.colorScheme.title": "配色",
@@ -578,8 +706,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。", "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
"settings.general.row.font.title": "コードフォント", "settings.general.row.font.title": "コードフォント",
"settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします", "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "ターミナルのフォント",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ",
"settings.general.row.uiFont.title": "UIフォント", "settings.general.row.uiFont.title": "UIフォント",
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします", "settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
"settings.general.row.followup.title": "フォローアップの動作", "settings.general.row.followup.title": "フォローアップの動作",
@@ -587,6 +715,21 @@ export const dict = {
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します", "フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
"settings.general.row.followup.option.queue": "キューに追加", "settings.general.row.followup.option.queue": "キューに追加",
"settings.general.row.followup.option.steer": "即座に実行 (Steer)", "settings.general.row.followup.option.steer": "即座に実行 (Steer)",
"settings.general.row.showFileTree.title": "ファイルツリー",
"settings.general.row.showFileTree.description": "セッションにファイルツリーパネルを表示します",
"settings.general.row.showNavigation.title": "ナビゲーションコントロール",
"settings.general.row.showNavigation.description": "デスクトップのタイトルバーに戻るボタンと進むボタンを表示します",
"settings.general.row.showSearch.title": "コマンドパレット",
"settings.general.row.showSearch.description": "タイトルバーに検索とコマンドパレットのボタンを表示します",
"settings.general.row.showTerminal.title": "ターミナル",
"settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します",
"settings.general.row.showStatus.title": "サーバーステータス",
"settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します",
"settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション",
"settings.general.row.mobileTitlebarBottom.description":
"モバイルではタイトルバーとセッションタブを画面下部に配置します",
"settings.general.row.showCustomAgents.title": "カスタムエージェント",
"settings.general.row.showCustomAgents.description": "コンポーザーにエージェント選択を表示します",
"settings.general.row.reasoningSummaries.title": "推論の要約を表示", "settings.general.row.reasoningSummaries.title": "推論の要約を表示",
"settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します", "settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します",
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開", "settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
@@ -595,6 +738,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開", "settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します", "タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
"settings.general.row.newLayoutDesigns.title": "新しいレイアウトとデザイン",
"settings.general.row.newLayoutDesigns.description":
"刷新されたレイアウト、ホーム、コンポーザー、セッションUIを有効にします",
"settings.general.row.pinchZoom.title": "ピンチでズーム",
"settings.general.row.pinchZoom.description":
"トラックパッドのピンチ操作とCtrl+スクロール操作によるズームを許可します",
"settings.general.row.wayland.title": "ネイティブWaylandを使用", "settings.general.row.wayland.title": "ネイティブWaylandを使用",
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。", "settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -607,6 +756,8 @@ export const dict = {
"settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします", "settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします",
"settings.updates.action.checkNow": "今すぐ確認", "settings.updates.action.checkNow": "今すぐ確認",
"settings.updates.action.checking": "確認中...", "settings.updates.action.checking": "確認中...",
"settings.updates.action.downloading": "ダウンロード中...",
"settings.updates.action.installing": "インストール中...",
"settings.updates.toast.latest.title": "最新です", "settings.updates.toast.latest.title": "最新です",
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。", "settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
"sound.option.none": "なし", "sound.option.none": "なし",
@@ -726,8 +877,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "名前によるスキルの読み込み", "settings.permissions.tool.skill.description": "名前によるスキルの読み込み",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "言語サーバークエリの実行", "settings.permissions.tool.lsp.description": "言語サーバークエリの実行",
"settings.permissions.tool.todowrite.title": "Todo書き込み",
"settings.permissions.tool.todowrite.description": "Todoリストの更新",
"settings.permissions.tool.webfetch.title": "Web取得", "settings.permissions.tool.webfetch.title": "Web取得",
"settings.permissions.tool.webfetch.description": "URLからコンテンツを取得", "settings.permissions.tool.webfetch.description": "URLからコンテンツを取得",
"settings.permissions.tool.websearch.title": "Web検索", "settings.permissions.tool.websearch.title": "Web検索",
@@ -790,7 +939,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "このプロジェクトの変更を追跡、レビュー、元に戻す", "session.review.noVcs.createGit.description": "このプロジェクトの変更を追跡、レビュー、元に戻す",
"session.review.noVcs.createGit.actionLoading": "Git リポジトリを作成中...", "session.review.noVcs.createGit.actionLoading": "Git リポジトリを作成中...",
"session.review.noVcs.createGit.action": "Git リポジトリを作成", "session.review.noVcs.createGit.action": "Git リポジトリを作成",
"session.todo.progress": "{{done}} 個中 {{total}} 個の Todo が完了",
"session.question.progress": "{{total}} 問中 {{current}} 問", "session.question.progress": "{{total}} 問中 {{current}} 問",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "エクスプローラー", "session.header.open.fileExplorer": "エクスプローラー",
+159 -8
View File
@@ -254,6 +254,11 @@ export const dict = {
"prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거", "prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거",
"prompt.context.removeFile": "컨텍스트에서 파일 제거", "prompt.context.removeFile": "컨텍스트에서 파일 제거",
"prompt.action.attachFile": "파일 첨부", "prompt.action.attachFile": "파일 첨부",
"prompt.menu.addImagesAndFiles": "파일 및 기타 항목 추가",
"prompt.menu.imagesAndFiles": "이미지 및 파일",
"prompt.menu.commands": "명령어",
"prompt.menu.context": "컨텍스트",
"prompt.menu.shellCommand": "셸 명령",
"prompt.attachment.remove": "첨부 파일 제거", "prompt.attachment.remove": "첨부 파일 제거",
"prompt.action.send": "전송", "prompt.action.send": "전송",
"prompt.action.stop": "중지", "prompt.action.stop": "중지",
@@ -471,9 +476,6 @@ export const dict = {
"session.messages.loading": "메시지 로드 중...", "session.messages.loading": "메시지 로드 중...",
"session.messages.jumpToLatest": "최신으로 이동", "session.messages.jumpToLatest": "최신으로 이동",
"session.context.addToContext": "컨텍스트에 {{selection}} 추가", "session.context.addToContext": "컨텍스트에 {{selection}} 추가",
"session.todo.title": "할 일",
"session.todo.collapse": "접기",
"session.todo.expand": "펼치기",
"session.followupDock.summary.one": "{{count}}개의 대기 중인 메시지", "session.followupDock.summary.one": "{{count}}개의 대기 중인 메시지",
"session.followupDock.summary.other": "{{count}}개의 대기 중인 메시지", "session.followupDock.summary.other": "{{count}}개의 대기 중인 메시지",
"session.followupDock.sendNow": "지금 전송", "session.followupDock.sendNow": "지금 전송",
@@ -575,8 +577,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정", "settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
"settings.general.row.font.title": "코드 글꼴", "settings.general.row.font.title": "코드 글꼴",
"settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정", "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "터미널 글꼴",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다",
"settings.general.row.uiFont.title": "UI 글꼴", "settings.general.row.uiFont.title": "UI 글꼴",
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정", "settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
"settings.general.row.followup.title": "후속 조치 동작", "settings.general.row.followup.title": "후속 조치 동작",
@@ -721,8 +723,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "이름으로 기술 로드", "settings.permissions.tool.skill.description": "이름으로 기술 로드",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "언어 서버 쿼리 실행", "settings.permissions.tool.lsp.description": "언어 서버 쿼리 실행",
"settings.permissions.tool.todowrite.title": "할 일 쓰기",
"settings.permissions.tool.todowrite.description": "할 일 목록 업데이트",
"settings.permissions.tool.webfetch.title": "웹 가져오기", "settings.permissions.tool.webfetch.title": "웹 가져오기",
"settings.permissions.tool.webfetch.description": "URL에서 콘텐츠 가져오기", "settings.permissions.tool.webfetch.description": "URL에서 콘텐츠 가져오기",
"settings.permissions.tool.websearch.title": "웹 검색", "settings.permissions.tool.websearch.title": "웹 검색",
@@ -785,7 +785,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "이 프로젝트의 변경 사항을 추적, 검토 및 실행 취소", "session.review.noVcs.createGit.description": "이 프로젝트의 변경 사항을 추적, 검토 및 실행 취소",
"session.review.noVcs.createGit.actionLoading": "Git 저장소 생성 중...", "session.review.noVcs.createGit.actionLoading": "Git 저장소 생성 중...",
"session.review.noVcs.createGit.action": "Git 저장소 생성", "session.review.noVcs.createGit.action": "Git 저장소 생성",
"session.todo.progress": "{{total}}개의 할 일 중 {{done}}개 완료",
"session.question.progress": "{{total}}개의 질문 중 {{current}}개", "session.question.progress": "{{total}}개의 질문 중 {{current}}개",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "파일 탐색기", "session.header.open.fileExplorer": "파일 탐색기",
@@ -848,4 +847,156 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패", "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
"error.childStore.storeCreateFailed": "저장소 생성 실패", "error.childStore.storeCreateFailed": "저장소 생성 실패",
"terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}",
"command.project.previous": "이전 프로젝트",
"command.project.next": "다음 프로젝트",
"command.project.index": "{{index}}번 프로젝트로 전환",
"command.tab.reopenClosed": "닫은 탭 다시 열기",
"model.tooltip.context.label": "컨텍스트",
"model.tooltip.inputs": "입력",
"model.tooltip.model": "모델",
"model.tooltip.provider": "공급자",
"model.tooltip.reasoning": "추론",
"common.clear": "지우기",
"session.child.promptDisabled": "하위 에이전트 세션에는 프롬프트를 입력할 수 없습니다.",
"session.child.backToParent": "메인 세션으로 돌아가기.",
"wsl.server.add": "WSL 서버 추가",
"wsl.server.addShort": "WSL 추가",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 서버",
"wsl.server.retryStart": "시작 재시도",
"wsl.server.updating": "업데이트 중...",
"wsl.onboarding.step.distro": "배포판 선택",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL 확인 중...",
"wsl.onboarding.restartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작해야 합니다.",
"wsl.onboarding.ready": "WSL이 준비되었습니다.",
"wsl.onboarding.required": "계속하려면 WSL이 필요합니다.",
"wsl.onboarding.checkingDistros": "배포판 확인 중...",
"wsl.onboarding.installingDistro": "{{distro}} 배포판 설치 중...",
"wsl.onboarding.checkingDistro": "{{distro}} 배포판 확인 중...",
"wsl.onboarding.listingDistros": "배포판 목록 확인 중...",
"wsl.onboarding.distroReady": "{{distro}} 배포판이 준비되었습니다.",
"wsl.onboarding.distroNotInstalled": "{{distro}} 배포판이 아직 설치되지 않았습니다.",
"wsl.onboarding.openDistroOnce": "설정을 완료하려면 {{distro}} 배포판을 한 번 여세요.",
"wsl.onboarding.finishingDistro": "{{distro}} 배포판 설정을 마무리하는 중입니다.",
"wsl.onboarding.pickDistro": "배포판을 선택하거나 아래에서 설치하세요.",
"wsl.onboarding.checkingOpencode": "OpenCode 확인 중...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}}에서 OpenCode 확인 중...",
"wsl.onboarding.updatingOpencode": "OpenCode 업데이트 중...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}}에서 OpenCode 업데이트 중...",
"wsl.onboarding.updateOpencodeIn": "{{distro}}에서 OpenCode를 업데이트하세요.",
"wsl.onboarding.updateOpencode": "OpenCode 업데이트",
"wsl.onboarding.opencodeReadyIn": "{{distro}}에서 OpenCode를 사용할 준비가 되었습니다.",
"wsl.onboarding.opencodeReady": "OpenCode가 준비되었습니다.",
"wsl.onboarding.installOpencodeIn": "{{distro}}에 OpenCode를 설치하세요.",
"wsl.onboarding.installOpencode": "OpenCode 설치",
"wsl.onboarding.chooseDistroFirst": "먼저 배포판을 선택하세요.",
"wsl.onboarding.loadFailed": "WSL 상태를 불러오지 못했습니다.",
"wsl.onboarding.loading": "로딩 중...",
"wsl.onboarding.installedDistros": "설치된 배포판",
"wsl.onboarding.checkAgain": "다시 확인",
"wsl.onboarding.distroStatus.ready": "준비됨",
"wsl.onboarding.distroStatus.checking": "확인 중...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode가 설치되지 않음",
"wsl.onboarding.distroStatus.missingTools": "bash, curl 없음",
"wsl.onboarding.distroStatus.unsupported": "지원되지 않음 · WSL 2 사용",
"wsl.onboarding.needAnotherDistro": "다른 배포판이 필요하신가요?",
"wsl.onboarding.needAnotherDistroHint": "WSL 카탈로그에서 Linux 배포판을 설치하세요",
"wsl.onboarding.wslNotInstalled.title": "WSL이 설치되지 않음",
"wsl.onboarding.wslNotInstalled.description":
"OpenCode에서 WSL 서버를 추가하려면 WSL (Windows Subsystem for Linux)이 필요합니다",
"wsl.onboarding.wslUnavailable.title": "WSL을 사용할 수 없음",
"wsl.onboarding.wslUnavailable.description": "OpenCode에서 이 컴퓨터의 WSL을 확인할 수 없습니다.",
"wsl.onboarding.installWsl": "WSL 설치",
"wsl.onboarding.windowsRestartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작한 다음 OpenCode를 다시 여세요.",
"wsl.onboarding.next": "다음",
"wsl.onboarding.refresh": "새로 고침",
"wsl.onboarding.allDistrosAdded": "설치된 모든 배포판이 이미 추가되었습니다.",
"wsl.onboarding.noDistros": "아직 감지된 배포판이 없습니다.",
"wsl.onboarding.install": "설치",
"wsl.onboarding.installing": "설치 중...",
"wsl.onboarding.installDistro": "배포판 설치",
"wsl.onboarding.searchDistros": "배포판 검색",
"wsl.onboarding.wsl2Required": "WSL 2가 필요합니다.",
"wsl.onboarding.toolsRequired": "이 배포판에는 bash와 curl이 필요합니다.",
"wsl.onboarding.openTerminal": "터미널 열기",
"wsl.onboarding.path": "경로: {{path}}",
"wsl.onboarding.notFound": "찾을 수 없음",
"wsl.onboarding.version": "버전: {{version}}",
"wsl.onboarding.unknown": "알 수 없음",
"wsl.onboarding.desktopVersion": "데스크톱 {{version}}",
"wsl.onboarding.versionMismatch": "설치된 버전이 데스크톱 앱 버전과 일치하지 않습니다.",
"wsl.onboarding.adding": "추가 중...",
"language.uk": "Українська",
"error.page.description.localServerStartup": "로컬 서버를 시작하는 동안 오류가 발생했습니다.",
"error.page.action.exportLogs": "로그 내보내기",
"home.title": "홈",
"home.projects": "프로젝트",
"home.project.add": "프로젝트 추가",
"home.recentlyClosed": "최근에 닫은 항목",
"home.server.collapse": "서버 프로젝트 접기",
"home.server.expand": "서버 프로젝트 펼치기",
"home.sessions.search.placeholder": "세션 검색",
"home.sessions.search.placeholder.scoped": "{{scope}}에서 세션 검색",
"home.sessions.search.sessions": "세션",
"home.sessions.search.noResults": "{{query}}에 해당하는 세션을 찾을 수 없습니다",
"home.sessions.empty": "아직 아무것도 없습니다",
"home.sessions.empty.description": "세션을 생성하여 시작하세요",
"home.sessions.group.today": "오늘",
"home.sessions.group.yesterday": "어제",
"home.sessions.group.older": "이전",
"home.providerTip": "75개 이상의 제공업체에 연결하여 Claude, GPT, Gemini 등의 다른 모델을 사용하세요",
"session.tab.unknown": "알 수 없는 세션",
"session.error.notFound": "이 세션을 찾을 수 없습니다",
"session.error.notFound.description": "이 탭은 이 서버에 더 이상 존재하지 않는 세션을 가리킵니다.",
"session.error.notFound.closeTab": "탭 닫기",
"session.error.serverConnection": "이 서버에 연결할 수 없습니다",
"session.review.noUncommittedChanges": "아직 커밋되지 않은 변경 사항이 없습니다",
"session.review.noBranchChanges": "아직 브랜치 변경 사항이 없습니다",
"session.question.minimize": "질문 최소화",
"session.question.restore": "질문 복원",
"session.question.pending.one": "{{count}}개의 대기 중인 질문",
"session.question.pending.other": "{{count}}개의 대기 중인 질문",
"session.new.project.new": "새 프로젝트",
"session.new.project.search": "프로젝트 검색",
"session.new.project.add": "프로젝트 추가",
"session.new.workspace.runIn": "세션 실행 위치",
"session.new.workspace.triggerLocal": "로컬",
"session.new.workspace.local": "로컬 저장소",
"session.new.workspace.existing": "작업 공간…",
"sidebar.empty.title": "열린 프로젝트 없음",
"sidebar.empty.description": "프로젝트를 열어 시작하세요",
"settings.general.section.advanced": "고급",
"settings.general.row.shell.title": "터미널 셸",
"settings.general.row.shell.description":
"터미널에서 사용할 셸을 선택하세요. 호환되는 셸은 에이전트 도구 호출에도 사용됩니다.",
"settings.general.row.shell.autoDefault": "자동 (기본값)",
"settings.general.row.shell.terminalOnly": "터미널 전용",
"settings.general.row.showFileTree.title": "파일 트리",
"settings.general.row.showFileTree.description": "세션에 파일 트리 패널 표시",
"settings.general.row.showNavigation.title": "탐색 컨트롤",
"settings.general.row.showNavigation.description": "데스크톱 제목 표시줄에 뒤로 및 앞으로 버튼 표시",
"settings.general.row.showSearch.title": "명령 팔레트",
"settings.general.row.showSearch.description": "제목 표시줄에 검색 및 명령 팔레트 버튼 표시",
"settings.general.row.showTerminal.title": "터미널",
"settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시",
"settings.general.row.showStatus.title": "서버 상태",
"settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시",
"settings.general.row.mobileTitlebarBottom.title": "하단 탐색",
"settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치",
"settings.general.row.showCustomAgents.title": "사용자 지정 에이전트",
"settings.general.row.showCustomAgents.description": "입력창에 에이전트 선택기 표시",
"settings.general.row.newLayoutDesigns.title": "새 레이아웃 및 디자인",
"settings.general.row.newLayoutDesigns.description": "새롭게 디자인된 레이아웃, 홈, 입력창 및 세션 UI 활성화",
"settings.general.row.pinchZoom.title": "핀치 줌",
"settings.general.row.pinchZoom.description": "트랙패드 핀치 및 Ctrl-스크롤 제스처로 확대/축소 허용",
"settings.updates.action.downloading": "다운로드 중...",
"settings.updates.action.installing": "설치 중...",
} }
+170 -10
View File
@@ -77,7 +77,7 @@ export const dict = {
"command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk", "command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
"command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser", "command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
"command.workspace.toggle": "Veksle arbeidsområder", "command.workspace.toggle": "Veksle arbeidsområder",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar", "command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet",
"command.session.undo": "Angre", "command.session.undo": "Angre",
"command.session.undo.description": "Angre siste melding", "command.session.undo.description": "Angre siste melding",
"command.session.redo": "Gjør om", "command.session.redo": "Gjør om",
@@ -277,6 +277,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst", "prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
"prompt.context.removeFile": "Fjern fil fra kontekst", "prompt.context.removeFile": "Fjern fil fra kontekst",
"prompt.action.attachFile": "Legg ved fil", "prompt.action.attachFile": "Legg ved fil",
"prompt.menu.addImagesAndFiles": "Legg til filer og mer",
"prompt.menu.imagesAndFiles": "Bilder og filer",
"prompt.menu.commands": "Kommandoer",
"prompt.menu.context": "Kontekst",
"prompt.menu.shellCommand": "Shell-kommando",
"prompt.attachment.remove": "Fjern vedlegg", "prompt.attachment.remove": "Fjern vedlegg",
"prompt.action.send": "Send", "prompt.action.send": "Send",
"prompt.action.stop": "Stopp", "prompt.action.stop": "Stopp",
@@ -469,7 +474,7 @@ export const dict = {
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?", "Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
"error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?", "error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
"directory.error.invalidUrl": "Invalid directory in URL.", "directory.error.invalidUrl": "Ugyldig mappe i URL-en.",
"error.chain.unknown": "Ukjent feil", "error.chain.unknown": "Ukjent feil",
"error.chain.causedBy": "Forårsaket av:", "error.chain.causedBy": "Forårsaket av:",
@@ -532,9 +537,6 @@ export const dict = {
"session.messages.jumpToLatest": "Hopp til nyeste", "session.messages.jumpToLatest": "Hopp til nyeste",
"session.context.addToContext": "Legg til {{selection}} i kontekst", "session.context.addToContext": "Legg til {{selection}} i kontekst",
"session.todo.title": "Oppgaver",
"session.todo.collapse": "Skjul",
"session.todo.expand": "Utvid",
"session.followupDock.summary.one": "{{count}} melding i kø", "session.followupDock.summary.one": "{{count}} melding i kø",
"session.followupDock.summary.other": "{{count}} meldinger i kø", "session.followupDock.summary.other": "{{count}} meldinger i kø",
"session.followupDock.sendNow": "Send nå", "session.followupDock.sendNow": "Send nå",
@@ -649,8 +651,8 @@ export const dict = {
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.", "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
"settings.general.row.font.title": "Kodefont", "settings.general.row.font.title": "Kodefont",
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker", "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminalskrift",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen",
"settings.general.row.uiFont.title": "UI-skrift", "settings.general.row.uiFont.title": "UI-skrift",
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet", "settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
"settings.general.row.followup.title": "Oppfølgingsadferd", "settings.general.row.followup.title": "Oppfølgingsadferd",
@@ -806,8 +808,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Last en ferdighet etter navn", "settings.permissions.tool.skill.description": "Last en ferdighet etter navn",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Kjør språkserverforespørsler", "settings.permissions.tool.lsp.description": "Kjør språkserverforespørsler",
"settings.permissions.tool.todowrite.title": "Skriv gjøremål",
"settings.permissions.tool.todowrite.description": "Oppdater gjøremålslisten",
"settings.permissions.tool.webfetch.title": "Webhenting", "settings.permissions.tool.webfetch.title": "Webhenting",
"settings.permissions.tool.webfetch.description": "Hent innhold fra en URL", "settings.permissions.tool.webfetch.description": "Hent innhold fra en URL",
"settings.permissions.tool.websearch.title": "Websøk", "settings.permissions.tool.websearch.title": "Websøk",
@@ -872,7 +872,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Spor, gjennomgå og angre endringer i dette prosjektet", "session.review.noVcs.createGit.description": "Spor, gjennomgå og angre endringer i dette prosjektet",
"session.review.noVcs.createGit.actionLoading": "Oppretter Git-depot...", "session.review.noVcs.createGit.actionLoading": "Oppretter Git-depot...",
"session.review.noVcs.createGit.action": "Opprett Git-depot", "session.review.noVcs.createGit.action": "Opprett Git-depot",
"session.todo.progress": "{{done}} av {{total}} oppgaver fullført",
"session.question.progress": "{{current}} av {{total}} spørsmål", "session.question.progress": "{{current}} av {{total}} spørsmål",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Filutforsker", "session.header.open.fileExplorer": "Filutforsker",
@@ -936,4 +935,165 @@ export const dict = {
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon", "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
"error.childStore.storeCreateFailed": "Kunne ikke opprette lager", "error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
"terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}", "terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}",
"command.project.previous": "Forrige prosjekt",
"command.project.next": "Neste prosjekt",
"command.project.index": "Bytt til prosjekt {{index}}",
"command.tab.reopenClosed": "Åpne lukket fane på nytt",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Inndata",
"model.tooltip.model": "Modell",
"model.tooltip.provider": "Leverandør",
"model.tooltip.reasoning": "Resonnering",
"common.clear": "Tøm",
"session.child.promptDisabled": "Underagentsesjoner kan ikke motta forespørsler.",
"session.child.backToParent": "Tilbake til hovedsesjonen.",
"wsl.server.add": "Legg til WSL-server",
"wsl.server.addShort": "Legg til WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL-server",
"wsl.server.retryStart": "Prøv å starte på nytt",
"wsl.server.updating": "Oppdaterer...",
"wsl.onboarding.step.distro": "Velg distribusjon",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Sjekker WSL...",
"wsl.onboarding.restartRequired": "Windows må startes på nytt for å fullføre installasjonen av WSL.",
"wsl.onboarding.ready": "WSL er klart.",
"wsl.onboarding.required": "WSL kreves for å fortsette.",
"wsl.onboarding.checkingDistros": "Sjekker distribusjoner...",
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
"wsl.onboarding.checkingDistro": "Sjekker {{distro}}...",
"wsl.onboarding.listingDistros": "Lister distribusjoner...",
"wsl.onboarding.distroReady": "{{distro}} er klar.",
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installert ennå.",
"wsl.onboarding.openDistroOnce": "Åpne {{distro}} én gang for å fullføre oppsettet.",
"wsl.onboarding.finishingDistro": "Fullfører oppsettet for {{distro}}.",
"wsl.onboarding.pickDistro": "Velg en distribusjon eller installer en nedenfor.",
"wsl.onboarding.checkingOpencode": "Sjekker OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Sjekker OpenCode i {{distro}}...",
"wsl.onboarding.updatingOpencode": "Oppdaterer OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Oppdaterer OpenCode i {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Oppdater OpenCode i {{distro}}.",
"wsl.onboarding.updateOpencode": "Oppdater OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
"wsl.onboarding.installOpencode": "Installer OpenCode",
"wsl.onboarding.chooseDistroFirst": "Velg en distribusjon først.",
"wsl.onboarding.loadFailed": "Kunne ikke laste inn WSL-status.",
"wsl.onboarding.loading": "Laster...",
"wsl.onboarding.installedDistros": "Installerte distribusjoner",
"wsl.onboarding.checkAgain": "Sjekk på nytt",
"wsl.onboarding.distroStatus.ready": "Klar",
"wsl.onboarding.distroStatus.checking": "Sjekker...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installert",
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
"wsl.onboarding.distroStatus.unsupported": "Ikke støttet · Bruk WSL 2",
"wsl.onboarding.needAnotherDistro": "Trenger du en annen distribusjon?",
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribusjon fra WSL-katalogen",
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installert",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Windows Subsystem for Linux) kreves før OpenCode kan legge til en WSL-server",
"wsl.onboarding.wslUnavailable.title": "WSL er utilgjengelig",
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekrefte WSL på denne maskinen.",
"wsl.onboarding.installWsl": "Installer WSL",
"wsl.onboarding.windowsRestartRequired":
"Start Windows på nytt for å fullføre installasjonen av WSL, og åpne deretter OpenCode på nytt.",
"wsl.onboarding.next": "Neste",
"wsl.onboarding.refresh": "Oppdater",
"wsl.onboarding.allDistrosAdded": "Alle installerte distribusjoner er allerede lagt til.",
"wsl.onboarding.noDistros": "Ingen distribusjoner oppdaget ennå.",
"wsl.onboarding.install": "Installer",
"wsl.onboarding.installing": "Installerer...",
"wsl.onboarding.installDistro": "Installer distribusjon",
"wsl.onboarding.searchDistros": "Søk etter distribusjoner",
"wsl.onboarding.wsl2Required": "WSL 2 er påkrevd.",
"wsl.onboarding.toolsRequired": "Denne distribusjonen trenger bash og curl.",
"wsl.onboarding.openTerminal": "Åpne terminal",
"wsl.onboarding.path": "Bane: {{path}}",
"wsl.onboarding.notFound": "ikke funnet",
"wsl.onboarding.version": "Versjon: {{version}}",
"wsl.onboarding.unknown": "ukjent",
"wsl.onboarding.desktopVersion": "skrivebordsapp {{version}}",
"wsl.onboarding.versionMismatch": "Den installerte versjonen samsvarer ikke med versjonen av skrivebordsappen.",
"wsl.onboarding.adding": "Legger til...",
"language.uk": "Українська",
"error.page.description.localServerStartup": "Det oppstod en feil under oppstart av den lokale serveren.",
"error.page.action.exportLogs": "Eksporter logger",
"home.title": "Hjem",
"home.projects": "Prosjekter",
"home.project.add": "Legg til prosjekt",
"home.recentlyClosed": "Nylig lukket",
"home.server.collapse": "Fold sammen serverprosjekter",
"home.server.expand": "Utvid serverprosjekter",
"home.sessions.search.placeholder": "Søk etter sesjoner",
"home.sessions.search.placeholder.scoped": "Søk etter sesjoner i {{scope}}",
"home.sessions.search.sessions": "Sesjoner",
"home.sessions.search.noResults": "Ingen sesjoner funnet for {{query}}",
"home.sessions.empty": "Ingenting her ennå",
"home.sessions.empty.description": "Opprett en sesjon for å komme i gang",
"home.sessions.group.today": "I dag",
"home.sessions.group.yesterday": "I går",
"home.sessions.group.older": "Eldre",
"home.providerTip":
"Koble til over 75 leverandører for å bruke andre modeller, inkludert Claude, GPT, Gemini og flere",
"session.tab.unknown": "Ukjent sesjon",
"session.error.notFound": "Denne sesjonen finnes ikke",
"session.error.notFound.description": "Denne fanen peker til en sesjon som ikke lenger finnes på denne serveren.",
"session.error.notFound.closeTab": "Lukk fane",
"session.error.serverConnection": "Kan ikke koble til denne serveren",
"session.review.noUncommittedChanges": "Ingen ucommittede endringer ennå",
"session.review.noBranchChanges": "Ingen grenendringer ennå",
"session.question.minimize": "Minimer spørsmål",
"session.question.restore": "Gjenopprett spørsmål",
"session.question.pending.one": "{{count}} ventende spørsmål",
"session.question.pending.other": "{{count}} ventende spørsmål",
"session.new.project.new": "Nytt prosjekt",
"session.new.project.search": "Søk etter prosjekter",
"session.new.project.add": "Legg til prosjekt",
"session.new.workspace.runIn": "Kjør sesjonen i",
"session.new.workspace.triggerLocal": "Lokalt",
"session.new.workspace.local": "Lokalt depot",
"session.new.workspace.existing": "Arbeidsområde…",
"sidebar.empty.title": "Ingen åpne prosjekter",
"sidebar.empty.description": "Åpne et prosjekt for å komme i gang",
"settings.general.section.advanced": "Avansert",
"settings.general.row.shell.title": "Terminalskall",
"settings.general.row.shell.description":
"Velg skallet som skal brukes i terminalen. Kompatible skall brukes også til agentens verktøykall.",
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
"settings.general.row.shell.terminalOnly": "kun terminal",
"settings.general.row.showFileTree.title": "Filtre",
"settings.general.row.showFileTree.description": "Vis filtrepanelet i sesjoner",
"settings.general.row.showNavigation.title": "Navigasjonskontroller",
"settings.general.row.showNavigation.description":
"Vis tilbake- og fremoverknappene på tittellinjen i skrivebordsappen",
"settings.general.row.showSearch.title": "Kommandopalett",
"settings.general.row.showSearch.description": "Vis knappen for søk og kommandopalett på tittellinjen",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen",
"settings.general.row.showStatus.title": "Serverstatus",
"settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen",
"settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst",
"settings.general.row.mobileTitlebarBottom.description":
"Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen",
"settings.general.row.showCustomAgents.title": "Egendefinerte agenter",
"settings.general.row.showCustomAgents.description": "Vis agentvelgeren i skrivefeltet",
"settings.general.row.newLayoutDesigns.title": "Ny layout og utforming",
"settings.general.row.newLayoutDesigns.description":
"Aktiver ny layout og utforming for startsiden, skrivefeltet og sesjonsgrensesnittet",
"settings.general.row.pinchZoom.title": "Knip for å zoome",
"settings.general.row.pinchZoom.description": "Tillat knipebevegelser på styreflaten og Ctrl-rulling for å zoome",
"settings.updates.action.downloading": "Laster ned...",
"settings.updates.action.installing": "Installerer...",
} satisfies Partial<Record<Keys, string>> } satisfies Partial<Record<Keys, string>>
+111 -26
View File
@@ -1,33 +1,118 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { dict as en } from "./en"
import { dict as ar } from "./ar"
import { dict as br } from "./br"
import { dict as bs } from "./bs"
import { dict as da } from "./da"
import { dict as de } from "./de"
import { dict as es } from "./es"
import { dict as fr } from "./fr"
import { dict as ja } from "./ja"
import { dict as ko } from "./ko"
import { dict as no } from "./no"
import { dict as pl } from "./pl"
import { dict as ru } from "./ru"
import { dict as uk } from "./uk"
import { dict as th } from "./th"
import { dict as zh } from "./zh"
import { dict as zht } from "./zht"
import { dict as tr } from "./tr"
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht] const appLocales = [
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const "ar",
"br",
"bs",
"da",
"de",
"es",
"fr",
"ja",
"ko",
"no",
"pl",
"ru",
"uk",
"th",
"tr",
"zh",
"zht",
] as const
const desktopLocales = appLocales.filter((locale) => locale !== "th" && locale !== "tr")
describe("i18n parity", () => { const domains = [
test("non-English locales translate targeted unseen session keys", () => { {
for (const locale of locales) { name: "app",
for (const key of keys) { source: "./en.ts",
expect(locale[key]).toBeDefined() target: (locale: string) => `./${locale}.ts`,
expect(locale[key]).not.toBe(en[key]) locales: appLocales,
},
{
name: "ui",
source: "../../../ui/src/i18n/en.ts",
target: (locale: string) => `../../../ui/src/i18n/${locale}.ts`,
locales: appLocales,
},
{
name: "desktop",
source: "../../../desktop/src/renderer/i18n/en.ts",
target: (locale: string) => `../../../desktop/src/renderer/i18n/${locale}.ts`,
locales: desktopLocales,
},
] as const
describe.skipIf(!!process.env.CI)("i18n parity", () => {
test("non-English locales have every English key", async () => {
for (const domain of domains) {
const source = await dictionary(domain.source)
for (const locale of domain.locales) {
const target = await dictionary(domain.target(locale))
const missing = Object.keys(source).filter((key) => !Object.hasOwn(target, key))
const extra = Object.keys(target).filter((key) => !Object.hasOwn(source, key))
expect({ domain: domain.name, locale, missing, extra }).toEqual({
domain: domain.name,
locale,
missing: [],
extra: [],
})
}
}
})
test("non-English locales preserve English placeholders", async () => {
for (const domain of domains) {
const source = await dictionary(domain.source)
for (const locale of domain.locales) {
const target = await dictionary(domain.target(locale))
const mismatched = Object.keys(source).filter(
(key) => Object.hasOwn(target, key) && placeholders(source[key]).join() !== placeholders(target[key]).join(),
)
expect({ domain: domain.name, locale, mismatched }).toEqual({ domain: domain.name, locale, mismatched: [] })
}
}
})
test("non-English locales translate targeted unseen session keys", async () => {
const source = await dictionary("./en.ts")
for (const locale of appLocales) {
const target = await dictionary(`./${locale}.ts`)
for (const key of ["command.session.previous.unseen", "command.session.next.unseen"]) {
expect(target[key]).toBeDefined()
expect(target[key]).not.toBe(source[key])
}
}
})
test("changed-file summary keys preserve rendered English copy and localize complete phrases", async () => {
const source = await dictionary("../../../ui/src/i18n/en.ts")
expect(source["ui.sessionTurn.diffs.changed.one"].replace("{{count}}", "1")).toBe("1 Changed file")
expect(source["ui.sessionTurn.diffs.changed.other"].replace("{{count}}", "2")).toBe("2 Changed files")
expect(source["ui.sessionTurn.diffs.changed"]).toBeUndefined()
for (const locale of appLocales) {
const target = await dictionary(`../../../ui/src/i18n/${locale}.ts`)
for (const key of ["ui.sessionTurn.diffs.changed.one", "ui.sessionTurn.diffs.changed.other"]) {
expect(target[key].trim()).not.toBe("")
expect(placeholders(target[key])).toEqual(["count"])
} }
} }
}) })
}) })
async function dictionary(file: string) {
const module: unknown = await import(file)
if (typeof module !== "object" || module === null || !("dict" in module) || !isDictionary(module.dict)) {
throw new Error(`Invalid translation dictionary: ${file}`)
}
return module.dict
}
function isDictionary(value: unknown): value is Record<string, string> {
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
return Object.values(value).every((item) => typeof item === "string")
}
function placeholders(value: string) {
return Array.from(value.matchAll(/{{\s*([^}]+?)\s*}}/g), (match) => match[1]).sort()
}
+164 -12
View File
@@ -21,6 +21,9 @@ export const dict = {
"theme.scheme.dark": "Ciemny", "theme.scheme.dark": "Ciemny",
"command.sidebar.toggle": "Przełącz pasek boczny", "command.sidebar.toggle": "Przełącz pasek boczny",
"command.project.open": "Otwórz projekt", "command.project.open": "Otwórz projekt",
"command.project.previous": "Poprzedni projekt",
"command.project.next": "Następny projekt",
"command.project.index": "Przełącz na projekt {{index}}",
"command.provider.connect": "Połącz dostawcę", "command.provider.connect": "Połącz dostawcę",
"command.server.switch": "Przełącz serwer", "command.server.switch": "Przełącz serwer",
"command.settings.open": "Otwórz ustawienia", "command.settings.open": "Otwórz ustawienia",
@@ -39,6 +42,7 @@ export const dict = {
"command.session.new": "Nowa sesja", "command.session.new": "Nowa sesja",
"command.file.open": "Otwórz plik", "command.file.open": "Otwórz plik",
"command.tab.close": "Zamknij kartę", "command.tab.close": "Zamknij kartę",
"command.tab.reopenClosed": "Otwórz ponownie zamkniętą kartę",
"command.context.addSelection": "Dodaj zaznaczenie do kontekstu", "command.context.addSelection": "Dodaj zaznaczenie do kontekstu",
"command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku", "command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku",
"command.input.focus": "Fokus na pole wejściowe", "command.input.focus": "Fokus na pole wejściowe",
@@ -194,6 +198,11 @@ export const dict = {
"model.input.video": "wideo", "model.input.video": "wideo",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Obsługuje: {{inputs}}", "model.tooltip.allows": "Obsługuje: {{inputs}}",
"model.tooltip.context.label": "Kontekst",
"model.tooltip.inputs": "Dane wejściowe",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Dostawca",
"model.tooltip.reasoning": "Wnioskowanie",
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie", "model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
"model.tooltip.reasoning.none": "Brak wnioskowania", "model.tooltip.reasoning.none": "Brak wnioskowania",
"model.tooltip.context": "Limit kontekstu {{limit}}", "model.tooltip.context": "Limit kontekstu {{limit}}",
@@ -203,6 +212,7 @@ export const dict = {
"common.loading": "Ładowanie", "common.loading": "Ładowanie",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Anuluj", "common.cancel": "Anuluj",
"common.clear": "Wyczyść",
"common.connect": "Połącz", "common.connect": "Połącz",
"common.disconnect": "Rozłącz", "common.disconnect": "Rozłącz",
"common.continue": "Prześlij", "common.continue": "Prześlij",
@@ -219,6 +229,8 @@ export const dict = {
"prompt.mode.shell": "Terminal", "prompt.mode.shell": "Terminal",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "esc aby wyjść", "prompt.mode.shell.exit": "esc aby wyjść",
"session.child.promptDisabled": "Nie można wysyłać promptów w sesjach podagentów.",
"session.child.backToParent": "Wróć do głównej sesji.",
"prompt.example.1": "Napraw TODO w bazie kodu", "prompt.example.1": "Napraw TODO w bazie kodu",
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?", "prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
"prompt.example.3": "Napraw zepsute testy", "prompt.example.3": "Napraw zepsute testy",
@@ -256,6 +268,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu", "prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu",
"prompt.context.removeFile": "Usuń plik z kontekstu", "prompt.context.removeFile": "Usuń plik z kontekstu",
"prompt.action.attachFile": "Załącz plik", "prompt.action.attachFile": "Załącz plik",
"prompt.menu.addImagesAndFiles": "Dodaj pliki i inne elementy",
"prompt.menu.imagesAndFiles": "Obrazy i pliki",
"prompt.menu.commands": "Polecenia",
"prompt.menu.context": "Kontekst",
"prompt.menu.shellCommand": "Polecenie powłoki",
"prompt.attachment.remove": "Usuń załącznik", "prompt.attachment.remove": "Usuń załącznik",
"prompt.action.send": "Wyślij", "prompt.action.send": "Wyślij",
"prompt.action.stop": "Zatrzymaj", "prompt.action.stop": "Zatrzymaj",
@@ -315,6 +332,75 @@ export const dict = {
"dialog.server.menu.delete": "Usuń", "dialog.server.menu.delete": "Usuń",
"dialog.server.current": "Obecny serwer", "dialog.server.current": "Obecny serwer",
"dialog.server.status.default": "Domyślny", "dialog.server.status.default": "Domyślny",
"wsl.server.add": "Dodaj serwer WSL",
"wsl.server.addShort": "Dodaj WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Serwer WSL",
"wsl.server.retryStart": "Ponów uruchamianie",
"wsl.server.updating": "Aktualizowanie...",
"wsl.onboarding.step.distro": "Wybierz dystrybucję",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Sprawdzanie WSL...",
"wsl.onboarding.restartRequired": "Aby dokończyć instalację WSL, uruchom ponownie system Windows.",
"wsl.onboarding.ready": "WSL jest gotowy.",
"wsl.onboarding.required": "Aby kontynuować, wymagany jest WSL.",
"wsl.onboarding.checkingDistros": "Sprawdzanie dystrybucji...",
"wsl.onboarding.installingDistro": "Instalowanie {{distro}}...",
"wsl.onboarding.checkingDistro": "Sprawdzanie {{distro}}...",
"wsl.onboarding.listingDistros": "Pobieranie listy dystrybucji...",
"wsl.onboarding.distroReady": "Dystrybucja {{distro}} jest gotowa.",
"wsl.onboarding.distroNotInstalled": "Dystrybucja {{distro}} nie jest jeszcze zainstalowana.",
"wsl.onboarding.openDistroOnce": "Uruchom raz {{distro}}, aby dokończyć konfigurację.",
"wsl.onboarding.finishingDistro": "Kończenie konfiguracji {{distro}}.",
"wsl.onboarding.pickDistro": "Wybierz dystrybucję lub zainstaluj jedną z poniższych.",
"wsl.onboarding.checkingOpencode": "Sprawdzanie OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Sprawdzanie OpenCode w dystrybucji {{distro}}...",
"wsl.onboarding.updatingOpencode": "Aktualizowanie OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Aktualizowanie OpenCode w dystrybucji {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Zaktualizuj OpenCode w dystrybucji {{distro}}.",
"wsl.onboarding.updateOpencode": "Zaktualizuj OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode jest gotowy w dystrybucji {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode jest gotowy.",
"wsl.onboarding.installOpencodeIn": "Zainstaluj OpenCode w dystrybucji {{distro}}.",
"wsl.onboarding.installOpencode": "Zainstaluj OpenCode",
"wsl.onboarding.chooseDistroFirst": "Najpierw wybierz dystrybucję.",
"wsl.onboarding.loadFailed": "Nie udało się załadować stanu WSL.",
"wsl.onboarding.loading": "Ładowanie...",
"wsl.onboarding.installedDistros": "Zainstalowane dystrybucje",
"wsl.onboarding.checkAgain": "Sprawdź ponownie",
"wsl.onboarding.distroStatus.ready": "Gotowa",
"wsl.onboarding.distroStatus.checking": "Sprawdzanie...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nie jest zainstalowany",
"wsl.onboarding.distroStatus.missingTools": "Brak bash i curl",
"wsl.onboarding.distroStatus.unsupported": "Nieobsługiwana · Użyj WSL 2",
"wsl.onboarding.needAnotherDistro": "Potrzebujesz innej dystrybucji?",
"wsl.onboarding.needAnotherDistroHint": "Zainstaluj dystrybucję systemu Linux z katalogu WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL nie jest zainstalowany",
"wsl.onboarding.wslNotInstalled.description":
"WSL (Podsystem Windows dla systemu Linux) jest wymagany, zanim OpenCode będzie mógł dodać serwer WSL",
"wsl.onboarding.wslUnavailable.title": "WSL jest niedostępny",
"wsl.onboarding.wslUnavailable.description": "OpenCode nie mógł zweryfikować WSL na tym komputerze.",
"wsl.onboarding.installWsl": "Zainstaluj WSL",
"wsl.onboarding.windowsRestartRequired":
"Uruchom ponownie system Windows, aby dokończyć instalację WSL, a następnie ponownie otwórz OpenCode.",
"wsl.onboarding.next": "Dalej",
"wsl.onboarding.refresh": "Odśwież",
"wsl.onboarding.allDistrosAdded": "Wszystkie zainstalowane dystrybucje zostały już dodane.",
"wsl.onboarding.noDistros": "Nie wykryto jeszcze żadnych dystrybucji.",
"wsl.onboarding.install": "Zainstaluj",
"wsl.onboarding.installing": "Instalowanie...",
"wsl.onboarding.installDistro": "Zainstaluj dystrybucję",
"wsl.onboarding.searchDistros": "Szukaj dystrybucji",
"wsl.onboarding.wsl2Required": "Wymagany jest WSL 2.",
"wsl.onboarding.toolsRequired": "Ta dystrybucja wymaga bash i curl.",
"wsl.onboarding.openTerminal": "Otwórz terminal",
"wsl.onboarding.path": "Ścieżka: {{path}}",
"wsl.onboarding.notFound": "nie znaleziono",
"wsl.onboarding.version": "Wersja: {{version}}",
"wsl.onboarding.unknown": "nieznana",
"wsl.onboarding.desktopVersion": "aplikacja komputerowa {{version}}",
"wsl.onboarding.versionMismatch": "Zainstalowana wersja nie jest zgodna z wersją aplikacji komputerowej.",
"wsl.onboarding.adding": "Dodawanie...",
"dialog.project.edit.title": "Edytuj projekt", "dialog.project.edit.title": "Edytuj projekt",
"dialog.project.edit.name": "Nazwa", "dialog.project.edit.name": "Nazwa",
"dialog.project.edit.icon": "Ikona", "dialog.project.edit.icon": "Ikona",
@@ -324,7 +410,8 @@ export const dict = {
"dialog.project.edit.color": "Kolor", "dialog.project.edit.color": "Kolor",
"dialog.project.edit.color.select": "Wybierz kolor {{color}}", "dialog.project.edit.color.select": "Wybierz kolor {{color}}",
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej", "dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).", "dialog.project.edit.worktree.startup.description":
"Uruchamiany po utworzeniu nowej przestrzeni roboczej (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "np. bun install", "dialog.project.edit.worktree.startup.placeholder": "np. bun install",
"context.breakdown.title": "Podział kontekstu", "context.breakdown.title": "Podział kontekstu",
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.', "context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
@@ -371,6 +458,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
"toast.language.title": "Język", "toast.language.title": "Język",
@@ -407,10 +495,12 @@ export const dict = {
"toast.update.action.notYet": "Jeszcze nie", "toast.update.action.notYet": "Jeszcze nie",
"error.page.title": "Coś poszło nie tak", "error.page.title": "Coś poszło nie tak",
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.", "error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
"error.page.description.localServerStartup": "Wystąpił błąd podczas uruchamiania serwera lokalnego.",
"error.page.details.label": "Szczegóły błędu", "error.page.details.label": "Szczegóły błędu",
"error.page.action.restart": "Restartuj", "error.page.action.restart": "Restartuj",
"error.page.action.report": "Zgłoś błąd", "error.page.action.report": "Zgłoś błąd",
"error.page.action.reported": "Błąd zgłoszony", "error.page.action.reported": "Błąd zgłoszony",
"error.page.action.exportLogs": "Eksportuj dzienniki",
"error.page.action.checking": "Sprawdzanie...", "error.page.action.checking": "Sprawdzanie...",
"error.page.action.checkUpdates": "Sprawdź aktualizacje", "error.page.action.checkUpdates": "Sprawdź aktualizacje",
"error.page.action.updateTo": "Zaktualizuj do {{version}}", "error.page.action.updateTo": "Zaktualizuj do {{version}}",
@@ -430,7 +520,8 @@ export const dict = {
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}", "error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}", "error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli", "error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.', "error.chain.mcpFailed":
"Serwer MCP „{{name}}” nie uruchomił się. Uwaga: OpenCode nie obsługuje jeszcze uwierzytelniania MCP.",
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}", "error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
"error.chain.providerInitFailed": "error.chain.providerInitFailed":
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.', 'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
@@ -452,10 +543,32 @@ export const dict = {
"home.recentProjects": "Ostatnie projekty", "home.recentProjects": "Ostatnie projekty",
"home.empty.title": "Brak ostatnich projektów", "home.empty.title": "Brak ostatnich projektów",
"home.empty.description": "Zacznij od otwarcia lokalnego projektu", "home.empty.description": "Zacznij od otwarcia lokalnego projektu",
"home.title": "Strona główna",
"home.projects": "Projekty",
"home.project.add": "Dodaj projekt",
"home.recentlyClosed": "Ostatnio zamknięte",
"home.server.collapse": "Zwiń projekty serwera",
"home.server.expand": "Rozwiń projekty serwera",
"home.sessions.search.placeholder": "Szukaj sesji",
"home.sessions.search.placeholder.scoped": "Szukaj sesji w {{scope}}",
"home.sessions.search.sessions": "Sesje",
"home.sessions.search.noResults": "Nie znaleziono sesji dla {{query}}",
"home.sessions.empty": "Jeszcze nic tu nie ma",
"home.sessions.empty.description": "Utwórz sesję, aby rozpocząć",
"home.sessions.group.today": "Dzisiaj",
"home.sessions.group.yesterday": "Wczoraj",
"home.sessions.group.older": "Starsze",
"home.providerTip":
"Połącz się z ponad 75 dostawcami, aby korzystać z innych modeli, w tym Claude, GPT, Gemini i innych",
"session.tab.session": "Sesja", "session.tab.session": "Sesja",
"session.tab.review": "Przegląd", "session.tab.review": "Przegląd",
"session.tab.context": "Kontekst", "session.tab.context": "Kontekst",
"session.tab.unknown": "Nieznana sesja",
"session.panel.reviewAndFiles": "Przegląd i pliki", "session.panel.reviewAndFiles": "Przegląd i pliki",
"session.error.notFound": "Nie można znaleźć tej sesji",
"session.error.notFound.description": "Ta karta wskazuje sesję, która nie istnieje już na tym serwerze.",
"session.error.notFound.closeTab": "Zamknij kartę",
"session.error.serverConnection": "Nie można połączyć się z tym serwerem",
"session.review.filesChanged": "Zmieniono {{count}} plików", "session.review.filesChanged": "Zmieniono {{count}} plików",
"session.review.change.one": "Zmiana", "session.review.change.one": "Zmiana",
"session.review.change.other": "Zmiany", "session.review.change.other": "Zmiany",
@@ -464,6 +577,8 @@ export const dict = {
"session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane", "session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane",
"session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne", "session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
"session.review.noChanges": "Brak zmian", "session.review.noChanges": "Brak zmian",
"session.review.noUncommittedChanges": "Brak jeszcze niezatwierdzonych zmian",
"session.review.noBranchChanges": "Brak jeszcze zmian w gałęzi",
"session.files.selectToOpen": "Wybierz plik do otwarcia", "session.files.selectToOpen": "Wybierz plik do otwarcia",
"session.files.all": "Wszystkie pliki", "session.files.all": "Wszystkie pliki",
"session.files.empty": "Brak plików", "session.files.empty": "Brak plików",
@@ -474,9 +589,10 @@ export const dict = {
"session.messages.loading": "Ładowanie wiadomości...", "session.messages.loading": "Ładowanie wiadomości...",
"session.messages.jumpToLatest": "Przejdź do najnowszych", "session.messages.jumpToLatest": "Przejdź do najnowszych",
"session.context.addToContext": "Dodaj {{selection}} do kontekstu", "session.context.addToContext": "Dodaj {{selection}} do kontekstu",
"session.todo.title": "Zadania", "session.question.minimize": "Zminimalizuj pytanie",
"session.todo.collapse": "Zwiń", "session.question.restore": "Przywróć pytanie",
"session.todo.expand": "Rozwiń", "session.question.pending.one": "{{count}} oczekujące pytanie",
"session.question.pending.other": "Pytania oczekujące: {{count}}",
"session.followupDock.summary.one": "{{count}} wiadomość w kolejce", "session.followupDock.summary.one": "{{count}} wiadomość w kolejce",
"session.followupDock.summary.other": "{{count}} wiadomości w kolejce", "session.followupDock.summary.other": "{{count}} wiadomości w kolejce",
"session.followupDock.sendNow": "Wyślij teraz", "session.followupDock.sendNow": "Wyślij teraz",
@@ -489,9 +605,16 @@ export const dict = {
"session.revertDock.expand": "Rozwiń cofnięte wiadomości", "session.revertDock.expand": "Rozwiń cofnięte wiadomości",
"session.revertDock.restore": "Przywróć wiadomość", "session.revertDock.restore": "Przywróć wiadomość",
"session.new.title": "Zbuduj cokolwiek", "session.new.title": "Zbuduj cokolwiek",
"session.new.project.new": "Nowy projekt",
"session.new.project.search": "Szukaj projektów",
"session.new.project.add": "Dodaj projekt",
"session.new.worktree.main": "Główna gałąź", "session.new.worktree.main": "Główna gałąź",
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})", "session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
"session.new.worktree.create": "Utwórz nowe drzewo robocze", "session.new.worktree.create": "Utwórz nowe drzewo robocze",
"session.new.workspace.runIn": "Uruchom sesję w",
"session.new.workspace.triggerLocal": "Lokalnie",
"session.new.workspace.local": "Lokalne repozytorium",
"session.new.workspace.existing": "Przestrzeń robocza…",
"session.new.lastModified": "Ostatnio zmodyfikowano", "session.new.lastModified": "Ostatnio zmodyfikowano",
"session.header.search.placeholder": "Szukaj {{project}}", "session.header.search.placeholder": "Szukaj {{project}}",
"session.header.searchFiles": "Szukaj plików", "session.header.searchFiles": "Szukaj plików",
@@ -555,15 +678,18 @@ export const dict = {
"sidebar.project.recentSessions": "Ostatnie sesje", "sidebar.project.recentSessions": "Ostatnie sesje",
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje", "sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
"sidebar.project.clearNotifications": "Wyczyść powiadomienia", "sidebar.project.clearNotifications": "Wyczyść powiadomienia",
"sidebar.empty.title": "Brak otwartych projektów",
"sidebar.empty.description": "Otwórz projekt, aby rozpocząć",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Pulpit", "settings.section.desktop": "Pulpit",
"settings.section.server": "Serwer", "settings.section.server": "Serwer",
"settings.tab.general": "Ogólne", "settings.tab.general": "Ogólne",
"settings.tab.shortcuts": "Skróty", "settings.tab.shortcuts": "Skróty",
"settings.desktop.section.wsl": "WSL", "settings.desktop.section.wsl": "WSL",
"settings.desktop.wsl.title": "WSL integration", "settings.desktop.wsl.title": "Integracja z WSL",
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", "settings.desktop.wsl.description": "Uruchamiaj serwer OpenCode wewnątrz WSL w systemie Windows.",
"settings.general.section.appearance": "Wygląd", "settings.general.section.appearance": "Wygląd",
"settings.general.section.advanced": "Zaawansowane",
"settings.general.section.notifications": "Powiadomienia systemowe", "settings.general.section.notifications": "Powiadomienia systemowe",
"settings.general.section.updates": "Aktualizacje", "settings.general.section.updates": "Aktualizacje",
"settings.general.section.sounds": "Efekty dźwiękowe", "settings.general.section.sounds": "Efekty dźwiękowe",
@@ -571,6 +697,11 @@ export const dict = {
"settings.general.section.display": "Ekran", "settings.general.section.display": "Ekran",
"settings.general.row.language.title": "Język", "settings.general.row.language.title": "Język",
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode", "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
"settings.general.row.shell.title": "Powłoka terminala",
"settings.general.row.shell.description":
"Wybierz powłokę używaną w terminalu. Zgodne powłoki są również używane przez wywołania narzędzi agenta.",
"settings.general.row.shell.autoDefault": "Automatycznie (domyślna)",
"settings.general.row.shell.terminalOnly": "tylko terminal",
"settings.general.row.appearance.title": "Wygląd", "settings.general.row.appearance.title": "Wygląd",
"settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu", "settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
"settings.general.row.colorScheme.title": "Schemat kolorów", "settings.general.row.colorScheme.title": "Schemat kolorów",
@@ -580,14 +711,30 @@ export const dict = {
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.", "settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
"settings.general.row.font.title": "Czcionka kodu", "settings.general.row.font.title": "Czcionka kodu",
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu", "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Czcionka terminala",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu",
"settings.general.row.uiFont.title": "Czcionka interfejsu", "settings.general.row.uiFont.title": "Czcionka interfejsu",
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie", "settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
"settings.general.row.followup.title": "Zachowanie kontynuacji", "settings.general.row.followup.title": "Zachowanie kontynuacji",
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce", "settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
"settings.general.row.followup.option.queue": "Kolejka", "settings.general.row.followup.option.queue": "Kolejka",
"settings.general.row.followup.option.steer": "Sterowanie", "settings.general.row.followup.option.steer": "Sterowanie",
"settings.general.row.showFileTree.title": "Drzewo plików",
"settings.general.row.showFileTree.description": "Pokaż panel drzewa plików w sesjach",
"settings.general.row.showNavigation.title": "Elementy nawigacji",
"settings.general.row.showNavigation.description":
"Pokaż przyciski Wstecz i Dalej na pasku tytułu aplikacji komputerowej",
"settings.general.row.showSearch.title": "Paleta poleceń",
"settings.general.row.showSearch.description": "Pokaż przycisk wyszukiwania i palety poleceń na pasku tytułu",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej",
"settings.general.row.showStatus.title": "Status serwera",
"settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu",
"settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja",
"settings.general.row.mobileTitlebarBottom.description":
"Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych",
"settings.general.row.showCustomAgents.title": "Agenci niestandardowi",
"settings.general.row.showCustomAgents.description": "Pokaż wybór agenta w edytorze wiadomości",
"settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania", "settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania",
"settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu", "settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell", "settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
@@ -596,6 +743,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit", "settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu", "Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
"settings.general.row.newLayoutDesigns.title": "Nowy układ i wygląd",
"settings.general.row.newLayoutDesigns.description":
"Włącz przeprojektowany układ, stronę główną, edytor wiadomości i interfejs sesji",
"settings.general.row.pinchZoom.title": "Powiększanie gestem szczypania",
"settings.general.row.pinchZoom.description":
"Zezwalaj na powiększanie gestem szczypania na gładziku i przewijaniem z klawiszem Ctrl",
"settings.general.row.wayland.title": "Użyj natywnego Wayland", "settings.general.row.wayland.title": "Użyj natywnego Wayland",
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.", "settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -608,6 +761,8 @@ export const dict = {
"settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne", "settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne",
"settings.updates.action.checkNow": "Sprawdź teraz", "settings.updates.action.checkNow": "Sprawdź teraz",
"settings.updates.action.checking": "Sprawdzanie...", "settings.updates.action.checking": "Sprawdzanie...",
"settings.updates.action.downloading": "Pobieranie...",
"settings.updates.action.installing": "Instalowanie...",
"settings.updates.toast.latest.title": "Masz najnowszą wersję", "settings.updates.toast.latest.title": "Masz najnowszą wersję",
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.", "settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
"sound.option.none": "Brak", "sound.option.none": "Brak",
@@ -728,8 +883,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Ładowanie umiejętności według nazwy", "settings.permissions.tool.skill.description": "Ładowanie umiejętności według nazwy",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Uruchamianie zapytań serwera językowego", "settings.permissions.tool.lsp.description": "Uruchamianie zapytań serwera językowego",
"settings.permissions.tool.todowrite.title": "Zapis Todo",
"settings.permissions.tool.todowrite.description": "Aktualizacja listy zadań",
"settings.permissions.tool.webfetch.title": "Pobieranie z sieci", "settings.permissions.tool.webfetch.title": "Pobieranie z sieci",
"settings.permissions.tool.webfetch.description": "Pobieranie zawartości z adresu URL", "settings.permissions.tool.webfetch.description": "Pobieranie zawartości z adresu URL",
"settings.permissions.tool.websearch.title": "Wyszukiwanie w sieci", "settings.permissions.tool.websearch.title": "Wyszukiwanie w sieci",
@@ -792,7 +945,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Śledź, przeglądaj i cofaj zmiany w tym projekcie", "session.review.noVcs.createGit.description": "Śledź, przeglądaj i cofaj zmiany w tym projekcie",
"session.review.noVcs.createGit.actionLoading": "Tworzenie repozytorium Git...", "session.review.noVcs.createGit.actionLoading": "Tworzenie repozytorium Git...",
"session.review.noVcs.createGit.action": "Utwórz repozytorium Git", "session.review.noVcs.createGit.action": "Utwórz repozytorium Git",
"session.todo.progress": "Ukończono {{done}} z {{total}} zadań",
"session.question.progress": "{{current}} z {{total}} pytań", "session.question.progress": "{{current}} z {{total}} pytań",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Eksplorator plików", "session.header.open.fileExplorer": "Eksplorator plików",
+158 -8
View File
@@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "Переключить боковую панель", "command.sidebar.toggle": "Переключить боковую панель",
"command.project.open": "Открыть проект", "command.project.open": "Открыть проект",
"command.project.previous": "Предыдущий проект",
"command.project.next": "Следующий проект",
"command.project.index": "Переключиться на проект {{index}}",
"command.provider.connect": "Подключить провайдера", "command.provider.connect": "Подключить провайдера",
"command.server.switch": "Переключить сервер", "command.server.switch": "Переключить сервер",
"command.settings.open": "Открыть настройки", "command.settings.open": "Открыть настройки",
@@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "Новая сессия", "command.session.new": "Новая сессия",
"command.file.open": "Открыть файл", "command.file.open": "Открыть файл",
"command.tab.close": "Закрыть вкладку", "command.tab.close": "Закрыть вкладку",
"command.tab.reopenClosed": "Повторно открыть закрытую вкладку",
"command.context.addSelection": "Добавить выделение в контекст", "command.context.addSelection": "Добавить выделение в контекст",
"command.context.addSelection.description": "Добавить выбранные строки из текущего файла", "command.context.addSelection.description": "Добавить выбранные строки из текущего файла",
"command.input.focus": "Фокус на поле ввода", "command.input.focus": "Фокус на поле ввода",
@@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "видео", "model.input.video": "видео",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Разрешено: {{inputs}}", "model.tooltip.allows": "Разрешено: {{inputs}}",
"model.tooltip.context.label": "Контекст",
"model.tooltip.inputs": "Входные данные",
"model.tooltip.model": "Модель",
"model.tooltip.provider": "Провайдер",
"model.tooltip.reasoning": "Рассуждение",
"model.tooltip.reasoning.allowed": "Разрешает рассуждение", "model.tooltip.reasoning.allowed": "Разрешает рассуждение",
"model.tooltip.reasoning.none": "Без рассуждения", "model.tooltip.reasoning.none": "Без рассуждения",
"model.tooltip.context": "Лимит контекста {{limit}}", "model.tooltip.context": "Лимит контекста {{limit}}",
@@ -218,6 +227,7 @@ export const dict = {
"common.loading": "Загрузка", "common.loading": "Загрузка",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Отмена", "common.cancel": "Отмена",
"common.clear": "Очистить",
"common.connect": "Подключить", "common.connect": "Подключить",
"common.disconnect": "Отключить", "common.disconnect": "Отключить",
"common.continue": "Отправить", "common.continue": "Отправить",
@@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Оболочка", "prompt.mode.shell": "Оболочка",
"prompt.mode.normal": "Промпт", "prompt.mode.normal": "Промпт",
"prompt.mode.shell.exit": "esc для выхода", "prompt.mode.shell.exit": "esc для выхода",
"session.child.promptDisabled": "В сессиях субагентов нельзя отправлять запросы.",
"session.child.backToParent": "Вернуться к основной сессии.",
"prompt.example.1": "Исправить TODO в коде", "prompt.example.1": "Исправить TODO в коде",
"prompt.example.2": "Какой технологический стек этого проекта?", "prompt.example.2": "Какой технологический стек этого проекта?",
@@ -274,6 +286,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Удалить активный файл из контекста", "prompt.context.removeActiveFile": "Удалить активный файл из контекста",
"prompt.context.removeFile": "Удалить файл из контекста", "prompt.context.removeFile": "Удалить файл из контекста",
"prompt.action.attachFile": "Прикрепить файл", "prompt.action.attachFile": "Прикрепить файл",
"prompt.menu.addImagesAndFiles": "Добавить файлы и другое",
"prompt.menu.imagesAndFiles": "Изображения и файлы",
"prompt.menu.commands": "Команды",
"prompt.menu.context": "Контекст",
"prompt.menu.shellCommand": "Команда оболочки",
"prompt.attachment.remove": "Удалить вложение", "prompt.attachment.remove": "Удалить вложение",
"prompt.action.send": "Отправить", "prompt.action.send": "Отправить",
"prompt.action.stop": "Остановить", "prompt.action.stop": "Остановить",
@@ -341,6 +358,75 @@ export const dict = {
"dialog.server.menu.delete": "Удалить", "dialog.server.menu.delete": "Удалить",
"dialog.server.current": "Текущий сервер", "dialog.server.current": "Текущий сервер",
"dialog.server.status.default": "По умолч.", "dialog.server.status.default": "По умолч.",
"wsl.server.add": "Добавить сервер WSL",
"wsl.server.addShort": "Добавить WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Сервер WSL",
"wsl.server.retryStart": "Повторить запуск",
"wsl.server.updating": "Обновление...",
"wsl.onboarding.step.distro": "Выбрать дистрибутив",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Проверка WSL...",
"wsl.onboarding.restartRequired": "Чтобы завершить установку WSL, перезапустите Windows.",
"wsl.onboarding.ready": "WSL готова к работе.",
"wsl.onboarding.required": "Для продолжения требуется WSL.",
"wsl.onboarding.checkingDistros": "Проверка дистрибутивов...",
"wsl.onboarding.installingDistro": "Установка {{distro}}...",
"wsl.onboarding.checkingDistro": "Проверка {{distro}}...",
"wsl.onboarding.listingDistros": "Получение списка дистрибутивов...",
"wsl.onboarding.distroReady": "Дистрибутив {{distro}} готов к работе.",
"wsl.onboarding.distroNotInstalled": "Дистрибутив {{distro}} пока не установлен.",
"wsl.onboarding.openDistroOnce": "Откройте {{distro}} один раз, чтобы завершить настройку.",
"wsl.onboarding.finishingDistro": "Завершение настройки {{distro}}.",
"wsl.onboarding.pickDistro": "Выберите дистрибутив или установите его ниже.",
"wsl.onboarding.checkingOpencode": "Проверка OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Проверка OpenCode в {{distro}}...",
"wsl.onboarding.updatingOpencode": "Обновление OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Обновление OpenCode в {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Обновите OpenCode в {{distro}}.",
"wsl.onboarding.updateOpencode": "Обновить OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode в {{distro}} готов к работе.",
"wsl.onboarding.opencodeReady": "OpenCode готов к работе.",
"wsl.onboarding.installOpencodeIn": "Установите OpenCode в {{distro}}.",
"wsl.onboarding.installOpencode": "Установить OpenCode",
"wsl.onboarding.chooseDistroFirst": "Сначала выберите дистрибутив.",
"wsl.onboarding.loadFailed": "Не удалось загрузить состояние WSL.",
"wsl.onboarding.loading": "Загрузка...",
"wsl.onboarding.installedDistros": "Установленные дистрибутивы",
"wsl.onboarding.checkAgain": "Проверить снова",
"wsl.onboarding.distroStatus.ready": "Готов",
"wsl.onboarding.distroStatus.checking": "Проверка...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не установлен",
"wsl.onboarding.distroStatus.missingTools": "Отсутствуют bash и curl",
"wsl.onboarding.distroStatus.unsupported": "Не поддерживается · Используйте WSL 2",
"wsl.onboarding.needAnotherDistro": "Нужен другой дистрибутив?",
"wsl.onboarding.needAnotherDistroHint": "Установите дистрибутив Linux из каталога WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL не установлена",
"wsl.onboarding.wslNotInstalled.description":
"Для добавления сервера WSL в OpenCode требуется WSL (Windows Subsystem for Linux)",
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
"wsl.onboarding.wslUnavailable.description": "OpenCode не смог проверить WSL на этом компьютере.",
"wsl.onboarding.installWsl": "Установить WSL",
"wsl.onboarding.windowsRestartRequired":
"Перезапустите Windows, чтобы завершить установку WSL, затем снова откройте OpenCode.",
"wsl.onboarding.next": "Далее",
"wsl.onboarding.refresh": "Обновить",
"wsl.onboarding.allDistrosAdded": "Все установленные дистрибутивы уже добавлены.",
"wsl.onboarding.noDistros": "Дистрибутивы пока не обнаружены.",
"wsl.onboarding.install": "Установить",
"wsl.onboarding.installing": "Установка...",
"wsl.onboarding.installDistro": "Установить дистрибутив",
"wsl.onboarding.searchDistros": "Поиск дистрибутивов",
"wsl.onboarding.wsl2Required": "Требуется WSL 2.",
"wsl.onboarding.toolsRequired": "Для этого дистрибутива нужны bash и curl.",
"wsl.onboarding.openTerminal": "Открыть терминал",
"wsl.onboarding.path": "Путь: {{path}}",
"wsl.onboarding.notFound": "не найден",
"wsl.onboarding.version": "Версия: {{version}}",
"wsl.onboarding.unknown": "неизвестно",
"wsl.onboarding.desktopVersion": "приложение {{version}}",
"wsl.onboarding.versionMismatch": "Установленная версия не соответствует версии приложения.",
"wsl.onboarding.adding": "Добавление...",
"dialog.project.edit.title": "Редактировать проект", "dialog.project.edit.title": "Редактировать проект",
"dialog.project.edit.name": "Название", "dialog.project.edit.name": "Название",
@@ -405,6 +491,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -452,10 +539,12 @@ export const dict = {
"error.page.title": "Что-то пошло не так", "error.page.title": "Что-то пошло не так",
"error.page.description": "Произошла ошибка при загрузке приложения.", "error.page.description": "Произошла ошибка при загрузке приложения.",
"error.page.description.localServerStartup": "Произошла ошибка при запуске локального сервера.",
"error.page.details.label": "Детали ошибки", "error.page.details.label": "Детали ошибки",
"error.page.action.restart": "Перезапустить", "error.page.action.restart": "Перезапустить",
"error.page.action.report": "Сообщить об ошибке", "error.page.action.report": "Сообщить об ошибке",
"error.page.action.reported": "Об ошибке сообщено", "error.page.action.reported": "Об ошибке сообщено",
"error.page.action.exportLogs": "Экспортировать журналы",
"error.page.action.checking": "Проверка...", "error.page.action.checking": "Проверка...",
"error.page.action.checkUpdates": "Проверить обновления", "error.page.action.checkUpdates": "Проверить обновления",
"error.page.action.updateTo": "Обновить до {{version}}", "error.page.action.updateTo": "Обновить до {{version}}",
@@ -505,11 +594,33 @@ export const dict = {
"home.recentProjects": "Недавние проекты", "home.recentProjects": "Недавние проекты",
"home.empty.title": "Нет недавних проектов", "home.empty.title": "Нет недавних проектов",
"home.empty.description": "Начните с открытия локального проекта", "home.empty.description": "Начните с открытия локального проекта",
"home.title": "Главная",
"home.projects": "Проекты",
"home.project.add": "Добавить проект",
"home.recentlyClosed": "Недавно закрытые",
"home.server.collapse": "Свернуть проекты сервера",
"home.server.expand": "Развернуть проекты сервера",
"home.sessions.search.placeholder": "Поиск сессий",
"home.sessions.search.placeholder.scoped": "Поиск сессий в {{scope}}",
"home.sessions.search.sessions": "Сессии",
"home.sessions.search.noResults": "Сессии по запросу {{query}} не найдены",
"home.sessions.empty": "Здесь пока ничего нет",
"home.sessions.empty.description": "Создайте сессию, чтобы начать",
"home.sessions.group.today": "Сегодня",
"home.sessions.group.yesterday": "Вчера",
"home.sessions.group.older": "Ранее",
"home.providerTip":
"Подключитесь к более чем 75 провайдерам, чтобы использовать другие модели, включая Claude, GPT, Gemini и другие",
"session.tab.session": "Сессия", "session.tab.session": "Сессия",
"session.tab.review": "Обзор", "session.tab.review": "Обзор",
"session.tab.context": "Контекст", "session.tab.context": "Контекст",
"session.tab.unknown": "Неизвестная сессия",
"session.panel.reviewAndFiles": "Обзор и файлы", "session.panel.reviewAndFiles": "Обзор и файлы",
"session.error.notFound": "Эта сессия не найдена",
"session.error.notFound.description": "Эта вкладка указывает на сессию, которой больше нет на этом сервере.",
"session.error.notFound.closeTab": "Закрыть вкладку",
"session.error.serverConnection": "Не удаётся подключиться к этому серверу",
"session.review.filesChanged": "{{count}} файлов изменено", "session.review.filesChanged": "{{count}} файлов изменено",
"session.review.change.one": "Изменение", "session.review.change.one": "Изменение",
"session.review.change.other": "Изменения", "session.review.change.other": "Изменения",
@@ -518,6 +629,8 @@ export const dict = {
"session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются", "session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
"session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны", "session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
"session.review.noChanges": "Нет изменений", "session.review.noChanges": "Нет изменений",
"session.review.noUncommittedChanges": "Пока нет незафиксированных изменений",
"session.review.noBranchChanges": "Пока нет изменений в ветке",
"session.files.selectToOpen": "Выберите файл, чтобы открыть", "session.files.selectToOpen": "Выберите файл, чтобы открыть",
"session.files.all": "Все файлы", "session.files.all": "Все файлы",
"session.files.empty": "Нет файлов", "session.files.empty": "Нет файлов",
@@ -529,9 +642,10 @@ export const dict = {
"session.messages.jumpToLatest": "Перейти к последнему", "session.messages.jumpToLatest": "Перейти к последнему",
"session.context.addToContext": "Добавить {{selection}} в контекст", "session.context.addToContext": "Добавить {{selection}} в контекст",
"session.todo.title": "Задачи", "session.question.minimize": "Свернуть вопрос",
"session.todo.collapse": "Свернуть", "session.question.restore": "Восстановить вопрос",
"session.todo.expand": "Развернуть", "session.question.pending.one": "{{count}} вопрос без ответа",
"session.question.pending.other": "Вопросы без ответа: {{count}}",
"session.followupDock.summary.one": "{{count}} сообщение в очереди", "session.followupDock.summary.one": "{{count}} сообщение в очереди",
"session.followupDock.summary.other": "{{count}} сообщений в очереди", "session.followupDock.summary.other": "{{count}} сообщений в очереди",
"session.followupDock.sendNow": "Отправить сейчас", "session.followupDock.sendNow": "Отправить сейчас",
@@ -545,9 +659,16 @@ export const dict = {
"session.revertDock.restore": "Восстановить сообщение", "session.revertDock.restore": "Восстановить сообщение",
"session.new.title": "Создавайте что угодно", "session.new.title": "Создавайте что угодно",
"session.new.project.new": "Новый проект",
"session.new.project.search": "Поиск проектов",
"session.new.project.add": "Добавить проект",
"session.new.worktree.main": "Основная ветка", "session.new.worktree.main": "Основная ветка",
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})", "session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
"session.new.worktree.create": "Создать новый worktree", "session.new.worktree.create": "Создать новый worktree",
"session.new.workspace.runIn": "Запустить сессию в",
"session.new.workspace.triggerLocal": "Локально",
"session.new.workspace.local": "Локальный репозиторий",
"session.new.workspace.existing": "Рабочее пространство…",
"session.new.lastModified": "Последнее изменение", "session.new.lastModified": "Последнее изменение",
"session.header.search.placeholder": "Поиск {{project}}", "session.header.search.placeholder": "Поиск {{project}}",
@@ -619,6 +740,8 @@ export const dict = {
"sidebar.project.recentSessions": "Недавние сессии", "sidebar.project.recentSessions": "Недавние сессии",
"sidebar.project.viewAllSessions": "Посмотреть все сессии", "sidebar.project.viewAllSessions": "Посмотреть все сессии",
"sidebar.project.clearNotifications": "Очистить уведомления", "sidebar.project.clearNotifications": "Очистить уведомления",
"sidebar.empty.title": "Нет открытых проектов",
"sidebar.empty.description": "Откройте проект, чтобы начать",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "Приложение", "settings.section.desktop": "Приложение",
@@ -630,6 +753,7 @@ export const dict = {
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.", "settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
"settings.general.section.appearance": "Внешний вид", "settings.general.section.appearance": "Внешний вид",
"settings.general.section.advanced": "Дополнительно",
"settings.general.section.notifications": "Системные уведомления", "settings.general.section.notifications": "Системные уведомления",
"settings.general.section.updates": "Обновления", "settings.general.section.updates": "Обновления",
"settings.general.section.sounds": "Звуковые эффекты", "settings.general.section.sounds": "Звуковые эффекты",
@@ -638,6 +762,11 @@ export const dict = {
"settings.general.row.language.title": "Язык", "settings.general.row.language.title": "Язык",
"settings.general.row.language.description": "Изменить язык отображения OpenCode", "settings.general.row.language.description": "Изменить язык отображения OpenCode",
"settings.general.row.shell.title": "Оболочка терминала",
"settings.general.row.shell.description":
"Выберите оболочку для терминала. Совместимые оболочки также используются агентом при вызове инструментов.",
"settings.general.row.shell.autoDefault": "Авто (по умолчанию)",
"settings.general.row.shell.terminalOnly": "только терминал",
"settings.general.row.appearance.title": "Внешний вид", "settings.general.row.appearance.title": "Внешний вид",
"settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве", "settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
"settings.general.row.colorScheme.title": "Цветовая схема", "settings.general.row.colorScheme.title": "Цветовая схема",
@@ -646,8 +775,8 @@ export const dict = {
"settings.general.row.theme.description": "Настройте оформление OpenCode.", "settings.general.row.theme.description": "Настройте оформление OpenCode.",
"settings.general.row.font.title": "Шрифт кода", "settings.general.row.font.title": "Шрифт кода",
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода", "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Шрифт терминала",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале",
"settings.general.row.uiFont.title": "Шрифт интерфейса", "settings.general.row.uiFont.title": "Шрифт интерфейса",
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе", "settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
"settings.general.row.followup.title": "Поведение уточняющих вопросов", "settings.general.row.followup.title": "Поведение уточняющих вопросов",
@@ -655,6 +784,22 @@ export const dict = {
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь", "Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
"settings.general.row.followup.option.queue": "Очередь", "settings.general.row.followup.option.queue": "Очередь",
"settings.general.row.followup.option.steer": "Направлять", "settings.general.row.followup.option.steer": "Направлять",
"settings.general.row.showFileTree.title": "Дерево файлов",
"settings.general.row.showFileTree.description": "Показывать панель дерева файлов в сессиях",
"settings.general.row.showNavigation.title": "Элементы навигации",
"settings.general.row.showNavigation.description":
"Показывать кнопки «Назад» и «Вперёд» в строке заголовка приложения",
"settings.general.row.showSearch.title": "Палитра команд",
"settings.general.row.showSearch.description": "Показывать кнопку поиска и палитры команд в строке заголовка",
"settings.general.row.showTerminal.title": "Терминал",
"settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения",
"settings.general.row.showStatus.title": "Состояние сервера",
"settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка",
"settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация",
"settings.general.row.mobileTitlebarBottom.description":
"На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана",
"settings.general.row.showCustomAgents.title": "Пользовательские агенты",
"settings.general.row.showCustomAgents.description": "Показывать выбор агента в редакторе запросов",
"settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений", "settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений",
"settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте", "settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте",
@@ -664,6 +809,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit", "settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию", "Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
"settings.general.row.newLayoutDesigns.title": "Новая компоновка и оформление",
"settings.general.row.newLayoutDesigns.description":
"Включить обновлённую компоновку, главную страницу, редактор запросов и интерфейс сессии",
"settings.general.row.pinchZoom.title": "Масштабирование щипком",
"settings.general.row.pinchZoom.description":
"Разрешить масштабирование жестом щипка на трекпаде и прокруткой с Ctrl",
"settings.general.row.wayland.title": "Использовать нативный Wayland", "settings.general.row.wayland.title": "Использовать нативный Wayland",
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.", "settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
"settings.general.row.wayland.tooltip": "settings.general.row.wayland.tooltip":
@@ -678,6 +829,8 @@ export const dict = {
"settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны", "settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны",
"settings.updates.action.checkNow": "Проверить сейчас", "settings.updates.action.checkNow": "Проверить сейчас",
"settings.updates.action.checking": "Проверка...", "settings.updates.action.checking": "Проверка...",
"settings.updates.action.downloading": "Загрузка...",
"settings.updates.action.installing": "Установка...",
"settings.updates.toast.latest.title": "У вас последняя версия", "settings.updates.toast.latest.title": "У вас последняя версия",
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.", "settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
"sound.option.none": "Нет", "sound.option.none": "Нет",
@@ -807,8 +960,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Загрузка навыка по имени", "settings.permissions.tool.skill.description": "Загрузка навыка по имени",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Запросы к языковому серверу", "settings.permissions.tool.lsp.description": "Запросы к языковому серверу",
"settings.permissions.tool.todowrite.title": "Todo Write",
"settings.permissions.tool.todowrite.description": "Обновление списка задач",
"settings.permissions.tool.webfetch.title": "Web Fetch", "settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Получение контента по URL", "settings.permissions.tool.webfetch.description": "Получение контента по URL",
"settings.permissions.tool.websearch.title": "Web Search", "settings.permissions.tool.websearch.title": "Web Search",
@@ -874,7 +1025,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Отслеживайте, просматривайте и отменяйте изменения в этом проекте", "session.review.noVcs.createGit.description": "Отслеживайте, просматривайте и отменяйте изменения в этом проекте",
"session.review.noVcs.createGit.actionLoading": "Создание репозитория Git...", "session.review.noVcs.createGit.actionLoading": "Создание репозитория Git...",
"session.review.noVcs.createGit.action": "Создать репозиторий Git", "session.review.noVcs.createGit.action": "Создать репозиторий Git",
"session.todo.progress": "Выполнено {{done}} из {{total}} задач",
"session.question.progress": "{{current}} из {{total}} вопросов", "session.question.progress": "{{current}} из {{total}} вопросов",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Проводник", "session.header.open.fileExplorer": "Проводник",
+160 -13
View File
@@ -23,6 +23,9 @@ export const dict = {
"command.sidebar.toggle": "สลับแถบข้าง", "command.sidebar.toggle": "สลับแถบข้าง",
"command.project.open": "เปิดโปรเจกต์", "command.project.open": "เปิดโปรเจกต์",
"command.project.previous": "โปรเจกต์ก่อนหน้า",
"command.project.next": "โปรเจกต์ถัดไป",
"command.project.index": "สลับไปยังโปรเจกต์ {{index}}",
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ", "command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
"command.server.switch": "สลับเซิร์ฟเวอร์", "command.server.switch": "สลับเซิร์ฟเวอร์",
"command.settings.open": "เปิดการตั้งค่า", "command.settings.open": "เปิดการตั้งค่า",
@@ -32,7 +35,7 @@ export const dict = {
"command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป", "command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป",
"command.session.archive": "จัดเก็บเซสชัน", "command.session.archive": "จัดเก็บเซสชัน",
"command.palette": "คำสั่งค้นหา", "command.palette": "ชุดคำสั่ง",
"command.theme.cycle": "เปลี่ยนธีม", "command.theme.cycle": "เปลี่ยนธีม",
"command.theme.set": "ใช้ธีม: {{theme}}", "command.theme.set": "ใช้ธีม: {{theme}}",
@@ -45,6 +48,7 @@ export const dict = {
"command.session.new": "เซสชันใหม่", "command.session.new": "เซสชันใหม่",
"command.file.open": "เปิดไฟล์", "command.file.open": "เปิดไฟล์",
"command.tab.close": "ปิดแท็บ", "command.tab.close": "ปิดแท็บ",
"command.tab.reopenClosed": "เปิดแท็บที่ปิดไปอีกครั้ง",
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท", "command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน", "command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
"command.input.focus": "โฟกัสช่องป้อนข้อมูล", "command.input.focus": "โฟกัสช่องป้อนข้อมูล",
@@ -163,7 +167,7 @@ export const dict = {
"provider.custom.field.providerID.placeholder": "myprovider", "provider.custom.field.providerID.placeholder": "myprovider",
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง", "provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
"provider.custom.field.name.label": "ชื่อที่แสดง", "provider.custom.field.name.label": "ชื่อที่แสดง",
"provider.custom.field.name.placeholder": "My AI Provider", "provider.custom.field.name.placeholder": "ผู้ให้บริการ AI ของฉัน",
"provider.custom.field.baseURL.label": "URL พื้นฐาน", "provider.custom.field.baseURL.label": "URL พื้นฐาน",
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
"provider.custom.field.apiKey.label": "คีย์ API", "provider.custom.field.apiKey.label": "คีย์ API",
@@ -208,6 +212,11 @@ export const dict = {
"model.input.video": "วิดีโอ", "model.input.video": "วิดีโอ",
"model.input.pdf": "PDF", "model.input.pdf": "PDF",
"model.tooltip.allows": "อนุญาต: {{inputs}}", "model.tooltip.allows": "อนุญาต: {{inputs}}",
"model.tooltip.context.label": "บริบท",
"model.tooltip.inputs": "ข้อมูลนำเข้า",
"model.tooltip.model": "โมเดล",
"model.tooltip.provider": "ผู้ให้บริการ",
"model.tooltip.reasoning": "การใช้เหตุผล",
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล", "model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล", "model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}", "model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
@@ -218,6 +227,7 @@ export const dict = {
"common.loading": "กำลังโหลด", "common.loading": "กำลังโหลด",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "ยกเลิก", "common.cancel": "ยกเลิก",
"common.clear": "ล้าง",
"common.connect": "เชื่อมต่อ", "common.connect": "เชื่อมต่อ",
"common.disconnect": "ยกเลิกการเชื่อมต่อ", "common.disconnect": "ยกเลิกการเชื่อมต่อ",
"common.continue": "ส่ง", "common.continue": "ส่ง",
@@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "เชลล์", "prompt.mode.shell": "เชลล์",
"prompt.mode.normal": "พรอมต์", "prompt.mode.normal": "พรอมต์",
"prompt.mode.shell.exit": "กด esc เพื่อออก", "prompt.mode.shell.exit": "กด esc เพื่อออก",
"session.child.promptDisabled": "ไม่สามารถส่งพรอมต์ในเซสชันเอเจนต์ย่อยได้.",
"session.child.backToParent": "กลับไปยังเซสชันหลัก.",
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส", "prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?", "prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
@@ -274,6 +286,11 @@ export const dict = {
"prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท", "prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
"prompt.context.removeFile": "เอาไฟล์ออกจากบริบท", "prompt.context.removeFile": "เอาไฟล์ออกจากบริบท",
"prompt.action.attachFile": "แนบไฟล์", "prompt.action.attachFile": "แนบไฟล์",
"prompt.menu.addImagesAndFiles": "เพิ่มไฟล์และอื่น ๆ",
"prompt.menu.imagesAndFiles": "รูปภาพและไฟล์",
"prompt.menu.commands": "คำสั่ง",
"prompt.menu.context": "บริบท",
"prompt.menu.shellCommand": "คำสั่งเชลล์",
"prompt.attachment.remove": "เอาไฟล์แนบออก", "prompt.attachment.remove": "เอาไฟล์แนบออก",
"prompt.action.send": "ส่ง", "prompt.action.send": "ส่ง",
"prompt.action.stop": "หยุด", "prompt.action.stop": "หยุด",
@@ -341,6 +358,75 @@ export const dict = {
"dialog.server.menu.delete": "ลบ", "dialog.server.menu.delete": "ลบ",
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน", "dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
"dialog.server.status.default": "ค่าเริ่มต้น", "dialog.server.status.default": "ค่าเริ่มต้น",
"wsl.server.add": "เพิ่มเซิร์ฟเวอร์ WSL",
"wsl.server.addShort": "เพิ่ม WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "เซิร์ฟเวอร์ WSL",
"wsl.server.retryStart": "ลองเริ่มอีกครั้ง",
"wsl.server.updating": "กำลังอัปเดต...",
"wsl.onboarding.step.distro": "เลือกดิสโทร",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "กำลังตรวจสอบ WSL...",
"wsl.onboarding.restartRequired": "ต้องรีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์.",
"wsl.onboarding.ready": "WSL พร้อมใช้งานแล้ว.",
"wsl.onboarding.required": "ต้องมี WSL เพื่อดำเนินการต่อ.",
"wsl.onboarding.checkingDistros": "กำลังตรวจสอบดิสโทร...",
"wsl.onboarding.installingDistro": "กำลังติดตั้ง {{distro}}...",
"wsl.onboarding.checkingDistro": "กำลังตรวจสอบ {{distro}}...",
"wsl.onboarding.listingDistros": "กำลังแสดงรายการดิสโทร...",
"wsl.onboarding.distroReady": "{{distro}} พร้อมใช้งานแล้ว.",
"wsl.onboarding.distroNotInstalled": "ยังไม่ได้ติดตั้ง {{distro}}.",
"wsl.onboarding.openDistroOnce": "เปิด {{distro}} หนึ่งครั้งเพื่อให้การตั้งค่าเสร็จสมบูรณ์.",
"wsl.onboarding.finishingDistro": "กำลังตั้งค่า {{distro}} ให้เสร็จสมบูรณ์.",
"wsl.onboarding.pickDistro": "เลือกดิสโทรหรือติดตั้งจากรายการด้านล่าง.",
"wsl.onboarding.checkingOpencode": "กำลังตรวจสอบ OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "กำลังตรวจสอบ OpenCode ใน {{distro}}...",
"wsl.onboarding.updatingOpencode": "กำลังอัปเดต OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "กำลังอัปเดต OpenCode ใน {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "อัปเดต OpenCode ใน {{distro}}.",
"wsl.onboarding.updateOpencode": "อัปเดต OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode พร้อมใช้งานใน {{distro}} แล้ว.",
"wsl.onboarding.opencodeReady": "OpenCode พร้อมใช้งานแล้ว.",
"wsl.onboarding.installOpencodeIn": "ติดตั้ง OpenCode ใน {{distro}}.",
"wsl.onboarding.installOpencode": "ติดตั้ง OpenCode",
"wsl.onboarding.chooseDistroFirst": "เลือกดิสโทรก่อน.",
"wsl.onboarding.loadFailed": "ไม่สามารถโหลดสถานะ WSL ได้.",
"wsl.onboarding.loading": "กำลังโหลด...",
"wsl.onboarding.installedDistros": "ดิสโทรที่ติดตั้งแล้ว",
"wsl.onboarding.checkAgain": "ตรวจสอบอีกครั้ง",
"wsl.onboarding.distroStatus.ready": "พร้อมใช้งาน",
"wsl.onboarding.distroStatus.checking": "กำลังตรวจสอบ...",
"wsl.onboarding.distroStatus.opencodeMissing": "ยังไม่ได้ติดตั้ง OpenCode",
"wsl.onboarding.distroStatus.missingTools": "ไม่มี bash, curl",
"wsl.onboarding.distroStatus.unsupported": "ไม่รองรับ · ใช้ WSL 2",
"wsl.onboarding.needAnotherDistro": "ต้องการดิสโทรอื่น?",
"wsl.onboarding.needAnotherDistroHint": "ติดตั้งดิสทริบิวชัน Linux จากแค็ตตาล็อก WSL",
"wsl.onboarding.wslNotInstalled.title": "ยังไม่ได้ติดตั้ง WSL",
"wsl.onboarding.wslNotInstalled.description":
"ต้องมี WSL (Windows Subsystem for Linux) ก่อนที่ OpenCode จะเพิ่มเซิร์ฟเวอร์ WSL ได้",
"wsl.onboarding.wslUnavailable.title": "WSL ไม่พร้อมใช้งาน",
"wsl.onboarding.wslUnavailable.description": "OpenCode ไม่สามารถตรวจสอบ WSL บนเครื่องนี้ได้.",
"wsl.onboarding.installWsl": "ติดตั้ง WSL",
"wsl.onboarding.windowsRestartRequired":
"รีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์ แล้วเปิด OpenCode อีกครั้ง.",
"wsl.onboarding.next": "ถัดไป",
"wsl.onboarding.refresh": "รีเฟรช",
"wsl.onboarding.allDistrosAdded": "เพิ่มดิสโทรที่ติดตั้งไว้ทั้งหมดแล้ว.",
"wsl.onboarding.noDistros": "ยังไม่พบดิสโทร.",
"wsl.onboarding.install": "ติดตั้ง",
"wsl.onboarding.installing": "กำลังติดตั้ง...",
"wsl.onboarding.installDistro": "ติดตั้งดิสโทร",
"wsl.onboarding.searchDistros": "ค้นหาดิสโทร",
"wsl.onboarding.wsl2Required": "ต้องใช้ WSL 2.",
"wsl.onboarding.toolsRequired": "ดิสโทรนี้ต้องมี bash และ curl.",
"wsl.onboarding.openTerminal": "เปิดเทอร์มินัล",
"wsl.onboarding.path": "เส้นทาง: {{path}}",
"wsl.onboarding.notFound": "ไม่พบ",
"wsl.onboarding.version": "เวอร์ชัน: {{version}}",
"wsl.onboarding.unknown": "ไม่ทราบ",
"wsl.onboarding.desktopVersion": "เดสก์ท็อป {{version}}",
"wsl.onboarding.versionMismatch": "เวอร์ชันที่ติดตั้งไม่ตรงกับเวอร์ชันของแอปเดสก์ท็อป.",
"wsl.onboarding.adding": "กำลังเพิ่ม...",
"dialog.project.edit.title": "แก้ไขโปรเจกต์", "dialog.project.edit.title": "แก้ไขโปรเจกต์",
"dialog.project.edit.name": "ชื่อ", "dialog.project.edit.name": "ชื่อ",
@@ -403,6 +489,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -451,10 +538,12 @@ export const dict = {
"error.page.title": "เกิดข้อผิดพลาด", "error.page.title": "เกิดข้อผิดพลาด",
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน", "error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
"error.page.description.localServerStartup": "เกิดข้อผิดพลาดขณะเริ่มเซิร์ฟเวอร์ในเครื่อง.",
"error.page.details.label": "รายละเอียดข้อผิดพลาด", "error.page.details.label": "รายละเอียดข้อผิดพลาด",
"error.page.action.restart": "รีสตาร์ท", "error.page.action.restart": "รีสตาร์ท",
"error.page.action.report": "รายงานข้อผิดพลาด", "error.page.action.report": "รายงานข้อผิดพลาด",
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว", "error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
"error.page.action.exportLogs": "ส่งออกบันทึก",
"error.page.action.checking": "กำลังตรวจสอบ...", "error.page.action.checking": "กำลังตรวจสอบ...",
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต", "error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
"error.page.action.updateTo": "อัปเดตเป็น {{version}}", "error.page.action.updateTo": "อัปเดตเป็น {{version}}",
@@ -500,11 +589,32 @@ export const dict = {
"home.recentProjects": "โปรเจกต์ล่าสุด", "home.recentProjects": "โปรเจกต์ล่าสุด",
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด", "home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
"home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง", "home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง",
"home.title": "หน้าหลัก",
"home.projects": "โปรเจกต์",
"home.project.add": "เพิ่มโปรเจกต์",
"home.recentlyClosed": "ที่ปิดไปล่าสุด",
"home.server.collapse": "ย่อโปรเจกต์ของเซิร์ฟเวอร์",
"home.server.expand": "ขยายโปรเจกต์ของเซิร์ฟเวอร์",
"home.sessions.search.placeholder": "ค้นหาเซสชัน",
"home.sessions.search.placeholder.scoped": "ค้นหาเซสชันใน {{scope}}",
"home.sessions.search.sessions": "เซสชัน",
"home.sessions.search.noResults": "ไม่พบเซสชันสำหรับ {{query}}",
"home.sessions.empty": "ยังไม่มีอะไรที่นี่",
"home.sessions.empty.description": "สร้างเซสชันเพื่อเริ่มต้น",
"home.sessions.group.today": "วันนี้",
"home.sessions.group.yesterday": "เมื่อวาน",
"home.sessions.group.older": "ก่อนหน้านี้",
"home.providerTip": "เชื่อมต่อกับผู้ให้บริการกว่า 75 รายเพื่อใช้โมเดลอื่นๆ รวมถึง Claude, GPT, Gemini และอีกมากมาย",
"session.tab.session": "เซสชัน", "session.tab.session": "เซสชัน",
"session.tab.review": "ตรวจสอบ", "session.tab.review": "ตรวจสอบ",
"session.tab.context": "บริบท", "session.tab.context": "บริบท",
"session.tab.unknown": "เซสชันที่ไม่รู้จัก",
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์", "session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
"session.error.notFound": "ไม่พบเซสชันนี้",
"session.error.notFound.description": "แท็บนี้อ้างถึงเซสชันที่ไม่มีอยู่บนเซิร์ฟเวอร์นี้แล้ว.",
"session.error.notFound.closeTab": "ปิดแท็บ",
"session.error.serverConnection": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้",
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง", "session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
"session.review.change.one": "การเปลี่ยนแปลง", "session.review.change.one": "การเปลี่ยนแปลง",
"session.review.change.other": "การเปลี่ยนแปลง", "session.review.change.other": "การเปลี่ยนแปลง",
@@ -513,6 +623,8 @@ export const dict = {
"session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง", "session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
"session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน", "session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง", "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
"session.review.noUncommittedChanges": "ยังไม่มีการเปลี่ยนแปลงที่รอคอมมิต",
"session.review.noBranchChanges": "ยังไม่มีการเปลี่ยนแปลงในสาขา",
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด", "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
"session.files.empty": "ไม่มีไฟล์", "session.files.empty": "ไม่มีไฟล์",
@@ -526,9 +638,10 @@ export const dict = {
"session.messages.jumpToLatest": "ไปที่ล่าสุด", "session.messages.jumpToLatest": "ไปที่ล่าสุด",
"session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท", "session.context.addToContext": "เพิ่ม {{selection}} ไปยังบริบท",
"session.todo.title": "สิ่งที่ต้องทำ", "session.question.minimize": "ย่อคำถาม",
"session.todo.collapse": "ย่อ", "session.question.restore": "คืนค่าคำถาม",
"session.todo.expand": "ขยาย", "session.question.pending.one": "คำถามที่รอดำเนินการ {{count}} ข้อ",
"session.question.pending.other": "คำถามที่รอดำเนินการ {{count}} ข้อ",
"session.followupDock.summary.one": "{{count}} ข้อความในคิว", "session.followupDock.summary.one": "{{count}} ข้อความในคิว",
"session.followupDock.summary.other": "{{count}} ข้อความในคิว", "session.followupDock.summary.other": "{{count}} ข้อความในคิว",
"session.followupDock.sendNow": "ส่งทันที", "session.followupDock.sendNow": "ส่งทันที",
@@ -542,9 +655,16 @@ export const dict = {
"session.revertDock.restore": "กู้คืนข้อความ", "session.revertDock.restore": "กู้คืนข้อความ",
"session.new.title": "สร้างอะไรก็ได้", "session.new.title": "สร้างอะไรก็ได้",
"session.new.project.new": "โปรเจกต์ใหม่",
"session.new.project.search": "ค้นหาโปรเจกต์",
"session.new.project.add": "เพิ่มโปรเจกต์",
"session.new.worktree.main": "สาขาหลัก", "session.new.worktree.main": "สาขาหลัก",
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})", "session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
"session.new.worktree.create": "สร้าง worktree ใหม่", "session.new.worktree.create": "สร้าง worktree ใหม่",
"session.new.workspace.runIn": "เรียกใช้เซสชันใน",
"session.new.workspace.triggerLocal": "ในเครื่อง",
"session.new.workspace.local": "รีโพซิทอรีในเครื่อง",
"session.new.workspace.existing": "พื้นที่ทำงาน…",
"session.new.lastModified": "แก้ไขล่าสุด", "session.new.lastModified": "แก้ไขล่าสุด",
"session.header.search.placeholder": "ค้นหา {{project}}", "session.header.search.placeholder": "ค้นหา {{project}}",
@@ -612,6 +732,8 @@ export const dict = {
"sidebar.project.recentSessions": "เซสชันล่าสุด", "sidebar.project.recentSessions": "เซสชันล่าสุด",
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด", "sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน", "sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
"sidebar.empty.title": "ไม่มีโปรเจกต์ที่เปิดอยู่",
"sidebar.empty.description": "เปิดโปรเจกต์เพื่อเริ่มต้น",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@@ -624,6 +746,7 @@ export const dict = {
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows", "settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
"settings.general.section.appearance": "รูปลักษณ์", "settings.general.section.appearance": "รูปลักษณ์",
"settings.general.section.advanced": "ขั้นสูง",
"settings.general.section.notifications": "การแจ้งเตือนระบบ", "settings.general.section.notifications": "การแจ้งเตือนระบบ",
"settings.general.section.updates": "การอัปเดต", "settings.general.section.updates": "การอัปเดต",
"settings.general.section.sounds": "เสียงเอฟเฟกต์", "settings.general.section.sounds": "เสียงเอฟเฟกต์",
@@ -632,6 +755,11 @@ export const dict = {
"settings.general.row.language.title": "ภาษา", "settings.general.row.language.title": "ภาษา",
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode", "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
"settings.general.row.shell.title": "เชลล์เทอร์มินัล",
"settings.general.row.shell.description":
"เลือกเชลล์ที่จะใช้กับเทอร์มินัล เชลล์ที่เข้ากันได้จะใช้สำหรับการเรียกเครื่องมือของเอเจนต์ด้วย",
"settings.general.row.shell.autoDefault": "อัตโนมัติ (ค่าเริ่มต้น)",
"settings.general.row.shell.terminalOnly": "เทอร์มินัลเท่านั้น",
"settings.general.row.appearance.title": "รูปลักษณ์", "settings.general.row.appearance.title": "รูปลักษณ์",
"settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ", "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
"settings.general.row.colorScheme.title": "โทนสี", "settings.general.row.colorScheme.title": "โทนสี",
@@ -640,14 +768,29 @@ export const dict = {
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
"settings.general.row.font.title": "ฟอนต์โค้ด", "settings.general.row.font.title": "ฟอนต์โค้ด",
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด", "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล",
"settings.general.row.uiFont.title": "ฟอนต์ UI", "settings.general.row.uiFont.title": "ฟอนต์ UI",
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ", "settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล", "settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว", "settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
"settings.general.row.followup.option.queue": "คิว", "settings.general.row.followup.option.queue": "คิว",
"settings.general.row.followup.option.steer": "นำทาง", "settings.general.row.followup.option.steer": "นำทาง",
"settings.general.row.showFileTree.title": "ต้นไม้ไฟล์",
"settings.general.row.showFileTree.description": "แสดงแผงต้นไม้ไฟล์ในเซสชัน",
"settings.general.row.showNavigation.title": "ตัวควบคุมการนำทาง",
"settings.general.row.showNavigation.description": "แสดงปุ่มย้อนกลับและไปข้างหน้าในแถบชื่อเรื่องเดสก์ท็อป",
"settings.general.row.showSearch.title": "ชุดคำสั่ง",
"settings.general.row.showSearch.description": "แสดงปุ่มค้นหาและชุดคำสั่งในแถบชื่อเรื่อง",
"settings.general.row.showTerminal.title": "เทอร์มินัล",
"settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป",
"settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์",
"settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง",
"settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง",
"settings.general.row.mobileTitlebarBottom.description":
"วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่",
"settings.general.row.showCustomAgents.title": "เอเจนต์ที่กำหนดเอง",
"settings.general.row.showCustomAgents.description": "แสดงตัวเลือกเอเจนต์ในช่องเขียนข้อความ",
"settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล", "settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล",
"settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์", "settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell", "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
@@ -655,6 +798,11 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit", "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์", "แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
"settings.general.row.newLayoutDesigns.title": "เลย์เอาต์และดีไซน์ใหม่",
"settings.general.row.newLayoutDesigns.description":
"เปิดใช้เลย์เอาต์ หน้าแรก ช่องเขียนข้อความ และ UI เซสชันที่ออกแบบใหม่",
"settings.general.row.pinchZoom.title": "บีบนิ้วเพื่อซูม",
"settings.general.row.pinchZoom.description": "อนุญาตให้ใช้ท่าบีบนิ้วบนแทร็คแพดและ Ctrl-scroll เพื่อซูม",
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ", "settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท", "settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า", "settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
@@ -668,6 +816,8 @@ export const dict = {
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี", "settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
"settings.updates.action.checkNow": "ตรวจสอบทันที", "settings.updates.action.checkNow": "ตรวจสอบทันที",
"settings.updates.action.checking": "กำลังตรวจสอบ...", "settings.updates.action.checking": "กำลังตรวจสอบ...",
"settings.updates.action.downloading": "กำลังดาวน์โหลด...",
"settings.updates.action.installing": "กำลังติดตั้ง...",
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว", "settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด", "settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
@@ -795,8 +945,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ", "settings.permissions.tool.skill.description": "โหลดทักษะตามชื่อ",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา", "settings.permissions.tool.lsp.description": "เรียกใช้การสืบค้นเซิร์ฟเวอร์ภาษา",
"settings.permissions.tool.todowrite.title": "เขียนรายการงาน",
"settings.permissions.tool.todowrite.description": "อัปเดตรายการงาน",
"settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ", "settings.permissions.tool.webfetch.title": "ดึงข้อมูลจากเว็บ",
"settings.permissions.tool.webfetch.description": "ดึงเนื้อหาจาก URL", "settings.permissions.tool.webfetch.description": "ดึงเนื้อหาจาก URL",
"settings.permissions.tool.websearch.title": "ค้นหาเว็บ", "settings.permissions.tool.websearch.title": "ค้นหาเว็บ",
@@ -857,11 +1005,10 @@ export const dict = {
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้", "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน", "dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
"server.row.noUsername": "ไม่มีชื่อผู้ใช้", "server.row.noUsername": "ไม่มีชื่อผู้ใช้",
"session.review.noVcs.createGit.title": "สร้าง Git repository", "session.review.noVcs.createGit.title": "สร้าง Git รีโพซิทอรี",
"session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้", "session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้",
"session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...", "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git รีโพซิทอรี...",
"session.review.noVcs.createGit.action": "สร้าง Git repository", "session.review.noVcs.createGit.action": "สร้าง Git รีโพซิทอรี",
"session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
"session.question.progress": "{{current}} จาก {{total}} คำถาม", "session.question.progress": "{{current}} จาก {{total}} คำถาม",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "File Explorer", "session.header.open.fileExplorer": "File Explorer",
+160 -12
View File
@@ -27,6 +27,9 @@ export const dict = {
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat", "command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
"command.project.open": "Proje aç", "command.project.open": "Proje aç",
"command.project.previous": "Önceki proje",
"command.project.next": "Sonraki proje",
"command.project.index": "{{index}} numaralı projeye geç",
"command.provider.connect": "Sağlayıcı bağla", "command.provider.connect": "Sağlayıcı bağla",
"command.server.switch": "Sunucu değiştir", "command.server.switch": "Sunucu değiştir",
"command.settings.open": "Ayarları aç", "command.settings.open": "Ayarları aç",
@@ -49,6 +52,7 @@ export const dict = {
"command.session.new": "Yeni oturum", "command.session.new": "Yeni oturum",
"command.file.open": "Dosya aç", "command.file.open": "Dosya aç",
"command.tab.close": "Sekmeyi kapat", "command.tab.close": "Sekmeyi kapat",
"command.tab.reopenClosed": "Kapatılan sekmeyi yeniden aç",
"command.context.addSelection": "Seçimi bağlama ekle", "command.context.addSelection": "Seçimi bağlama ekle",
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle", "command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
"command.input.focus": "Girişi odakla", "command.input.focus": "Girişi odakla",
@@ -132,7 +136,7 @@ export const dict = {
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...", "provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}", "provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
"provider.connect.apiKey.description": "provider.connect.apiKey.description":
"{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.", "{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için API anahtarınızı girin.",
"provider.connect.apiKey.label": "{{provider}} API anahtarı", "provider.connect.apiKey.label": "{{provider}} API anahtarı",
"provider.connect.apiKey.placeholder": "API anahtarı", "provider.connect.apiKey.placeholder": "API anahtarı",
"provider.connect.apiKey.required": "API anahtarı gerekli", "provider.connect.apiKey.required": "API anahtarı gerekli",
@@ -143,10 +147,10 @@ export const dict = {
"provider.connect.opencodeZen.visit.prefix": "", "provider.connect.opencodeZen.visit.prefix": "",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen", "provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.", "provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
"provider.connect.oauth.code.visit.prefix": "provider.connect.oauth.code.visit.prefix": "Yetkilendirme kodunuzu almak için ",
"Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
"provider.connect.oauth.code.visit.link": "bu bağlantıya", "provider.connect.oauth.code.visit.link": "bu bağlantıya",
"provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.", "provider.connect.oauth.code.visit.suffix":
" gidin; ardından hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.",
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu", "provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
"provider.connect.oauth.code.placeholder": "Yetkilendirme kodu", "provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli", "provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
@@ -213,6 +217,11 @@ export const dict = {
"model.input.video": "video", "model.input.video": "video",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Kabul eder: {{inputs}}", "model.tooltip.allows": "Kabul eder: {{inputs}}",
"model.tooltip.context.label": "Bağlam",
"model.tooltip.inputs": "Girdiler",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Sağlayıcı",
"model.tooltip.reasoning": "Akıl yürütme",
"model.tooltip.reasoning.allowed": "Akıl yürütme destekler", "model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
"model.tooltip.reasoning.none": "Akıl yürütme yok", "model.tooltip.reasoning.none": "Akıl yürütme yok",
"model.tooltip.context": "Bağlam limiti {{limit}}", "model.tooltip.context": "Bağlam limiti {{limit}}",
@@ -223,6 +232,7 @@ export const dict = {
"common.loading": "Yükleniyor", "common.loading": "Yükleniyor",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "İptal", "common.cancel": "İptal",
"common.clear": "Temizle",
"common.connect": "Bağlan", "common.connect": "Bağlan",
"common.disconnect": "Bağlantı Kes", "common.disconnect": "Bağlantı Kes",
"common.continue": "Gönder", "common.continue": "Gönder",
@@ -240,6 +250,8 @@ export const dict = {
"prompt.mode.shell": "Kabuk", "prompt.mode.shell": "Kabuk",
"prompt.mode.normal": "Komut", "prompt.mode.normal": "Komut",
"prompt.mode.shell.exit": "çıkmak için esc", "prompt.mode.shell.exit": "çıkmak için esc",
"session.child.promptDisabled": "Alt ajan oturumlarına istem gönderilemez.",
"session.child.backToParent": "Ana oturuma dön.",
"prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt", "prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
"prompt.example.2": "Bu projenin teknoloji yığını nedir?", "prompt.example.2": "Bu projenin teknoloji yığını nedir?",
@@ -279,6 +291,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar", "prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
"prompt.context.removeFile": "Dosyayı bağlamdan çıkar", "prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
"prompt.action.attachFile": "Dosya ekle", "prompt.action.attachFile": "Dosya ekle",
"prompt.menu.addImagesAndFiles": "Dosya ve daha fazlasını ekle",
"prompt.menu.imagesAndFiles": "Görseller ve dosyalar",
"prompt.menu.commands": "Komutlar",
"prompt.menu.context": "Bağlam",
"prompt.menu.shellCommand": "Kabuk komutu",
"prompt.attachment.remove": "Eki kaldır", "prompt.attachment.remove": "Eki kaldır",
"prompt.action.send": "Gönder", "prompt.action.send": "Gönder",
"prompt.action.stop": "Durdur", "prompt.action.stop": "Durdur",
@@ -346,6 +363,75 @@ export const dict = {
"dialog.server.menu.delete": "Sil", "dialog.server.menu.delete": "Sil",
"dialog.server.current": "Mevcut Sunucu", "dialog.server.current": "Mevcut Sunucu",
"dialog.server.status.default": "Varsayılan", "dialog.server.status.default": "Varsayılan",
"wsl.server.add": "WSL sunucusu ekle",
"wsl.server.addShort": "WSL ekle",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL sunucusu",
"wsl.server.retryStart": "Başlatmayı yeniden dene",
"wsl.server.updating": "Güncelleniyor...",
"wsl.onboarding.step.distro": "Dağıtım seç",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "WSL kontrol ediliyor...",
"wsl.onboarding.restartRequired": "WSL kurulumunu tamamlamak için Windows'un yeniden başlatılması gerekiyor.",
"wsl.onboarding.ready": "WSL hazır.",
"wsl.onboarding.required": "Devam etmek için WSL gerekli.",
"wsl.onboarding.checkingDistros": "Dağıtımlar kontrol ediliyor...",
"wsl.onboarding.installingDistro": "{{distro}} kuruluyor...",
"wsl.onboarding.checkingDistro": "{{distro}} kontrol ediliyor...",
"wsl.onboarding.listingDistros": "Dağıtımlar listeleniyor...",
"wsl.onboarding.distroReady": "{{distro}} hazır.",
"wsl.onboarding.distroNotInstalled": "{{distro}} henüz kurulu değil.",
"wsl.onboarding.openDistroOnce": "Kurulumu tamamlamak için {{distro}} dağıtımını bir kez açın.",
"wsl.onboarding.finishingDistro": "{{distro}} kurulumu tamamlanıyor.",
"wsl.onboarding.pickDistro": "Bir dağıtım seçin veya aşağıdan bir dağıtım kurun.",
"wsl.onboarding.checkingOpencode": "OpenCode kontrol ediliyor...",
"wsl.onboarding.checkingOpencodeIn": "{{distro}} içinde OpenCode kontrol ediliyor...",
"wsl.onboarding.updatingOpencode": "OpenCode güncelleniyor...",
"wsl.onboarding.updatingOpencodeIn": "{{distro}} içinde OpenCode güncelleniyor...",
"wsl.onboarding.updateOpencodeIn": "{{distro}} içindeki OpenCode'u güncelleyin.",
"wsl.onboarding.updateOpencode": "OpenCode'u güncelle",
"wsl.onboarding.opencodeReadyIn": "{{distro}} içinde OpenCode hazır.",
"wsl.onboarding.opencodeReady": "OpenCode hazır.",
"wsl.onboarding.installOpencodeIn": "OpenCode'u {{distro}} içine kurun.",
"wsl.onboarding.installOpencode": "OpenCode'u kur",
"wsl.onboarding.chooseDistroFirst": "Önce bir dağıtım seçin.",
"wsl.onboarding.loadFailed": "WSL durumu yüklenemedi.",
"wsl.onboarding.loading": "Yükleniyor...",
"wsl.onboarding.installedDistros": "Kurulu dağıtımlar",
"wsl.onboarding.checkAgain": "Tekrar kontrol et",
"wsl.onboarding.distroStatus.ready": "Hazır",
"wsl.onboarding.distroStatus.checking": "Kontrol ediliyor...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode kurulu değil",
"wsl.onboarding.distroStatus.missingTools": "bash ve curl eksik",
"wsl.onboarding.distroStatus.unsupported": "Desteklenmiyor · WSL 2 kullanın",
"wsl.onboarding.needAnotherDistro": "Başka bir dağıtım mı gerekiyor?",
"wsl.onboarding.needAnotherDistroHint": "WSL kataloğundan bir Linux dağıtımı kurun",
"wsl.onboarding.wslNotInstalled.title": "WSL kurulu değil",
"wsl.onboarding.wslNotInstalled.description":
"OpenCode'un WSL sunucusu ekleyebilmesi için önce WSL (Linux için Windows Alt Sistemi) kurulmalıdır",
"wsl.onboarding.wslUnavailable.title": "WSL kullanılamıyor",
"wsl.onboarding.wslUnavailable.description": "OpenCode bu makinede WSL'yi doğrulayamadı.",
"wsl.onboarding.installWsl": "WSL'yi kur",
"wsl.onboarding.windowsRestartRequired":
"WSL kurulumunu tamamlamak için Windows'u yeniden başlatın, ardından OpenCode'u tekrar açın.",
"wsl.onboarding.next": "İleri",
"wsl.onboarding.refresh": "Yenile",
"wsl.onboarding.allDistrosAdded": "Kurulu tüm dağıtımlar zaten eklenmiş.",
"wsl.onboarding.noDistros": "Henüz dağıtım algılanmadı.",
"wsl.onboarding.install": "Kur",
"wsl.onboarding.installing": "Kuruluyor...",
"wsl.onboarding.installDistro": "Dağıtım kur",
"wsl.onboarding.searchDistros": "Dağıtım ara",
"wsl.onboarding.wsl2Required": "WSL 2 gerekli.",
"wsl.onboarding.toolsRequired": "Bu dağıtım için bash ve curl gerekli.",
"wsl.onboarding.openTerminal": "Terminali aç",
"wsl.onboarding.path": "Yol: {{path}}",
"wsl.onboarding.notFound": "bulunamadı",
"wsl.onboarding.version": "Sürüm: {{version}}",
"wsl.onboarding.unknown": "bilinmiyor",
"wsl.onboarding.desktopVersion": "masaüstü {{version}}",
"wsl.onboarding.versionMismatch": "Kurulu sürüm masaüstü uygulamasının sürümüyle eşleşmiyor.",
"wsl.onboarding.adding": "Ekleniyor...",
"dialog.project.edit.title": "Projeyi düzenle", "dialog.project.edit.title": "Projeyi düzenle",
"dialog.project.edit.name": "Ad", "dialog.project.edit.name": "Ad",
@@ -408,6 +494,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -456,10 +543,12 @@ export const dict = {
"error.page.title": "Bir şeyler yanlış gitti", "error.page.title": "Bir şeyler yanlış gitti",
"error.page.description": "Uygulama yüklenirken bir hata oluştu.", "error.page.description": "Uygulama yüklenirken bir hata oluştu.",
"error.page.description.localServerStartup": "Yerel sunucu başlatılırken bir hata oluştu.",
"error.page.details.label": "Hata Detayları", "error.page.details.label": "Hata Detayları",
"error.page.action.restart": "Yeniden Başlat", "error.page.action.restart": "Yeniden Başlat",
"error.page.action.report": "Hatayı Bildir", "error.page.action.report": "Hatayı Bildir",
"error.page.action.reported": "Hata Bildirildi", "error.page.action.reported": "Hata Bildirildi",
"error.page.action.exportLogs": "Günlükleri Dışa Aktar",
"error.page.action.checking": "Kontrol ediliyor...", "error.page.action.checking": "Kontrol ediliyor...",
"error.page.action.checkUpdates": "Güncellemeleri kontrol et", "error.page.action.checkUpdates": "Güncellemeleri kontrol et",
"error.page.action.updateTo": "{{version}} sürümüne güncelle", "error.page.action.updateTo": "{{version}} sürümüne güncelle",
@@ -509,11 +598,32 @@ export const dict = {
"home.recentProjects": "Son projeler", "home.recentProjects": "Son projeler",
"home.empty.title": "Son proje yok", "home.empty.title": "Son proje yok",
"home.empty.description": "Yerel bir proje açarak başlayın", "home.empty.description": "Yerel bir proje açarak başlayın",
"home.title": "Ana Sayfa",
"home.projects": "Projeler",
"home.project.add": "Proje ekle",
"home.recentlyClosed": "Yakın zamanda kapatılanlar",
"home.server.collapse": "Sunucu projelerini daralt",
"home.server.expand": "Sunucu projelerini genişlet",
"home.sessions.search.placeholder": "Oturum ara",
"home.sessions.search.placeholder.scoped": "{{scope}} kapsamında oturum ara",
"home.sessions.search.sessions": "Oturumlar",
"home.sessions.search.noResults": "{{query}} için oturum bulunamadı",
"home.sessions.empty": "Henüz burada bir şey yok",
"home.sessions.empty.description": "Başlamak için bir oturum oluşturun",
"home.sessions.group.today": "Bugün",
"home.sessions.group.yesterday": "Dün",
"home.sessions.group.older": "Daha eski",
"home.providerTip": "Claude, GPT, Gemini ve diğer modelleri kullanmak için 75'ten fazla sağlayıcıya bağlanın",
"session.tab.session": "Oturum", "session.tab.session": "Oturum",
"session.tab.review": "İnceleme", "session.tab.review": "İnceleme",
"session.tab.context": "Bağlam", "session.tab.context": "Bağlam",
"session.tab.unknown": "Bilinmeyen Oturum",
"session.panel.reviewAndFiles": "İnceleme ve dosyalar", "session.panel.reviewAndFiles": "İnceleme ve dosyalar",
"session.error.notFound": "Bu oturum bulunamıyor",
"session.error.notFound.description": "Bu sekme, artık bu sunucuda bulunmayan bir oturuma yönlendiriyor.",
"session.error.notFound.closeTab": "Sekmeyi Kapat",
"session.error.serverConnection": "Bu sunucuya bağlanılamıyor",
"session.review.filesChanged": "{{count}} Dosya Değişti", "session.review.filesChanged": "{{count}} Dosya Değişti",
"session.review.change.one": "Değişiklik", "session.review.change.one": "Değişiklik",
"session.review.change.other": "Değişiklik", "session.review.change.other": "Değişiklik",
@@ -523,6 +633,8 @@ export const dict = {
"session.review.noSnapshot": "session.review.noSnapshot":
"Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor", "Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor",
"session.review.noChanges": "Değişiklik yok", "session.review.noChanges": "Değişiklik yok",
"session.review.noUncommittedChanges": "Henüz commit edilmemiş değişiklik yok",
"session.review.noBranchChanges": "Henüz dal değişikliği yok",
"session.files.selectToOpen": "Açmak için bir dosya seçin", "session.files.selectToOpen": "Açmak için bir dosya seçin",
"session.files.all": "Tüm dosyalar", "session.files.all": "Tüm dosyalar",
@@ -536,9 +648,10 @@ export const dict = {
"session.messages.jumpToLatest": "En sona atla", "session.messages.jumpToLatest": "En sona atla",
"session.context.addToContext": "{{selection}} bağlama ekle", "session.context.addToContext": "{{selection}} bağlama ekle",
"session.todo.title": "Görevler", "session.question.minimize": "Soruyu küçült",
"session.todo.collapse": "Daralt", "session.question.restore": "Soruyu geri yükle",
"session.todo.expand": "Genişlet", "session.question.pending.one": "{{count}} bekleyen soru",
"session.question.pending.other": "{{count}} bekleyen soru",
"session.followupDock.summary.one": "{{count}} sıradaki mesaj", "session.followupDock.summary.one": "{{count}} sıradaki mesaj",
"session.followupDock.summary.other": "{{count}} sıradaki mesaj", "session.followupDock.summary.other": "{{count}} sıradaki mesaj",
"session.followupDock.sendNow": "Şimdi gönder", "session.followupDock.sendNow": "Şimdi gönder",
@@ -552,9 +665,16 @@ export const dict = {
"session.revertDock.restore": "Mesajı geri yükle", "session.revertDock.restore": "Mesajı geri yükle",
"session.new.title": "İstediğini yap", "session.new.title": "İstediğini yap",
"session.new.project.new": "Yeni proje",
"session.new.project.search": "Proje ara",
"session.new.project.add": "Proje ekle",
"session.new.worktree.main": "Ana dal", "session.new.worktree.main": "Ana dal",
"session.new.worktree.mainWithBranch": "Ana dal ({{branch}})", "session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
"session.new.worktree.create": "Yeni çalışma ağacı oluştur", "session.new.worktree.create": "Yeni çalışma ağacı oluştur",
"session.new.workspace.runIn": "Oturumu şurada çalıştır",
"session.new.workspace.triggerLocal": "Yerel",
"session.new.workspace.local": "Yerel depo",
"session.new.workspace.existing": "Çalışma alanı…",
"session.new.lastModified": "Son değişiklik", "session.new.lastModified": "Son değişiklik",
"session.header.search.placeholder": "{{project}} ara", "session.header.search.placeholder": "{{project}} ara",
@@ -624,6 +744,8 @@ export const dict = {
"sidebar.project.recentSessions": "Son oturumlar", "sidebar.project.recentSessions": "Son oturumlar",
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle", "sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
"sidebar.project.clearNotifications": "Bildirimleri temizle", "sidebar.project.clearNotifications": "Bildirimleri temizle",
"sidebar.empty.title": "Açık proje yok",
"sidebar.empty.description": "Başlamak için bir proje açın",
"app.name.desktop": "OpenCode Masaüstü", "app.name.desktop": "OpenCode Masaüstü",
@@ -636,6 +758,7 @@ export const dict = {
"settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.", "settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
"settings.general.section.appearance": "Görünüm", "settings.general.section.appearance": "Görünüm",
"settings.general.section.advanced": "Gelişmiş",
"settings.general.section.notifications": "Sistem bildirimleri", "settings.general.section.notifications": "Sistem bildirimleri",
"settings.general.section.updates": "Güncellemeler", "settings.general.section.updates": "Güncellemeler",
"settings.general.section.sounds": "Ses efektleri", "settings.general.section.sounds": "Ses efektleri",
@@ -644,6 +767,11 @@ export const dict = {
"settings.general.row.language.title": "Dil", "settings.general.row.language.title": "Dil",
"settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin", "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
"settings.general.row.shell.title": "Terminal Kabuğu",
"settings.general.row.shell.description":
"Terminalinizde kullanılacak kabuğu seçin. Uyumlu kabuklar, ajan araç çağrılarında da kullanılır.",
"settings.general.row.shell.autoDefault": "Otomatik (Varsayılan)",
"settings.general.row.shell.terminalOnly": "yalnızca terminal",
"settings.general.row.appearance.title": "Görünüm", "settings.general.row.appearance.title": "Görünüm",
"settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin", "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
"settings.general.row.colorScheme.title": "Renk şeması", "settings.general.row.colorScheme.title": "Renk şeması",
@@ -653,8 +781,8 @@ export const dict = {
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.", "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
"settings.general.row.font.title": "Kod Yazı Tipi", "settings.general.row.font.title": "Kod Yazı Tipi",
"settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin", "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "Terminal yazı tipi",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "Terminalde kullanılan yazı tipini özelleştirin",
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi", "settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin", "settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
"settings.general.row.followup.title": "Takip davranışı", "settings.general.row.followup.title": "Takip davranışı",
@@ -662,6 +790,21 @@ export const dict = {
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin", "Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
"settings.general.row.followup.option.queue": "Sıra", "settings.general.row.followup.option.queue": "Sıra",
"settings.general.row.followup.option.steer": "Yönlendir", "settings.general.row.followup.option.steer": "Yönlendir",
"settings.general.row.showFileTree.title": "Dosya ağacı",
"settings.general.row.showFileTree.description": "Oturumlarda dosya ağacı panelini göster",
"settings.general.row.showNavigation.title": "Gezinme denetimleri",
"settings.general.row.showNavigation.description": "Masaüstü başlık çubuğunda geri ve ileri düğmelerini göster",
"settings.general.row.showSearch.title": "Komut paleti",
"settings.general.row.showSearch.description": "Başlık çubuğunda arama ve komut paleti düğmesini göster",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster",
"settings.general.row.showStatus.title": "Sunucu durumu",
"settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster",
"settings.general.row.mobileTitlebarBottom.title": "Alt gezinme",
"settings.general.row.mobileTitlebarBottom.description":
"Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir",
"settings.general.row.showCustomAgents.title": "Özel ajanlar",
"settings.general.row.showCustomAgents.description": "Yazma alanında ajan seçiciyi göster",
"settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster", "settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
"settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle", "settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
"settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet", "settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
@@ -670,6 +813,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet", "settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster", "Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
"settings.general.row.newLayoutDesigns.title": "Yeni düzen ve tasarımlar",
"settings.general.row.newLayoutDesigns.description":
"Yeniden tasarlanan düzeni, ana sayfayı, yazma alanını ve oturum arayüzünü etkinleştir",
"settings.general.row.pinchZoom.title": "Sıkıştırarak yakınlaştır",
"settings.general.row.pinchZoom.description":
"İzleme dörtgeninde sıkıştırma ve Ctrl-kaydırma hareketleriyle yakınlaştırmaya izin ver",
"settings.general.row.wayland.title": "Yerel Wayland kullan", "settings.general.row.wayland.title": "Yerel Wayland kullan",
"settings.general.row.wayland.description": "settings.general.row.wayland.description":
@@ -686,6 +835,8 @@ export const dict = {
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle", "settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
"settings.updates.action.checkNow": "Şimdi kontrol et", "settings.updates.action.checkNow": "Şimdi kontrol et",
"settings.updates.action.checking": "Kontrol ediliyor...", "settings.updates.action.checking": "Kontrol ediliyor...",
"settings.updates.action.downloading": "İndiriliyor...",
"settings.updates.action.installing": "Kuruluyor...",
"settings.updates.toast.latest.title": "Güncelsiniz", "settings.updates.toast.latest.title": "Güncelsiniz",
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.", "settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
@@ -814,8 +965,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Ada göre bir beceri yükle", "settings.permissions.tool.skill.description": "Ada göre bir beceri yükle",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır", "settings.permissions.tool.lsp.description": "Dil sunucusu sorguları çalıştır",
"settings.permissions.tool.todowrite.title": "Görev Yaz",
"settings.permissions.tool.todowrite.description": "Görev listesini güncelle",
"settings.permissions.tool.webfetch.title": "Web Getir", "settings.permissions.tool.webfetch.title": "Web Getir",
"settings.permissions.tool.webfetch.description": "Bir URL'den içerik getir", "settings.permissions.tool.webfetch.description": "Bir URL'den içerik getir",
"settings.permissions.tool.websearch.title": "Web Ara", "settings.permissions.tool.websearch.title": "Web Ara",
@@ -880,7 +1029,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "Bu projedeki değişiklikleri takip et, incele ve geri al", "session.review.noVcs.createGit.description": "Bu projedeki değişiklikleri takip et, incele ve geri al",
"session.review.noVcs.createGit.actionLoading": "Git deposu oluşturuluyor...", "session.review.noVcs.createGit.actionLoading": "Git deposu oluşturuluyor...",
"session.review.noVcs.createGit.action": "Git deposu oluştur", "session.review.noVcs.createGit.action": "Git deposu oluştur",
"session.todo.progress": "{{total}} görevin {{done}} tanesi tamamlandı",
"session.question.progress": "{{total}} sorunun {{current}} tanesi", "session.question.progress": "{{total}} sorunun {{current}} tanesi",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "Dosya Gezgini", "session.header.open.fileExplorer": "Dosya Gezgini",
+129 -6
View File
@@ -48,6 +48,7 @@ export const dict = {
"command.session.new": "Нова сесія", "command.session.new": "Нова сесія",
"command.file.open": "Відкрити файл", "command.file.open": "Відкрити файл",
"command.tab.close": "Закрити вкладку", "command.tab.close": "Закрити вкладку",
"command.tab.reopenClosed": "Повторно відкрити закриту вкладку",
"command.context.addSelection": "Додати виділення до контексту", "command.context.addSelection": "Додати виділення до контексту",
"command.context.addSelection.description": "Додати вибрані рядки з поточного файлу", "command.context.addSelection.description": "Додати вибрані рядки з поточного файлу",
"command.input.focus": "Фокус на полі введення", "command.input.focus": "Фокус на полі введення",
@@ -212,6 +213,11 @@ export const dict = {
"model.input.video": "відео", "model.input.video": "відео",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "Дозволяє: {{inputs}}", "model.tooltip.allows": "Дозволяє: {{inputs}}",
"model.tooltip.context.label": "Контекст",
"model.tooltip.inputs": "Вхідні дані",
"model.tooltip.model": "Модель",
"model.tooltip.provider": "Провайдер",
"model.tooltip.reasoning": "Мислення",
"model.tooltip.reasoning.allowed": "Підтримує мислення", "model.tooltip.reasoning.allowed": "Підтримує мислення",
"model.tooltip.reasoning.none": "Без мислення", "model.tooltip.reasoning.none": "Без мислення",
"model.tooltip.context": "Ліміт контексту {{limit}}", "model.tooltip.context": "Ліміт контексту {{limit}}",
@@ -222,6 +228,7 @@ export const dict = {
"common.loading": "Завантаження", "common.loading": "Завантаження",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "Скасувати", "common.cancel": "Скасувати",
"common.clear": "Очистити",
"common.open": "Відкрити", "common.open": "Відкрити",
"common.connect": "Підключити", "common.connect": "Підключити",
"common.disconnect": "Відключити", "common.disconnect": "Відключити",
@@ -281,6 +288,11 @@ export const dict = {
"prompt.context.removeActiveFile": "Видалити активний файл з контексту", "prompt.context.removeActiveFile": "Видалити активний файл з контексту",
"prompt.context.removeFile": "Видалити файл з контексту", "prompt.context.removeFile": "Видалити файл з контексту",
"prompt.action.attachFile": "Додати файли", "prompt.action.attachFile": "Додати файли",
"prompt.menu.addImagesAndFiles": "Додати файли та інше",
"prompt.menu.imagesAndFiles": "Зображення та файли",
"prompt.menu.commands": "Команди",
"prompt.menu.context": "Контекст",
"prompt.menu.shellCommand": "Команда оболонки",
"prompt.attachment.remove": "Видалити вкладення", "prompt.attachment.remove": "Видалити вкладення",
"prompt.action.send": "Надіслати", "prompt.action.send": "Надіслати",
"prompt.action.stop": "Зупинити", "prompt.action.stop": "Зупинити",
@@ -354,6 +366,75 @@ export const dict = {
"dialog.server.menu.delete": "Видалити", "dialog.server.menu.delete": "Видалити",
"dialog.server.current": "Поточний сервер", "dialog.server.current": "Поточний сервер",
"dialog.server.status.default": "За замовчуванням", "dialog.server.status.default": "За замовчуванням",
"wsl.server.add": "Додати сервер WSL",
"wsl.server.addShort": "Додати WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "Сервер WSL",
"wsl.server.retryStart": "Повторити запуск",
"wsl.server.updating": "Оновлення...",
"wsl.onboarding.step.distro": "Вибрати дистрибутив",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "Перевірка WSL...",
"wsl.onboarding.restartRequired": "Щоб завершити встановлення WSL, потрібно перезапустити Windows.",
"wsl.onboarding.ready": "WSL готова до роботи.",
"wsl.onboarding.required": "Для продовження потрібна WSL.",
"wsl.onboarding.checkingDistros": "Перевірка дистрибутивів...",
"wsl.onboarding.installingDistro": "Встановлення {{distro}}...",
"wsl.onboarding.checkingDistro": "Перевірка {{distro}}...",
"wsl.onboarding.listingDistros": "Отримання списку дистрибутивів...",
"wsl.onboarding.distroReady": "{{distro}} готовий до роботи.",
"wsl.onboarding.distroNotInstalled": "{{distro}} ще не встановлено.",
"wsl.onboarding.openDistroOnce": "Відкрийте {{distro}} один раз, щоб завершити налаштування.",
"wsl.onboarding.finishingDistro": "Завершення налаштування {{distro}}.",
"wsl.onboarding.pickDistro": "Виберіть дистрибутив або встановіть один із наведених нижче.",
"wsl.onboarding.checkingOpencode": "Перевірка OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "Перевірка OpenCode у {{distro}}...",
"wsl.onboarding.updatingOpencode": "Оновлення OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "Оновлення OpenCode у {{distro}}...",
"wsl.onboarding.updateOpencodeIn": "Оновіть OpenCode у {{distro}}.",
"wsl.onboarding.updateOpencode": "Оновити OpenCode",
"wsl.onboarding.opencodeReadyIn": "OpenCode готовий до роботи у {{distro}}.",
"wsl.onboarding.opencodeReady": "OpenCode готовий до роботи.",
"wsl.onboarding.installOpencodeIn": "Встановіть OpenCode у {{distro}}.",
"wsl.onboarding.installOpencode": "Встановити OpenCode",
"wsl.onboarding.chooseDistroFirst": "Спочатку виберіть дистрибутив.",
"wsl.onboarding.loadFailed": "Не вдалося завантажити стан WSL.",
"wsl.onboarding.loading": "Завантаження...",
"wsl.onboarding.installedDistros": "Встановлені дистрибутиви",
"wsl.onboarding.checkAgain": "Перевірити ще раз",
"wsl.onboarding.distroStatus.ready": "Готовий",
"wsl.onboarding.distroStatus.checking": "Перевірка...",
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не встановлено",
"wsl.onboarding.distroStatus.missingTools": "Немає bash і curl",
"wsl.onboarding.distroStatus.unsupported": "Не підтримується · Використовуйте WSL 2",
"wsl.onboarding.needAnotherDistro": "Потрібен інший дистрибутив?",
"wsl.onboarding.needAnotherDistroHint": "Встановіть дистрибутив Linux із каталогу WSL",
"wsl.onboarding.wslNotInstalled.title": "WSL не встановлено",
"wsl.onboarding.wslNotInstalled.description":
"Для додавання сервера WSL в OpenCode потрібна WSL (Підсистема Windows для Linux)",
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
"wsl.onboarding.wslUnavailable.description": "OpenCode не вдалося перевірити WSL на цьому комп'ютері.",
"wsl.onboarding.installWsl": "Встановити WSL",
"wsl.onboarding.windowsRestartRequired":
"Перезапустіть Windows, щоб завершити встановлення WSL, а потім знову відкрийте OpenCode.",
"wsl.onboarding.next": "Далі",
"wsl.onboarding.refresh": "Оновити",
"wsl.onboarding.allDistrosAdded": "Усі встановлені дистрибутиви вже додано.",
"wsl.onboarding.noDistros": "Дистрибутивів поки не виявлено.",
"wsl.onboarding.install": "Встановити",
"wsl.onboarding.installing": "Встановлення...",
"wsl.onboarding.installDistro": "Встановити дистрибутив",
"wsl.onboarding.searchDistros": "Пошук дистрибутивів",
"wsl.onboarding.wsl2Required": "Для роботи потрібна WSL 2.",
"wsl.onboarding.toolsRequired": "Для цього дистрибутива потрібні bash і curl.",
"wsl.onboarding.openTerminal": "Відкрити термінал",
"wsl.onboarding.path": "Шлях: {{path}}",
"wsl.onboarding.notFound": "не знайдено",
"wsl.onboarding.version": "Версія: {{version}}",
"wsl.onboarding.unknown": "невідомо",
"wsl.onboarding.desktopVersion": "десктопний застосунок {{version}}",
"wsl.onboarding.versionMismatch": "Встановлена версія не відповідає версії десктопного застосунку.",
"wsl.onboarding.adding": "Додавання...",
"server.row.noUsername": "без імені користувача", "server.row.noUsername": "без імені користувача",
"dialog.project.edit.title": "Редагувати проєкт", "dialog.project.edit.title": "Редагувати проєкт",
@@ -473,10 +554,12 @@ export const dict = {
"error.page.title": "Щось пішло не так", "error.page.title": "Щось пішло не так",
"error.page.description": "Під час завантаження програми сталася помилка.", "error.page.description": "Під час завантаження програми сталася помилка.",
"error.page.description.localServerStartup": "Під час запуску локального сервера сталася помилка.",
"error.page.details.label": "Деталі помилки", "error.page.details.label": "Деталі помилки",
"error.page.action.restart": "Перезапустити", "error.page.action.restart": "Перезапустити",
"error.page.action.report": "Повідомити про помилку", "error.page.action.report": "Повідомити про помилку",
"error.page.action.reported": "Помилку повідомлено", "error.page.action.reported": "Помилку повідомлено",
"error.page.action.exportLogs": "Експортувати журнали",
"error.page.action.checking": "Перевірка...", "error.page.action.checking": "Перевірка...",
"error.page.action.checkUpdates": "Перевірити оновлення", "error.page.action.checkUpdates": "Перевірити оновлення",
"error.page.action.updateTo": "Оновити до {{version}}", "error.page.action.updateTo": "Оновити до {{version}}",
@@ -533,11 +616,33 @@ export const dict = {
"home.recentProjects": "Нещодавні проєкти", "home.recentProjects": "Нещодавні проєкти",
"home.empty.title": "Немає нещодавніх проєктів", "home.empty.title": "Немає нещодавніх проєктів",
"home.empty.description": "Почніть, відкривши локальний проєкт", "home.empty.description": "Почніть, відкривши локальний проєкт",
"home.title": "Головна",
"home.projects": "Проєкти",
"home.project.add": "Додати проєкт",
"home.recentlyClosed": "Нещодавно закриті",
"home.server.collapse": "Згорнути проєкти сервера",
"home.server.expand": "Розгорнути проєкти сервера",
"home.sessions.search.placeholder": "Пошук сесій",
"home.sessions.search.placeholder.scoped": "Пошук сесій у {{scope}}",
"home.sessions.search.sessions": "Сесії",
"home.sessions.search.noResults": "Сесій за запитом {{query}} не знайдено",
"home.sessions.empty": "Тут поки нічого немає",
"home.sessions.empty.description": "Створіть сесію, щоб почати",
"home.sessions.group.today": "Сьогодні",
"home.sessions.group.yesterday": "Учора",
"home.sessions.group.older": "Раніше",
"home.providerTip":
"Підключіться до понад 75 провайдерів, щоб використовувати інші моделі, зокрема Claude, GPT, Gemini та інші",
"session.tab.session": "Сесія", "session.tab.session": "Сесія",
"session.tab.review": "Огляд", "session.tab.review": "Огляд",
"session.tab.context": "Контекст", "session.tab.context": "Контекст",
"session.tab.unknown": "Невідома сесія",
"session.panel.reviewAndFiles": "Огляд і файли", "session.panel.reviewAndFiles": "Огляд і файли",
"session.error.notFound": "Цю сесію не знайдено",
"session.error.notFound.description": "Ця вкладка посилається на сесію, якої більше немає на цьому сервері.",
"session.error.notFound.closeTab": "Закрити вкладку",
"session.error.serverConnection": "Не вдається підключитися до цього сервера",
"session.review.filesChanged": "Змінено файлів: {{count}}", "session.review.filesChanged": "Змінено файлів: {{count}}",
"session.review.change.one": "Зміна", "session.review.change.one": "Зміна",
"session.review.change.other": "Зміни", "session.review.change.other": "Зміни",
@@ -565,11 +670,11 @@ export const dict = {
"session.messages.jumpToLatest": "Перейти до останніх", "session.messages.jumpToLatest": "Перейти до останніх",
"session.context.addToContext": "Додати {{selection}} до контексту", "session.context.addToContext": "Додати {{selection}} до контексту",
"session.todo.title": "Завдання",
"session.todo.collapse": "Згорнути",
"session.todo.expand": "Розгорнути",
"session.todo.progress": "Виконано {{done}} з {{total}} завдань",
"session.question.progress": "{{current}} з {{total}} запитань", "session.question.progress": "{{current}} з {{total}} запитань",
"session.question.minimize": "Згорнути запитання",
"session.question.restore": "Відновити запитання",
"session.question.pending.one": "{{count}} запитання очікує відповіді",
"session.question.pending.other": "Запитання, що очікують відповіді: {{count}}",
"session.followupDock.summary.one": "{{count}} повідомлення в черзі", "session.followupDock.summary.one": "{{count}} повідомлення в черзі",
"session.followupDock.summary.other": "{{count}} повідомлень у черзі", "session.followupDock.summary.other": "{{count}} повідомлень у черзі",
"session.followupDock.sendNow": "Надіслати зараз", "session.followupDock.sendNow": "Надіслати зараз",
@@ -583,9 +688,16 @@ export const dict = {
"session.revertDock.restore": "Відновити повідомлення", "session.revertDock.restore": "Відновити повідомлення",
"session.new.title": "Створити що завгодно", "session.new.title": "Створити що завгодно",
"session.new.project.new": "Новий проєкт",
"session.new.project.search": "Пошук проєктів",
"session.new.project.add": "Додати проєкт",
"session.new.worktree.main": "Основна гілка", "session.new.worktree.main": "Основна гілка",
"session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})", "session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})",
"session.new.worktree.create": "Створити нове робоче дерево", "session.new.worktree.create": "Створити нове робоче дерево",
"session.new.workspace.runIn": "Запустити сесію в",
"session.new.workspace.triggerLocal": "Локально",
"session.new.workspace.local": "Локальний репозиторій",
"session.new.workspace.existing": "Робоча область…",
"session.new.lastModified": "Востаннє змінено", "session.new.lastModified": "Востаннє змінено",
"session.header.search.placeholder": "Пошук {{project}}", "session.header.search.placeholder": "Пошук {{project}}",
@@ -773,6 +885,11 @@ export const dict = {
"settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу",
"settings.general.row.showStatus.title": "Статус сервера", "settings.general.row.showStatus.title": "Статус сервера",
"settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку", "settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку",
"settings.general.row.mobileTitlebarBottom.title": "Нижня навігація",
"settings.general.row.mobileTitlebarBottom.description":
"Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях",
"settings.general.row.showCustomAgents.title": "Користувацькі агенти",
"settings.general.row.showCustomAgents.description": "Показувати вибір агента в редакторі запиту",
"settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення", "settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення",
"settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі", "settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі",
"settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки", "settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки",
@@ -781,6 +898,12 @@ export const dict = {
"settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування", "settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування",
"settings.general.row.editToolPartsExpanded.description": "settings.general.row.editToolPartsExpanded.description":
"Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі", "Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі",
"settings.general.row.newLayoutDesigns.title": "Новий макет і дизайн",
"settings.general.row.newLayoutDesigns.description":
"Увімкнути оновлений макет та інтерфейси головної сторінки, редактора запитів і сесії",
"settings.general.row.pinchZoom.title": "Масштабування щипком",
"settings.general.row.pinchZoom.description":
"Дозволити масштабування жестом щипка на трекпаді та прокручуванням із Ctrl",
"settings.general.row.wayland.title": "Використовувати нативний Wayland", "settings.general.row.wayland.title": "Використовувати нативний Wayland",
"settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.", "settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.",
@@ -796,6 +919,8 @@ export const dict = {
"settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні", "settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні",
"settings.updates.action.checkNow": "Перевірити зараз", "settings.updates.action.checkNow": "Перевірити зараз",
"settings.updates.action.checking": "Перевірка...", "settings.updates.action.checking": "Перевірка...",
"settings.updates.action.downloading": "Завантаження...",
"settings.updates.action.installing": "Встановлення...",
"settings.updates.toast.latest.title": "У вас актуальна версія", "settings.updates.toast.latest.title": "У вас актуальна версія",
"settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.", "settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.",
"sound.option.none": "Немає", "sound.option.none": "Немає",
@@ -925,8 +1050,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "Завантаження навички за назвою", "settings.permissions.tool.skill.description": "Завантаження навички за назвою",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Виконання запитів мовного сервера", "settings.permissions.tool.lsp.description": "Виконання запитів мовного сервера",
"settings.permissions.tool.todowrite.title": "Todo Write",
"settings.permissions.tool.todowrite.description": "Оновлення списку завдань",
"settings.permissions.tool.webfetch.title": "Web Fetch", "settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "Отримання вмісту з URL", "settings.permissions.tool.webfetch.description": "Отримання вмісту з URL",
"settings.permissions.tool.websearch.title": "Web Search", "settings.permissions.tool.websearch.title": "Web Search",
+138 -8
View File
@@ -28,6 +28,9 @@ export const dict = {
"command.sidebar.toggle": "切换侧边栏", "command.sidebar.toggle": "切换侧边栏",
"command.project.open": "打开项目", "command.project.open": "打开项目",
"command.project.previous": "上一个项目",
"command.project.next": "下一个项目",
"command.project.index": "切换到项目 {{index}}",
"command.provider.connect": "连接提供商", "command.provider.connect": "连接提供商",
@@ -56,6 +59,7 @@ export const dict = {
"command.file.open": "打开文件", "command.file.open": "打开文件",
"command.tab.close": "关闭标签页", "command.tab.close": "关闭标签页",
"command.tab.reopenClosed": "重新打开已关闭的标签页",
"command.context.addSelection": "将所选内容添加到上下文", "command.context.addSelection": "将所选内容添加到上下文",
"command.context.addSelection.description": "添加当前文件中选中的行", "command.context.addSelection.description": "添加当前文件中选中的行",
@@ -230,6 +234,11 @@ export const dict = {
"model.input.video": "视频", "model.input.video": "视频",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "支持:{{inputs}}", "model.tooltip.allows": "支持:{{inputs}}",
"model.tooltip.context.label": "上下文",
"model.tooltip.inputs": "输入",
"model.tooltip.model": "模型",
"model.tooltip.provider": "提供商",
"model.tooltip.reasoning": "推理",
"model.tooltip.reasoning.allowed": "支持推理", "model.tooltip.reasoning.allowed": "支持推理",
"model.tooltip.reasoning.none": "不支持推理", "model.tooltip.reasoning.none": "不支持推理",
"model.tooltip.context": "上下文上限 {{limit}}", "model.tooltip.context": "上下文上限 {{limit}}",
@@ -240,6 +249,7 @@ export const dict = {
"common.loading": "加载中", "common.loading": "加载中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "取消", "common.cancel": "取消",
"common.clear": "清除",
"common.connect": "连接", "common.connect": "连接",
"common.disconnect": "断开连接", "common.disconnect": "断开连接",
"common.continue": "提交", "common.continue": "提交",
@@ -257,6 +267,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "按 esc 退出", "prompt.mode.shell.exit": "按 esc 退出",
"session.child.promptDisabled": "无法向子智能体会话发送提示词。",
"session.child.backToParent": "返回主会话。",
"prompt.example.1": "修复代码库中的一个 TODO", "prompt.example.1": "修复代码库中的一个 TODO",
"prompt.example.2": "这个项目的技术栈是什么?", "prompt.example.2": "这个项目的技术栈是什么?",
"prompt.example.3": "修复失败的测试", "prompt.example.3": "修复失败的测试",
@@ -294,6 +306,11 @@ export const dict = {
"prompt.context.removeActiveFile": "从上下文移除活动文件", "prompt.context.removeActiveFile": "从上下文移除活动文件",
"prompt.context.removeFile": "从上下文移除文件", "prompt.context.removeFile": "从上下文移除文件",
"prompt.action.attachFile": "附加文件", "prompt.action.attachFile": "附加文件",
"prompt.menu.addImagesAndFiles": "添加文件及更多内容",
"prompt.menu.imagesAndFiles": "图片和文件",
"prompt.menu.commands": "命令",
"prompt.menu.context": "上下文",
"prompt.menu.shellCommand": "shell 命令",
"prompt.attachment.remove": "移除附件", "prompt.attachment.remove": "移除附件",
"prompt.action.send": "发送", "prompt.action.send": "发送",
"prompt.action.stop": "停止", "prompt.action.stop": "停止",
@@ -359,6 +376,73 @@ export const dict = {
"dialog.server.menu.delete": "删除", "dialog.server.menu.delete": "删除",
"dialog.server.current": "当前服务器", "dialog.server.current": "当前服务器",
"dialog.server.status.default": "默认", "dialog.server.status.default": "默认",
"wsl.server.add": "添加 WSL 服务器",
"wsl.server.addShort": "添加 WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 服务器",
"wsl.server.retryStart": "重试启动",
"wsl.server.updating": "正在更新...",
"wsl.onboarding.step.distro": "选择发行版",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "正在检查 WSL...",
"wsl.onboarding.restartRequired": "Windows 需要重启才能完成 WSL 安装。",
"wsl.onboarding.ready": "WSL 已就绪。",
"wsl.onboarding.required": "需要 WSL 才能继续。",
"wsl.onboarding.checkingDistros": "正在检查发行版...",
"wsl.onboarding.installingDistro": "正在安装 {{distro}}...",
"wsl.onboarding.checkingDistro": "正在检查 {{distro}}...",
"wsl.onboarding.listingDistros": "正在列出发行版...",
"wsl.onboarding.distroReady": "{{distro}} 已就绪。",
"wsl.onboarding.distroNotInstalled": "{{distro}} 尚未安装。",
"wsl.onboarding.openDistroOnce": "打开一次 {{distro}} 以完成设置。",
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的设置。",
"wsl.onboarding.pickDistro": "选择一个发行版,或在下方安装。",
"wsl.onboarding.checkingOpencode": "正在检查 OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "正在检查 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
"wsl.onboarding.updateOpencode": "更新 OpenCode",
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就绪。",
"wsl.onboarding.opencodeReady": "OpenCode 已就绪。",
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安装 OpenCode。",
"wsl.onboarding.installOpencode": "安装 OpenCode",
"wsl.onboarding.chooseDistroFirst": "请先选择发行版。",
"wsl.onboarding.loadFailed": "加载 WSL 状态失败。",
"wsl.onboarding.loading": "加载中...",
"wsl.onboarding.installedDistros": "已安装的发行版",
"wsl.onboarding.checkAgain": "重新检查",
"wsl.onboarding.distroStatus.ready": "已就绪",
"wsl.onboarding.distroStatus.checking": "检查中...",
"wsl.onboarding.distroStatus.opencodeMissing": "未安装 OpenCode",
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
"wsl.onboarding.distroStatus.unsupported": "不支持 · 请使用 WSL 2",
"wsl.onboarding.needAnotherDistro": "需要其他发行版?",
"wsl.onboarding.needAnotherDistroHint": "从 WSL 目录安装 Linux 发行版",
"wsl.onboarding.wslNotInstalled.title": "未安装 WSL",
"wsl.onboarding.wslNotInstalled.description": "OpenCode 需要 WSLWindows Subsystem for Linux)才能添加 WSL 服务器",
"wsl.onboarding.wslUnavailable.title": "WSL 不可用",
"wsl.onboarding.wslUnavailable.description": "OpenCode 无法验证此设备上的 WSL。",
"wsl.onboarding.installWsl": "安装 WSL",
"wsl.onboarding.windowsRestartRequired": "重启 Windows 以完成 WSL 安装,然后重新打开 OpenCode。",
"wsl.onboarding.next": "下一步",
"wsl.onboarding.refresh": "刷新",
"wsl.onboarding.allDistrosAdded": "所有已安装的发行版均已添加。",
"wsl.onboarding.noDistros": "尚未检测到发行版。",
"wsl.onboarding.install": "安装",
"wsl.onboarding.installing": "正在安装...",
"wsl.onboarding.installDistro": "安装发行版",
"wsl.onboarding.searchDistros": "搜索发行版",
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
"wsl.onboarding.toolsRequired": "此发行版需要 bash 和 curl。",
"wsl.onboarding.openTerminal": "打开终端",
"wsl.onboarding.path": "路径:{{path}}",
"wsl.onboarding.notFound": "未找到",
"wsl.onboarding.version": "版本:{{version}}",
"wsl.onboarding.unknown": "未知",
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
"wsl.onboarding.versionMismatch": "已安装版本与桌面应用版本不一致。",
"wsl.onboarding.adding": "正在添加...",
"dialog.project.edit.title": "编辑项目", "dialog.project.edit.title": "编辑项目",
"dialog.project.edit.name": "名称", "dialog.project.edit.name": "名称",
@@ -418,6 +502,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -456,10 +541,12 @@ export const dict = {
"error.page.title": "出了点问题", "error.page.title": "出了点问题",
"error.page.description": "加载应用程序时发生错误。", "error.page.description": "加载应用程序时发生错误。",
"error.page.description.localServerStartup": "启动本地服务器时发生错误。",
"error.page.details.label": "错误详情", "error.page.details.label": "错误详情",
"error.page.action.restart": "重启", "error.page.action.restart": "重启",
"error.page.action.report": "上报错误", "error.page.action.report": "上报错误",
"error.page.action.reported": "错误已上报", "error.page.action.reported": "错误已上报",
"error.page.action.exportLogs": "导出日志",
"error.page.action.checking": "检查中...", "error.page.action.checking": "检查中...",
"error.page.action.checkUpdates": "检查更新", "error.page.action.checkUpdates": "检查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
@@ -506,6 +593,9 @@ export const dict = {
"home.title": "主页", "home.title": "主页",
"home.projects": "项目", "home.projects": "项目",
"home.project.add": "添加项目", "home.project.add": "添加项目",
"home.recentlyClosed": "最近关闭",
"home.server.collapse": "折叠服务器项目",
"home.server.expand": "展开服务器项目",
"home.sessions.search.placeholder": "搜索会话", "home.sessions.search.placeholder": "搜索会话",
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话", "home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话",
"home.sessions.search.sessions": "会话", "home.sessions.search.sessions": "会话",
@@ -515,11 +605,17 @@ export const dict = {
"home.sessions.group.today": "今天", "home.sessions.group.today": "今天",
"home.sessions.group.yesterday": "昨天", "home.sessions.group.yesterday": "昨天",
"home.sessions.group.older": "更早", "home.sessions.group.older": "更早",
"home.providerTip": "连接 75 个以上的提供商,使用 Claude、GPT、Gemini 等其他模型",
"session.tab.session": "会话", "session.tab.session": "会话",
"session.tab.review": "审查", "session.tab.review": "审查",
"session.tab.context": "上下文", "session.tab.context": "上下文",
"session.tab.unknown": "未知会话",
"session.panel.reviewAndFiles": "审查和文件", "session.panel.reviewAndFiles": "审查和文件",
"session.error.notFound": "找不到此会话",
"session.error.notFound.description": "此标签页指向的会话已不存在于此服务器上。",
"session.error.notFound.closeTab": "关闭标签页",
"session.error.serverConnection": "无法连接到此服务器",
"session.review.filesChanged": "{{count}} 个文件变更", "session.review.filesChanged": "{{count}} 个文件变更",
"session.review.change.one": "更改", "session.review.change.one": "更改",
"session.review.change.other": "更改", "session.review.change.other": "更改",
@@ -528,6 +624,8 @@ export const dict = {
"session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改", "session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
"session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用", "session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
"session.review.noChanges": "无更改", "session.review.noChanges": "无更改",
"session.review.noUncommittedChanges": "尚无未提交的更改",
"session.review.noBranchChanges": "尚无分支更改",
"session.files.selectToOpen": "选择要打开的文件", "session.files.selectToOpen": "选择要打开的文件",
"session.files.all": "所有文件", "session.files.all": "所有文件",
"session.files.empty": "无文件", "session.files.empty": "无文件",
@@ -538,9 +636,10 @@ export const dict = {
"session.messages.loading": "正在加载消息...", "session.messages.loading": "正在加载消息...",
"session.messages.jumpToLatest": "跳转到最新", "session.messages.jumpToLatest": "跳转到最新",
"session.context.addToContext": "将 {{selection}} 添加到上下文", "session.context.addToContext": "将 {{selection}} 添加到上下文",
"session.todo.title": "待办事项", "session.question.minimize": "最小化问题",
"session.todo.collapse": "折叠", "session.question.restore": "恢复问题",
"session.todo.expand": "展开", "session.question.pending.one": "{{count}} 个待处理问题",
"session.question.pending.other": "{{count}} 个待处理问题",
"session.followupDock.summary.one": "{{count}} 条排队消息", "session.followupDock.summary.one": "{{count}} 条排队消息",
"session.followupDock.summary.other": "{{count}} 条排队消息", "session.followupDock.summary.other": "{{count}} 条排队消息",
"session.followupDock.sendNow": "立即发送", "session.followupDock.sendNow": "立即发送",
@@ -553,9 +652,16 @@ export const dict = {
"session.revertDock.expand": "展开已回滚消息", "session.revertDock.expand": "展开已回滚消息",
"session.revertDock.restore": "恢复消息", "session.revertDock.restore": "恢复消息",
"session.new.title": "构建任何东西", "session.new.title": "构建任何东西",
"session.new.project.new": "新建项目",
"session.new.project.search": "搜索项目",
"session.new.project.add": "添加项目",
"session.new.worktree.main": "主分支", "session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支({{branch}}", "session.new.worktree.mainWithBranch": "主分支({{branch}}",
"session.new.worktree.create": "创建新的 worktree", "session.new.worktree.create": "创建新的 worktree",
"session.new.workspace.runIn": "运行会话的位置",
"session.new.workspace.triggerLocal": "本地",
"session.new.workspace.local": "本地仓库",
"session.new.workspace.existing": "工作区…",
"session.new.lastModified": "最后修改", "session.new.lastModified": "最后修改",
"session.header.search.placeholder": "搜索 {{project}}", "session.header.search.placeholder": "搜索 {{project}}",
"session.header.searchFiles": "搜索文件", "session.header.searchFiles": "搜索文件",
@@ -623,6 +729,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近会话", "sidebar.project.recentSessions": "最近会话",
"sidebar.project.viewAllSessions": "查看全部会话", "sidebar.project.viewAllSessions": "查看全部会话",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"sidebar.empty.title": "没有打开的项目",
"sidebar.empty.description": "打开一个项目以开始使用",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
@@ -637,6 +745,7 @@ export const dict = {
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。", "settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
"settings.general.section.appearance": "外观", "settings.general.section.appearance": "外观",
"settings.general.section.advanced": "高级",
"settings.general.section.notifications": "系统通知", "settings.general.section.notifications": "系统通知",
"settings.general.section.updates": "更新", "settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效", "settings.general.section.sounds": "音效",
@@ -644,6 +753,10 @@ export const dict = {
"settings.general.section.display": "显示", "settings.general.section.display": "显示",
"settings.general.row.language.title": "语言", "settings.general.row.language.title": "语言",
"settings.general.row.language.description": "更改 OpenCode 的显示语言", "settings.general.row.language.description": "更改 OpenCode 的显示语言",
"settings.general.row.shell.title": "终端 Shell",
"settings.general.row.shell.description": "选择终端使用的 shell。兼容的 shell 也会用于智能体工具调用。",
"settings.general.row.shell.autoDefault": "自动(默认)",
"settings.general.row.shell.terminalOnly": "仅终端",
"settings.general.row.appearance.title": "外观", "settings.general.row.appearance.title": "外观",
"settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观", "settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
"settings.general.row.colorScheme.title": "配色方案", "settings.general.row.colorScheme.title": "配色方案",
@@ -652,20 +765,38 @@ export const dict = {
"settings.general.row.theme.description": "自定义 OpenCode 的主题。", "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
"settings.general.row.font.title": "代码字体", "settings.general.row.font.title": "代码字体",
"settings.general.row.font.description": "自定义代码块使用的字体", "settings.general.row.font.description": "自定义代码块使用的字体",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "终端字体",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "自定义终端使用的字体",
"settings.general.row.uiFont.title": "界面字体", "settings.general.row.uiFont.title": "界面字体",
"settings.general.row.uiFont.description": "自定义整个界面使用的字体", "settings.general.row.uiFont.description": "自定义整个界面使用的字体",
"settings.general.row.followup.title": "跟进消息行为", "settings.general.row.followup.title": "跟进消息行为",
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待", "settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
"settings.general.row.followup.option.queue": "排队", "settings.general.row.followup.option.queue": "排队",
"settings.general.row.followup.option.steer": "引导", "settings.general.row.followup.option.steer": "引导",
"settings.general.row.showFileTree.title": "文件树",
"settings.general.row.showFileTree.description": "在会话中显示文件树面板",
"settings.general.row.showNavigation.title": "导航控件",
"settings.general.row.showNavigation.description": "在桌面版标题栏中显示前进和后退按钮",
"settings.general.row.showSearch.title": "命令面板",
"settings.general.row.showSearch.description": "在标题栏中显示搜索和命令面板按钮",
"settings.general.row.showTerminal.title": "终端",
"settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮",
"settings.general.row.showStatus.title": "服务器状态",
"settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮",
"settings.general.row.mobileTitlebarBottom.title": "底部导航",
"settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部",
"settings.general.row.showCustomAgents.title": "自定义智能体",
"settings.general.row.showCustomAgents.description": "在输入框中显示智能体选择器",
"settings.general.row.reasoningSummaries.title": "显示推理摘要", "settings.general.row.reasoningSummaries.title": "显示推理摘要",
"settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要", "settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要",
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分", "settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分", "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分", "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分", "settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
"settings.general.row.newLayoutDesigns.title": "新版布局和设计",
"settings.general.row.newLayoutDesigns.description": "启用重新设计的布局、主页、输入框和会话界面",
"settings.general.row.pinchZoom.title": "双指缩放",
"settings.general.row.pinchZoom.description": "允许使用触控板双指捏合和 Ctrl-scroll 手势缩放",
"settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。", "settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。", "settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
@@ -678,6 +809,8 @@ export const dict = {
"settings.updates.row.check.description": "手动检查更新并在有更新时安装", "settings.updates.row.check.description": "手动检查更新并在有更新时安装",
"settings.updates.action.checkNow": "立即检查", "settings.updates.action.checkNow": "立即检查",
"settings.updates.action.checking": "正在检查...", "settings.updates.action.checking": "正在检查...",
"settings.updates.action.downloading": "正在下载...",
"settings.updates.action.installing": "正在安装...",
"settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.title": "已是最新版本",
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
@@ -805,8 +938,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "按名称加载技能", "settings.permissions.tool.skill.description": "按名称加载技能",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "运行语言服务器查询", "settings.permissions.tool.lsp.description": "运行语言服务器查询",
"settings.permissions.tool.todowrite.title": "更新待办",
"settings.permissions.tool.todowrite.description": "更新待办列表",
"settings.permissions.tool.webfetch.title": "网页获取", "settings.permissions.tool.webfetch.title": "网页获取",
"settings.permissions.tool.webfetch.description": "从 URL 获取内容", "settings.permissions.tool.webfetch.description": "从 URL 获取内容",
"settings.permissions.tool.websearch.title": "网页搜索", "settings.permissions.tool.websearch.title": "网页搜索",
@@ -871,7 +1002,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "在此项目中跟踪、审查和撤消更改", "session.review.noVcs.createGit.description": "在此项目中跟踪、审查和撤消更改",
"session.review.noVcs.createGit.actionLoading": "正在创建 Git 仓库...", "session.review.noVcs.createGit.actionLoading": "正在创建 Git 仓库...",
"session.review.noVcs.createGit.action": "创建 Git 仓库", "session.review.noVcs.createGit.action": "创建 Git 仓库",
"session.todo.progress": "已完成 {{done}} 个任务(共 {{total}} 个)",
"session.question.progress": "{{current}}/{{total}} 个问题", "session.question.progress": "{{current}}/{{total}} 个问题",
"session.header.open.finder": "访达", "session.header.open.finder": "访达",
"session.header.open.fileExplorer": "文件资源管理器", "session.header.open.fileExplorer": "文件资源管理器",
+150 -8
View File
@@ -27,6 +27,9 @@ export const dict = {
"command.sidebar.toggle": "切換側邊欄", "command.sidebar.toggle": "切換側邊欄",
"command.project.open": "開啟專案", "command.project.open": "開啟專案",
"command.project.previous": "上一個專案",
"command.project.next": "下一個專案",
"command.project.index": "切換至專案 {{index}}",
"command.provider.connect": "連接提供者", "command.provider.connect": "連接提供者",
"command.server.switch": "切換伺服器", "command.server.switch": "切換伺服器",
"command.settings.open": "開啟設定", "command.settings.open": "開啟設定",
@@ -49,6 +52,7 @@ export const dict = {
"command.session.new": "新增工作階段", "command.session.new": "新增工作階段",
"command.file.open": "開啟檔案", "command.file.open": "開啟檔案",
"command.tab.close": "關閉分頁", "command.tab.close": "關閉分頁",
"command.tab.reopenClosed": "重新開啟已關閉的分頁",
"command.context.addSelection": "將選取內容加入上下文", "command.context.addSelection": "將選取內容加入上下文",
"command.context.addSelection.description": "加入目前檔案中選取的行", "command.context.addSelection.description": "加入目前檔案中選取的行",
"command.input.focus": "聚焦輸入框", "command.input.focus": "聚焦輸入框",
@@ -209,6 +213,11 @@ export const dict = {
"model.input.video": "影片", "model.input.video": "影片",
"model.input.pdf": "pdf", "model.input.pdf": "pdf",
"model.tooltip.allows": "支援: {{inputs}}", "model.tooltip.allows": "支援: {{inputs}}",
"model.tooltip.context.label": "上下文",
"model.tooltip.inputs": "輸入",
"model.tooltip.model": "模型",
"model.tooltip.provider": "供應商",
"model.tooltip.reasoning": "推理",
"model.tooltip.reasoning.allowed": "支援推理", "model.tooltip.reasoning.allowed": "支援推理",
"model.tooltip.reasoning.none": "不支援推理", "model.tooltip.reasoning.none": "不支援推理",
"model.tooltip.context": "上下文上限 {{limit}}", "model.tooltip.context": "上下文上限 {{limit}}",
@@ -218,6 +227,7 @@ export const dict = {
"common.loading": "載入中", "common.loading": "載入中",
"common.loading.ellipsis": "...", "common.loading.ellipsis": "...",
"common.cancel": "取消", "common.cancel": "取消",
"common.clear": "清除",
"common.connect": "連線", "common.connect": "連線",
"common.disconnect": "中斷連線", "common.disconnect": "中斷連線",
"common.continue": "提交", "common.continue": "提交",
@@ -235,6 +245,8 @@ export const dict = {
"prompt.mode.shell": "Shell", "prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt", "prompt.mode.normal": "Prompt",
"prompt.mode.shell.exit": "按 esc 退出", "prompt.mode.shell.exit": "按 esc 退出",
"session.child.promptDisabled": "無法在子代理程式工作階段中輸入提示詞。",
"session.child.backToParent": "返回主要工作階段。",
"prompt.example.1": "修復程式碼庫中的一個 TODO", "prompt.example.1": "修復程式碼庫中的一個 TODO",
"prompt.example.2": "這個專案的技術堆疊是什麼?", "prompt.example.2": "這個專案的技術堆疊是什麼?",
@@ -274,6 +286,11 @@ export const dict = {
"prompt.context.removeActiveFile": "從上下文移除目前檔案", "prompt.context.removeActiveFile": "從上下文移除目前檔案",
"prompt.context.removeFile": "從上下文移除檔案", "prompt.context.removeFile": "從上下文移除檔案",
"prompt.action.attachFile": "附加檔案", "prompt.action.attachFile": "附加檔案",
"prompt.menu.addImagesAndFiles": "新增檔案及更多內容",
"prompt.menu.imagesAndFiles": "圖片和檔案",
"prompt.menu.commands": "命令",
"prompt.menu.context": "上下文",
"prompt.menu.shellCommand": "shell 命令",
"prompt.attachment.remove": "移除附件", "prompt.attachment.remove": "移除附件",
"prompt.action.send": "傳送", "prompt.action.send": "傳送",
"prompt.action.stop": "停止", "prompt.action.stop": "停止",
@@ -340,6 +357,73 @@ export const dict = {
"dialog.server.menu.delete": "刪除", "dialog.server.menu.delete": "刪除",
"dialog.server.current": "目前伺服器", "dialog.server.current": "目前伺服器",
"dialog.server.status.default": "預設", "dialog.server.status.default": "預設",
"wsl.server.add": "新增 WSL 伺服器",
"wsl.server.addShort": "新增 WSL",
"wsl.server.label": "WSL",
"wsl.server.menu.label": "WSL 伺服器",
"wsl.server.retryStart": "重試啟動",
"wsl.server.updating": "更新中...",
"wsl.onboarding.step.distro": "選擇發行版",
"wsl.onboarding.step.opencode": "OpenCode",
"wsl.onboarding.checkingRuntime": "正在檢查 WSL...",
"wsl.onboarding.restartRequired": "Windows 需要重新啟動,才能完成 WSL 安裝。",
"wsl.onboarding.ready": "WSL 已就緒。",
"wsl.onboarding.required": "需要 WSL 才能繼續。",
"wsl.onboarding.checkingDistros": "正在檢查發行版...",
"wsl.onboarding.installingDistro": "正在安裝 {{distro}}...",
"wsl.onboarding.checkingDistro": "正在檢查 {{distro}}...",
"wsl.onboarding.listingDistros": "正在列出發行版...",
"wsl.onboarding.distroReady": "{{distro}} 已就緒。",
"wsl.onboarding.distroNotInstalled": "尚未安裝 {{distro}}。",
"wsl.onboarding.openDistroOnce": "開啟 {{distro}} 一次以完成設定。",
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的設定。",
"wsl.onboarding.pickDistro": "選擇發行版,或在下方安裝。",
"wsl.onboarding.checkingOpencode": "正在檢查 OpenCode...",
"wsl.onboarding.checkingOpencodeIn": "正在檢查 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
"wsl.onboarding.updateOpencode": "更新 OpenCode",
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就緒。",
"wsl.onboarding.opencodeReady": "OpenCode 已就緒。",
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安裝 OpenCode。",
"wsl.onboarding.installOpencode": "安裝 OpenCode",
"wsl.onboarding.chooseDistroFirst": "請先選擇發行版。",
"wsl.onboarding.loadFailed": "無法載入 WSL 狀態。",
"wsl.onboarding.loading": "載入中...",
"wsl.onboarding.installedDistros": "已安裝的發行版",
"wsl.onboarding.checkAgain": "再次檢查",
"wsl.onboarding.distroStatus.ready": "就緒",
"wsl.onboarding.distroStatus.checking": "檢查中...",
"wsl.onboarding.distroStatus.opencodeMissing": "未安裝 OpenCode",
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
"wsl.onboarding.distroStatus.unsupported": "不支援 · 請使用 WSL 2",
"wsl.onboarding.needAnotherDistro": "需要其他發行版?",
"wsl.onboarding.needAnotherDistroHint": "從 WSL 目錄安裝 Linux 發行版",
"wsl.onboarding.wslNotInstalled.title": "未安裝 WSL",
"wsl.onboarding.wslNotInstalled.description": "新增 WSL 伺服器前,必須先安裝 WSLWindows Subsystem for Linux",
"wsl.onboarding.wslUnavailable.title": "WSL 無法使用",
"wsl.onboarding.wslUnavailable.description": "OpenCode 無法確認此電腦上的 WSL。",
"wsl.onboarding.installWsl": "安裝 WSL",
"wsl.onboarding.windowsRestartRequired": "重新啟動 Windows 以完成 WSL 安裝,然後重新開啟 OpenCode。",
"wsl.onboarding.next": "下一步",
"wsl.onboarding.refresh": "重新整理",
"wsl.onboarding.allDistrosAdded": "所有已安裝的發行版都已新增。",
"wsl.onboarding.noDistros": "尚未偵測到發行版。",
"wsl.onboarding.install": "安裝",
"wsl.onboarding.installing": "安裝中...",
"wsl.onboarding.installDistro": "安裝發行版",
"wsl.onboarding.searchDistros": "搜尋發行版",
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
"wsl.onboarding.toolsRequired": "此發行版需要 bash 和 curl。",
"wsl.onboarding.openTerminal": "開啟終端機",
"wsl.onboarding.path": "路徑: {{path}}",
"wsl.onboarding.notFound": "找不到",
"wsl.onboarding.version": "版本: {{version}}",
"wsl.onboarding.unknown": "未知",
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
"wsl.onboarding.versionMismatch": "已安裝版本與桌面應用程式版本不符。",
"wsl.onboarding.adding": "新增中...",
"dialog.project.edit.title": "編輯專案", "dialog.project.edit.title": "編輯專案",
"dialog.project.edit.name": "名稱", "dialog.project.edit.name": "名稱",
@@ -402,6 +486,7 @@ export const dict = {
"language.no": "Norsk", "language.no": "Norsk",
"language.br": "Português (Brasil)", "language.br": "Português (Brasil)",
"language.bs": "Bosanski", "language.bs": "Bosanski",
"language.uk": "Українська",
"language.th": "ไทย", "language.th": "ไทย",
"language.tr": "Türkçe", "language.tr": "Türkçe",
@@ -449,10 +534,12 @@ export const dict = {
"error.page.title": "出了點問題", "error.page.title": "出了點問題",
"error.page.description": "載入應用程式時發生錯誤。", "error.page.description": "載入應用程式時發生錯誤。",
"error.page.description.localServerStartup": "啟動本機伺服器時發生錯誤。",
"error.page.details.label": "錯誤詳情", "error.page.details.label": "錯誤詳情",
"error.page.action.restart": "重新啟動", "error.page.action.restart": "重新啟動",
"error.page.action.report": "回報錯誤", "error.page.action.report": "回報錯誤",
"error.page.action.reported": "已回報錯誤", "error.page.action.reported": "已回報錯誤",
"error.page.action.exportLogs": "匯出日誌",
"error.page.action.checking": "檢查中...", "error.page.action.checking": "檢查中...",
"error.page.action.checkUpdates": "檢查更新", "error.page.action.checkUpdates": "檢查更新",
"error.page.action.updateTo": "更新到 {{version}}", "error.page.action.updateTo": "更新到 {{version}}",
@@ -498,17 +585,40 @@ export const dict = {
"home.recentProjects": "最近專案", "home.recentProjects": "最近專案",
"home.empty.title": "沒有最近專案", "home.empty.title": "沒有最近專案",
"home.empty.description": "透過開啟本地專案開始使用", "home.empty.description": "透過開啟本地專案開始使用",
"home.title": "首頁",
"home.projects": "專案",
"home.project.add": "新增專案",
"home.recentlyClosed": "最近關閉",
"home.server.collapse": "收合伺服器專案",
"home.server.expand": "展開伺服器專案",
"home.sessions.search.placeholder": "搜尋工作階段",
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜尋工作階段",
"home.sessions.search.sessions": "工作階段",
"home.sessions.search.noResults": "找不到符合 {{query}} 的工作階段",
"home.sessions.empty": "這裡還沒有內容",
"home.sessions.empty.description": "建立工作階段以開始使用",
"home.sessions.group.today": "今天",
"home.sessions.group.yesterday": "昨天",
"home.sessions.group.older": "更早",
"home.providerTip": "連接 75 個以上的供應商,使用 Claude、GPT、Gemini 等其他模型",
"session.tab.session": "工作階段", "session.tab.session": "工作階段",
"session.tab.review": "審查", "session.tab.review": "審查",
"session.tab.context": "上下文", "session.tab.context": "上下文",
"session.tab.unknown": "未知的工作階段",
"session.panel.reviewAndFiles": "審查與檔案", "session.panel.reviewAndFiles": "審查與檔案",
"session.error.notFound": "找不到此工作階段",
"session.error.notFound.description": "此分頁指向的工作階段已不存在於此伺服器。",
"session.error.notFound.closeTab": "關閉分頁",
"session.error.serverConnection": "無法連線至此伺服器",
"session.review.filesChanged": "{{count}} 個檔案變更", "session.review.filesChanged": "{{count}} 個檔案變更",
"session.review.change.one": "變更", "session.review.change.one": "變更",
"session.review.change.other": "變更", "session.review.change.other": "變更",
"session.review.loadingChanges": "正在載入變更...", "session.review.loadingChanges": "正在載入變更...",
"session.review.empty": "此工作階段暫無變更", "session.review.empty": "此工作階段暫無變更",
"session.review.noChanges": "沒有變更", "session.review.noChanges": "沒有變更",
"session.review.noUncommittedChanges": "尚無未提交的變更",
"session.review.noBranchChanges": "尚無分支變更",
"session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更", "session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
"session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更", "session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
"session.files.selectToOpen": "選取要開啟的檔案", "session.files.selectToOpen": "選取要開啟的檔案",
@@ -522,9 +632,10 @@ export const dict = {
"session.messages.jumpToLatest": "跳到最新", "session.messages.jumpToLatest": "跳到最新",
"session.context.addToContext": "將 {{selection}} 新增到上下文", "session.context.addToContext": "將 {{selection}} 新增到上下文",
"session.todo.title": "待辦事項", "session.question.minimize": "最小化問題",
"session.todo.collapse": "折疊", "session.question.restore": "還原問題",
"session.todo.expand": "展開", "session.question.pending.one": "{{count}} 個待處理問題",
"session.question.pending.other": "{{count}} 個待處理問題",
"session.followupDock.summary.one": "{{count}} 則佇列訊息", "session.followupDock.summary.one": "{{count}} 則佇列訊息",
"session.followupDock.summary.other": "{{count}} 則佇列訊息", "session.followupDock.summary.other": "{{count}} 則佇列訊息",
"session.followupDock.sendNow": "立即傳送", "session.followupDock.sendNow": "立即傳送",
@@ -538,9 +649,16 @@ export const dict = {
"session.revertDock.restore": "還原訊息", "session.revertDock.restore": "還原訊息",
"session.new.title": "建構任何東西", "session.new.title": "建構任何東西",
"session.new.project.new": "新專案",
"session.new.project.search": "搜尋專案",
"session.new.project.add": "新增專案",
"session.new.worktree.main": "主分支", "session.new.worktree.main": "主分支",
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})", "session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
"session.new.worktree.create": "建立新的 worktree", "session.new.worktree.create": "建立新的 worktree",
"session.new.workspace.runIn": "工作階段執行位置",
"session.new.workspace.triggerLocal": "本機",
"session.new.workspace.local": "本機儲存庫",
"session.new.workspace.existing": "工作區…",
"session.new.lastModified": "最後修改", "session.new.lastModified": "最後修改",
"session.header.search.placeholder": "搜尋 {{project}}", "session.header.search.placeholder": "搜尋 {{project}}",
@@ -608,6 +726,8 @@ export const dict = {
"sidebar.project.recentSessions": "最近工作階段", "sidebar.project.recentSessions": "最近工作階段",
"sidebar.project.viewAllSessions": "查看全部工作階段", "sidebar.project.viewAllSessions": "查看全部工作階段",
"sidebar.project.clearNotifications": "清除通知", "sidebar.project.clearNotifications": "清除通知",
"sidebar.empty.title": "未開啟任何專案",
"sidebar.empty.description": "開啟專案以開始使用",
"app.name.desktop": "OpenCode Desktop", "app.name.desktop": "OpenCode Desktop",
"settings.section.desktop": "桌面", "settings.section.desktop": "桌面",
@@ -619,6 +739,7 @@ export const dict = {
"settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。", "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
"settings.general.section.appearance": "外觀", "settings.general.section.appearance": "外觀",
"settings.general.section.advanced": "進階",
"settings.general.section.notifications": "系統通知", "settings.general.section.notifications": "系統通知",
"settings.general.section.updates": "更新", "settings.general.section.updates": "更新",
"settings.general.section.sounds": "音效", "settings.general.section.sounds": "音效",
@@ -627,6 +748,10 @@ export const dict = {
"settings.general.row.language.title": "語言", "settings.general.row.language.title": "語言",
"settings.general.row.language.description": "變更 OpenCode 的顯示語言", "settings.general.row.language.description": "變更 OpenCode 的顯示語言",
"settings.general.row.shell.title": "終端機 Shell",
"settings.general.row.shell.description": "選擇終端機使用的 Shell。相容的 Shell 也會用於代理程式工具呼叫。",
"settings.general.row.shell.autoDefault": "自動(預設)",
"settings.general.row.shell.terminalOnly": "僅限終端機",
"settings.general.row.appearance.title": "外觀", "settings.general.row.appearance.title": "外觀",
"settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀", "settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
"settings.general.row.colorScheme.title": "配色方案", "settings.general.row.colorScheme.title": "配色方案",
@@ -635,14 +760,28 @@ export const dict = {
"settings.general.row.theme.description": "自訂 OpenCode 的主題。", "settings.general.row.theme.description": "自訂 OpenCode 的主題。",
"settings.general.row.font.title": "程式碼字型", "settings.general.row.font.title": "程式碼字型",
"settings.general.row.font.description": "自訂程式碼區塊使用的字型", "settings.general.row.font.description": "自訂程式碼區塊使用的字型",
"settings.general.row.terminalFont.title": "Terminal Font", "settings.general.row.terminalFont.title": "終端機字型",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal", "settings.general.row.terminalFont.description": "自訂終端機使用的字型",
"settings.general.row.uiFont.title": "介面字型", "settings.general.row.uiFont.title": "介面字型",
"settings.general.row.uiFont.description": "自訂整個介面使用的字型", "settings.general.row.uiFont.description": "自訂整個介面使用的字型",
"settings.general.row.followup.title": "後續追問行為", "settings.general.row.followup.title": "後續追問行為",
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待", "settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
"settings.general.row.followup.option.queue": "佇列", "settings.general.row.followup.option.queue": "佇列",
"settings.general.row.followup.option.steer": "引導", "settings.general.row.followup.option.steer": "引導",
"settings.general.row.showFileTree.title": "檔案樹",
"settings.general.row.showFileTree.description": "在工作階段中顯示檔案樹面板",
"settings.general.row.showNavigation.title": "導覽控制項",
"settings.general.row.showNavigation.description": "在桌面標題列中顯示上一頁和下一頁按鈕",
"settings.general.row.showSearch.title": "命令面板",
"settings.general.row.showSearch.description": "在標題列中顯示搜尋和命令面板按鈕",
"settings.general.row.showTerminal.title": "終端機",
"settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕",
"settings.general.row.showStatus.title": "伺服器狀態",
"settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕",
"settings.general.row.mobileTitlebarBottom.title": "底部導覽",
"settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部",
"settings.general.row.showCustomAgents.title": "自訂代理程式",
"settings.general.row.showCustomAgents.description": "在輸入區顯示代理程式選擇器",
"settings.general.row.reasoningSummaries.title": "顯示推理摘要", "settings.general.row.reasoningSummaries.title": "顯示推理摘要",
"settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要", "settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要",
@@ -650,6 +789,10 @@ export const dict = {
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊", "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊", "settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊", "settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
"settings.general.row.newLayoutDesigns.title": "新版面與設計",
"settings.general.row.newLayoutDesigns.description": "啟用重新設計的版面、首頁、輸入區和工作階段介面",
"settings.general.row.pinchZoom.title": "雙指縮放",
"settings.general.row.pinchZoom.description": "允許使用觸控板雙指開合和 Ctrl-捲動手勢縮放",
"settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.title": "使用原生 Wayland",
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。", "settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。", "settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
@@ -663,6 +806,8 @@ export const dict = {
"settings.updates.row.check.description": "手動檢查更新並在有更新時安裝", "settings.updates.row.check.description": "手動檢查更新並在有更新時安裝",
"settings.updates.action.checkNow": "立即檢查", "settings.updates.action.checkNow": "立即檢查",
"settings.updates.action.checking": "檢查中...", "settings.updates.action.checking": "檢查中...",
"settings.updates.action.downloading": "下載中...",
"settings.updates.action.installing": "安裝中...",
"settings.updates.toast.latest.title": "已是最新版本", "settings.updates.toast.latest.title": "已是最新版本",
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。", "settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
@@ -789,8 +934,6 @@ export const dict = {
"settings.permissions.tool.skill.description": "按名稱載入技能", "settings.permissions.tool.skill.description": "按名稱載入技能",
"settings.permissions.tool.lsp.title": "LSP", "settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "執行語言伺服器查詢", "settings.permissions.tool.lsp.description": "執行語言伺服器查詢",
"settings.permissions.tool.todowrite.title": "更新待辦",
"settings.permissions.tool.todowrite.description": "更新待辦清單",
"settings.permissions.tool.webfetch.title": "Web Fetch", "settings.permissions.tool.webfetch.title": "Web Fetch",
"settings.permissions.tool.webfetch.description": "從 URL 取得內容", "settings.permissions.tool.webfetch.description": "從 URL 取得內容",
"settings.permissions.tool.websearch.title": "Web Search", "settings.permissions.tool.websearch.title": "Web Search",
@@ -855,7 +998,6 @@ export const dict = {
"session.review.noVcs.createGit.description": "追蹤、檢閱及復原此專案中的變更", "session.review.noVcs.createGit.description": "追蹤、檢閱及復原此專案中的變更",
"session.review.noVcs.createGit.actionLoading": "正在建立 Git 儲存庫...", "session.review.noVcs.createGit.actionLoading": "正在建立 Git 儲存庫...",
"session.review.noVcs.createGit.action": "建立 Git 儲存庫", "session.review.noVcs.createGit.action": "建立 Git 儲存庫",
"session.todo.progress": "已完成 {{done}} 個待辦事項(共 {{total}} 個)",
"session.question.progress": "{{current}}/{{total}} 個問題", "session.question.progress": "{{current}}/{{total}} 個問題",
"session.header.open.finder": "Finder", "session.header.open.finder": "Finder",
"session.header.open.fileExplorer": "檔案總管", "session.header.open.fileExplorer": "檔案總管",
+40 -2
View File
@@ -224,7 +224,26 @@
[data-slot="model-selector-scroll"]::before { [data-slot="model-selector-scroll"]::before {
top: 0; top: 0;
background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent); height: 12px;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
);
} }
[data-slot="manage-models-scroll"]::before { [data-slot="manage-models-scroll"]::before {
@@ -239,7 +258,26 @@
[data-slot="model-selector-scroll"]::after { [data-slot="model-selector-scroll"]::after {
bottom: 0; bottom: 0;
background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent); height: 12px;
background: linear-gradient(
0deg,
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
);
} }
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) { @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
+23 -3
View File
@@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform"
import { DateTime } from "luxon" import { DateTime } from "luxon"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useDirectoryPicker } from "@/components/directory-picker" import { useDirectoryPicker } from "@/components/directory-picker"
import { useSettingsCommand } from "@/components/settings-dialog" import { useSettingsDialog } from "@/components/settings-dialog"
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server" import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2" import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
import { ServerConnection, serverName, useServer } from "@/context/server" import { ServerConnection, serverName, useServer } from "@/context/server"
@@ -68,6 +68,7 @@ import { preloadMarkdown } from "@opencode-ai/session-ui/markdown-cache"
import { archiveHomeSession } from "./home-session-archive" import { archiveHomeSession } from "./home-session-archive"
import { shouldOpenSessionInBackground } from "./home-session-open" import { shouldOpenSessionInBackground } from "./home-session-open"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { fileManagerApp } from "@/utils/file-manager"
const HOME_SESSION_LIMIT = 64 const HOME_SESSION_LIMIT = 64
const HOME_SESSION_HEADER_STICKY_TOP = 12 const HOME_SESSION_HEADER_STICKY_TOP = 12
@@ -267,7 +268,7 @@ export function NewHome() {
const command = useCommand() const command = useCommand()
const notification = useNotification() const notification = useNotification()
const marked = useMarked() const marked = useMarked()
const openSettings = useSettingsCommand() const openSettings = useSettingsDialog()
let focusSessionSearch: (() => void) | undefined let focusSessionSearch: (() => void) | undefined
const [state, setState] = createStore({ const [state, setState] = createStore({
search: "", search: "",
@@ -1030,8 +1031,24 @@ function HomeProjectRow(props: {
language: ReturnType<typeof useLanguage> language: ReturnType<typeof useLanguage>
}) { }) {
const global = useGlobal() const global = useGlobal()
const platform = usePlatform()
const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false
const [state, setState] = createStore({ menuOpen: false }) const [state, setState] = createStore({ menuOpen: false })
const canRevealInFileManager = () =>
platform.platform === "desktop" && !!platform.openPath && ServerConnection.local(props.server)
const fileManagerActionLabel = () =>
props.language.t(
fileManagerApp(platform.platform === "desktop" ? (platform.os ?? "unknown") : "unknown").actionLabel,
)
const revealInFileManager = () => {
if (!platform.openPath) return
platform.openPath(props.project.worktree).catch((err: unknown) =>
showToast({
title: props.language.t("common.requestFailed"),
description: errorMessage(err, props.language.t("common.requestFailed")),
}),
)
}
return ( return (
<div class="group/project relative flex h-7 min-w-0 items-center rounded-[6px]"> <div class="group/project relative flex h-7 min-w-0 items-center rounded-[6px]">
<button <button
@@ -1073,6 +1090,9 @@ function HomeProjectRow(props: {
<MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}> <MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}>
{props.language.t("dialog.project.edit.title")} {props.language.t("dialog.project.edit.title")}
</MenuV2.Item> </MenuV2.Item>
<Show when={canRevealInFileManager()}>
<MenuV2.Item onSelect={revealInFileManager}>{fileManagerActionLabel()}</MenuV2.Item>
</Show>
<MenuV2.Item <MenuV2.Item
disabled={props.unseenCount === 0} disabled={props.unseenCount === 0}
onSelect={() => props.clearNotifications(props.server, props.project)} onSelect={() => props.clearNotifications(props.server, props.project)}
@@ -1278,7 +1298,7 @@ function HomeSessionSearch(props: {
</div> </div>
</div> </div>
</Show> </Show>
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02"> <label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02 focus-within:bg-v2-background-bg-layer-02">
<IconV2 name="magnifying-glass" /> <IconV2 name="magnifying-glass" />
<input <input
ref={input} ref={input}
+4 -1
View File
@@ -1,16 +1,18 @@
import { createEffect, Suspense, type ParentProps } from "solid-js" import { createEffect, Suspense, type ParentProps } from "solid-js"
import { useNavigate } from "@solidjs/router" import { useNavigate } from "@solidjs/router"
import { DebugBar } from "@/components/debug-bar" import { DebugBar } from "@/components/debug-bar"
import { HelpButton } from "@/components/help-button" import { HelpButton, TabsInfoPopup } from "@/components/help-button"
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar" import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { setNavigate } from "@/utils/notification-click" import { setNavigate } from "@/utils/notification-click"
import { setV2Toast, ToastRegion } from "@/utils/toast" import { setV2Toast, ToastRegion } from "@/utils/toast"
import { useSettingsCommand } from "@/components/settings-dialog"
export default function NewLayout(props: ParentProps) { export default function NewLayout(props: ParentProps) {
const platform = usePlatform() const platform = usePlatform()
const navigate = useNavigate() const navigate = useNavigate()
setNavigate(navigate) setNavigate(navigate)
useSettingsCommand()
createEffect(() => setV2Toast(true)) createEffect(() => setV2Toast(true))
@@ -37,6 +39,7 @@ export default function NewLayout(props: ParentProps) {
<Suspense>{props.children}</Suspense> <Suspense>{props.children}</Suspense>
</main> </main>
{import.meta.env.DEV && <DebugBar inline />} {import.meta.env.DEV && <DebugBar inline />}
<TabsInfoPopup />
<HelpButton /> <HelpButton />
<ToastRegion v2 /> <ToastRegion v2 />
</div> </div>
+2 -1
View File
@@ -54,7 +54,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
import { useCommand, type CommandOption } from "@/context/command" import { useCommand, type CommandOption } from "@/context/command"
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd" import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
import { DebugBar } from "@/components/debug-bar" import { DebugBar } from "@/components/debug-bar"
import { HelpButton } from "@/components/help-button" import { HelpButton, TabsInfoPopup } from "@/components/help-button"
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar" import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { useDirectoryPicker } from "@/components/directory-picker" import { useDirectoryPicker } from "@/components/directory-picker"
import { ServerConnection, useServer } from "@/context/server" import { ServerConnection, useServer } from "@/context/server"
@@ -2395,6 +2395,7 @@ export default function LegacyLayout(props: ParentProps) {
</div> </div>
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />} {import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />}
</div> </div>
<TabsInfoPopup />
<HelpButton /> <HelpButton />
<ToastRegion v2={false} /> <ToastRegion v2={false} />
</div> </div>
@@ -2,8 +2,8 @@ import { createMemo, type Accessor } from "solid-js"
import { useGlobal } from "@/context/global" import { useGlobal } from "@/context/global"
import { useNotification } from "@/context/notification" import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission" import { usePermission } from "@/context/permission"
import { sessionPermissionRequest, sessionQuestionRequest } from "@/pages/session/composer/session-request-tree"
import { ServerConnection } from "@/context/server" import { ServerConnection } from "@/context/server"
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
export function useSessionTabAvatarState( export function useSessionTabAvatarState(
server: Accessor<ServerConnection.Key>, server: Accessor<ServerConnection.Key>,
@@ -26,15 +26,20 @@ export function useSessionTabAvatarState(
return !permission.autoResponds(item, directory()) return !permission.autoResponds(item, directory())
}) })
}) })
const unread = createMemo(() => { const hasQuestions = createMemo(() => {
if (hasPermissions()) return true const serverSync = sync()
if (!connection()) return false if (!serverSync) return false
return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0 const [store] = serverSync.child(directory(), { bootstrap: false })
return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
}) })
const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
const unread = createMemo(
() => needsAttention() || notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0,
)
const loading = createMemo(() => { const loading = createMemo(() => {
const serverSync = sync() const serverSync = sync()
if (!serverSync) return false if (!serverSync) return false
if (hasPermissions()) return false if (needsAttention()) return false
return serverSync.session.data.session_working(sessionId()) return serverSync.session.data.session_working(sessionId())
}) })
return { unread, loading } return { unread, loading }
+83 -3
View File
@@ -1,12 +1,13 @@
import { Show, createEffect, createMemo, createResource, untrack } from "solid-js" import { Show, createEffect, createMemo, createResource, createSignal, onCleanup, untrack } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web" import { Portal } from "solid-js/web"
import { useSearchParams } from "@solidjs/router" import { useSearchParams } from "@solidjs/router"
import { Tooltip } from "@opencode-ai/ui/tooltip" import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { NewSessionDesignView } from "@/components/session" import { NewSessionDesignView } from "@/components/session"
import { PromptInput } from "@/components/prompt-input" import { PromptInput } from "@/components/prompt-input"
import { StatusPopoverV2 } from "@/components/status-popover" import { StatusPopoverV2 } from "@/components/status-popover"
import { useSettingsCommand } from "@/components/settings-dialog"
import { import {
PromptProjectAddButton, PromptProjectAddButton,
PromptProjectSelector, PromptProjectSelector,
@@ -25,8 +26,14 @@ import { useComposerCommands } from "@/pages/session/use-composer-commands"
import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout" import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout"
import { PromptWorkspaceSelector } from "@/components/prompt-workspace-selector" import { PromptWorkspaceSelector } from "@/components/prompt-workspace-selector"
import { useTitlebarRightMount } from "@/components/titlebar" import { useTitlebarRightMount } from "@/components/titlebar"
import { useProviders } from "@/hooks/use-providers"
import { useSettingsDialog } from "@/components/settings-dialog"
import { Persist, persisted } from "@/utils/persist"
import createPresence from "solid-presence"
const workspaceBarEnabled = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod" const workspaceBarEnabled = import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
const providerTipDismissalDuration = 30 * 24 * 60 * 60 * 1000
const providerTipExitDuration = 250
/** /**
* The `/new-session` draft page. Unlike `session.tsx`, this only renders the prompt * The `/new-session` draft page. Unlike `session.tsx`, this only renders the prompt
@@ -41,11 +48,12 @@ export default function NewSessionPage() {
const comments = useComments() const comments = useComments()
const language = useLanguage() const language = useLanguage()
const settings = useSettings() const settings = useSettings()
const providers = useProviders(() => sdk().directory)
const openProviderSettings = useSettingsDialog("providers")
const route = useSessionKey() const route = useSessionKey()
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>() const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
useComposerCommands() useComposerCommands()
useSettingsCommand()
let inputRef: HTMLDivElement | undefined let inputRef: HTMLDivElement | undefined
@@ -176,9 +184,81 @@ export default function NewSessionPage() {
</Show> </Show>
</div> </div>
</NewSessionDesignView> </NewSessionDesignView>
<ProviderTip
ready={() => serverSync().child(sdk().directory)[0].provider_ready}
connected={() => providers.paid().length > 0}
openProviders={openProviderSettings}
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
) )
} }
function ProviderTip(props: { ready: () => boolean; connected: () => boolean; openProviders: () => void }) {
const language = useLanguage()
const [persistedState, setPersistedState, , persistedReady] = persisted(
Persist.global("new-session.provider-tip"),
createStore({ dismissedAt: 0 }),
)
const visible = createMemo(
() =>
props.ready() &&
persistedReady() &&
!props.connected() &&
Date.now() - persistedState.dismissedAt >= providerTipDismissalDuration,
)
function dismiss() {
setPersistedState("dismissedAt", Date.now())
}
const [ref, setRef] = createSignal<HTMLDivElement>()
const presence = createPresence({
show: () => visible(),
element: () => ref() ?? null,
})
return (
<Show when={presence.present()}>
<div class="pointer-events-none absolute inset-x-0 bottom-4 flex justify-center px-10">
<div
ref={setRef}
data-component="provider-tip"
data-visible={visible()}
class="group/provider-tip pointer-events-auto relative flex h-6 max-w-full items-center transition-[opacity,transform] duration-[250ms] ease-[cubic-bezier(0.215,0.61,0.355,1)] motion-reduce:transition-none"
classList={{
"data-[visible=false]:animate-out fade-out slide-out-to-bottom-4": true,
}}
>
<button
type="button"
class="flex h-6 min-w-0 items-center rounded-[4px] pl-1.5 text-[13px] leading-none tracking-[-0.04px] text-v2-text-text-faint transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-text-text-muted focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-text-text-muted focus-visible:outline-none"
onClick={props.openProviders}
>
<span class="truncate">{language.t("home.providerTip")}</span>
<span class="flex size-6 shrink-0 items-center justify-center" aria-hidden="true">
<IconV2 name="chevron-down" size="small" class="-rotate-90" />
</span>
</button>
<TooltipV2
class="hover-reveal absolute left-full top-0 flex h-6 w-7 items-center justify-end delay-0 duration-0 group-hover/provider-tip:delay-[250ms] group-hover/provider-tip:duration-150 group-hover/provider-tip:opacity-100 focus-within:delay-0 focus-within:duration-0 focus-within:opacity-100"
placement="top"
openDelay={1000}
value={language.t("common.dismiss")}
>
<button
type="button"
class="flex size-6 items-center justify-center rounded-[4px] text-v2-icon-icon-muted transition-[background-color,color] duration-150 ease-in-out hover:bg-v2-overlay-simple-overlay-hover hover:text-v2-icon-icon-base focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:text-v2-icon-icon-base focus-visible:outline-none"
aria-label={language.t("common.dismiss")}
onClick={dismiss}
>
<IconV2 name="xmark-small" />
</button>
</TooltipV2>
</div>
</div>
</Show>
)
}
+127 -90
View File
@@ -1,4 +1,4 @@
import type { Project, UserMessage } from "@opencode-ai/sdk/v2" import type { Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
import { import {
@@ -11,6 +11,7 @@ import {
createMemo, createMemo,
createEffect, createEffect,
createComputed, createComputed,
createSignal,
on, on,
onMount, onMount,
type ParentProps, type ParentProps,
@@ -35,7 +36,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b
import { Button } from "@opencode-ai/ui/button" import { Button } from "@opencode-ai/ui/button"
import { showToast } from "@/utils/toast" import { showToast } from "@/utils/toast"
import { base64Encode, checksum } from "@opencode-ai/core/util/encode" import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { NewSessionView, SessionHeader } from "@/components/session" import { NewSessionView, SessionHeader } from "@/components/session"
import { ErrorPage } from "@/pages/error" import { ErrorPage } from "@/pages/error"
import { CommentsProvider, useComments } from "@/context/comments" import { CommentsProvider, useComments } from "@/context/comments"
@@ -56,7 +57,6 @@ import { useSync } from "@/context/sync"
import { useTabs } from "@/context/tabs" import { useTabs } from "@/context/tabs"
import { TerminalProvider, useTerminal } from "@/context/terminal" import { TerminalProvider, useTerminal } from "@/context/terminal"
import { PromptInput } from "@/components/prompt-input" import { PromptInput } from "@/components/prompt-input"
import { useSettingsCommand } from "@/components/settings-dialog"
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit" import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
import { import {
createPromptInputController, createPromptInputController,
@@ -70,12 +70,18 @@ import { createTimelineModel } from "@/pages/session/timeline/model"
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab" import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
import { useSessionLayout } from "@/pages/session/session-layout" import { useSessionLayout } from "@/pages/session/session-layout"
import { syncSessionModel } from "@/pages/session/session-model-helpers" import { syncSessionModel } from "@/pages/session/session-model-helpers"
import {
clampSessionPanelWidth,
SESSION_PANEL_WIDTH_MIN,
sessionPanelWidthMax,
} from "@/pages/session/session-panel-width"
import { SessionSidePanel } from "@/pages/session/session-side-panel" import { SessionSidePanel } from "@/pages/session/session-side-panel"
import { sessionPanelLayout } from "@/pages/session/session-panel-layout" import { sessionPanelLayout } from "@/pages/session/session-panel-layout"
import { SessionReviewEmptyChangesV2 } from "@opencode-ai/session-ui/v2/session-review-empty-changes-v2" import { SessionReviewEmptyChangesV2 } from "@opencode-ai/session-ui/v2/session-review-empty-changes-v2"
import { SessionReviewEmptyNoGitV2 } from "@opencode-ai/session-ui/v2/session-review-empty-no-git-v2" import { SessionReviewEmptyNoGitV2 } from "@opencode-ai/session-ui/v2/session-review-empty-no-git-v2"
import { ReviewPanelV2 } from "@/pages/session/v2/review-panel-v2" import { ReviewPanelV2 } from "@/pages/session/v2/review-panel-v2"
import { createReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state" import { createReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state"
import { reviewDiffDirectory, reviewDiffNeedsLoad, reviewRootDirectory } from "@/pages/session/v2/review-diff-kinds"
import { TerminalPanel } from "@/pages/session/terminal-panel" import { TerminalPanel } from "@/pages/session/terminal-panel"
import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2" import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2"
import { useComposerCommands } from "@/pages/session/use-composer-commands" import { useComposerCommands } from "@/pages/session/use-composer-commands"
@@ -101,7 +107,6 @@ type VcsMode = "git" | "branch"
const sessionViewState = () => ({ const sessionViewState = () => ({
messageId: undefined as string | undefined, messageId: undefined as string | undefined,
mobileTab: "session" as "session" | "changes", mobileTab: "session" as "session" | "changes",
changes: "git" as ChangeMode,
}) })
function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) { function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) {
@@ -153,7 +158,7 @@ export function TargetSessionRouteContent() {
) )
} }
function SessionRouteErrorBoundary( export function SessionRouteErrorBoundary(
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>, props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
) { ) {
const settings = useSettings() const settings = useSettings()
@@ -221,7 +226,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve
function ResolvedTargetSessionRoute() { function ResolvedTargetSessionRoute() {
const params = useParams<{ serverKey: string; id: string }>() const params = useParams<{ serverKey: string; id: string }>()
const settings = useSettings()
const tabs = useTabs() const tabs = useTabs()
const sync = useServerSync() const sync = useServerSync()
const serverKey = createMemo(() => requireServerKey(params.serverKey)) const serverKey = createMemo(() => requireServerKey(params.serverKey))
@@ -248,17 +252,12 @@ function ResolvedTargetSessionRoute() {
the terminal. Same-workspace tab switches keep it open because warm the terminal. Same-workspace tab switches keep it open because warm
targets resolve synchronously from the sync cache. */} targets resolve synchronously from the sync cache. */}
<Show when={directory()}> <Show when={directory()}>
<Show
when={settings.general.newLayoutDesigns()}
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
>
<SDKProvider directory={targetDirectory}> <SDKProvider directory={targetDirectory}>
<DirectoryDataProvider directory={targetDirectory} server={serverKey}> <DirectoryDataProvider directory={targetDirectory} server={serverKey}>
<TargetSessionPage /> <TargetSessionPage />
</DirectoryDataProvider> </DirectoryDataProvider>
</SDKProvider> </SDKProvider>
</Show> </Show>
</Show>
</TargetServerScopedProviders> </TargetServerScopedProviders>
) )
} }
@@ -354,6 +353,8 @@ export default function Page() {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { params, sessionKey, workspaceKey, tabs, view } = useSessionLayout() const { params, sessionKey, workspaceKey, tabs, view } = useSessionLayout()
const reviewMode = () => view().review.mode() ?? "git"
const reviewFile = () => view().review.file()
const sessionOwnership = createSessionOwnership(sessionKey) const sessionOwnership = createSessionOwnership(sessionKey)
const newSessionDesign = createMemo(() => settings.general.newLayoutDesigns()) const newSessionDesign = createMemo(() => settings.general.newLayoutDesigns())
@@ -447,9 +448,39 @@ export default function Page() {
newSessionDesign() ? desktopV2ReviewOpen() || desktopTerminalOpen() : desktopReviewOpen(), newSessionDesign() ? desktopV2ReviewOpen() || desktopTerminalOpen() : desktopReviewOpen(),
) )
const desktopSidePanelOpen = createMemo(() => desktopSessionResizeOpen() || desktopFileTreeOpen()) const desktopSidePanelOpen = createMemo(() => desktopSessionResizeOpen() || desktopFileTreeOpen())
let panelRow: HTMLDivElement | undefined
const [panelRowWidth, setPanelRowWidth] = createSignal<number>()
createResizeObserver(
() => panelRow,
({ width }) => setPanelRowWidth(width),
)
const splitReview = createMemo(
() => (newSessionDesign() ? desktopV2ReviewOpen() : desktopReviewOpen()) && layout.review.diffStyle() === "split",
)
// The observer reports the content-box width, which already excludes the row
// padding; only the flex gap between the panels remains to subtract.
const sessionPanelAvailable = createMemo(() => {
const width = panelRowWidth()
if (width === undefined) return undefined
return width - (settings.general.newLayoutDesigns() ? 8 : 0)
})
const sessionPanelMax = createMemo(() => {
const available = sessionPanelAvailable()
if (available === undefined) return 1000
return sessionPanelWidthMax({ available, split: splitReview() })
})
// Clamp at render time so window or sidebar resizes squeeze the chat panel
// instead of the review pane, without overwriting the persisted width.
const sessionPanelResizedWidth = createMemo(() =>
clampSessionPanelWidth({
width: layout.session.width(),
available: sessionPanelAvailable(),
split: splitReview(),
}),
)
const sessionPanelWidth = createMemo(() => { const sessionPanelWidth = createMemo(() => {
if (!desktopSidePanelOpen()) return "100%" if (!desktopSidePanelOpen()) return "100%"
if (desktopSessionResizeOpen()) return `${layout.session.width()}px` if (desktopSessionResizeOpen()) return `${sessionPanelResizedWidth()}px`
return `calc(100% - ${layout.fileTree.width()}px)` return `calc(100% - ${layout.fileTree.width()}px)`
}) })
const centered = createMemo(() => isDesktop() && !desktopReviewOpen()) const centered = createMemo(() => isDesktop() && !desktopReviewOpen())
@@ -572,8 +603,6 @@ export default function Page() {
}) })
let reviewFrame: number | undefined let reviewFrame: number | undefined
let todoFrame: number | undefined
let todoTimer: number | undefined
let diffFrame: number | undefined let diffFrame: number | undefined
let diffTimer: number | undefined let diffTimer: number | undefined
@@ -613,7 +642,8 @@ export default function Page() {
: store.mobileTab === "changes", : store.mobileTab === "changes",
) )
const vcsMode = createMemo<VcsMode | undefined>(() => { const vcsMode = createMemo<VcsMode | undefined>(() => {
if (store.changes === "git" || store.changes === "branch") return store.changes const mode = reviewMode()
if (mode === "git" || mode === "branch") return mode
}) })
const vcsKey = createMemo( const vcsKey = createMemo(
() => () =>
@@ -640,17 +670,63 @@ export default function Page() {
}) })
const refreshVcs = debounce(() => void queryClient.invalidateQueries({ queryKey: vcsKey() }), 100) const refreshVcs = debounce(() => void queryClient.invalidateQueries({ queryKey: vcsKey() }), 100)
const reviewDiffs = () => { const reviewDiffs = () => {
if (store.changes === "git" || store.changes === "branch") if (reviewMode() === "git" || reviewMode() === "branch")
// avoids suspense // avoids suspense
return vcsQuery.isFetched ? (vcsQuery.data ?? []) : [] return vcsQuery.isFetched ? (vcsQuery.data ?? []) : []
return turnDiffs() return turnDiffs()
} }
const activeReviewFile = () => {
const diffs = reviewDiffs()
const selected = reviewFile()
if (selected && diffs.some((diff) => diff.file === selected)) return selected
return diffs[0]?.file
}
const reviewCount = () => reviewDiffs().length const reviewCount = () => reviewDiffs().length
const hasReview = () => reviewCount() > 0 const hasReview = () => reviewCount() > 0
const reviewReady = () => { const reviewReady = () => {
if (store.changes === "git" || store.changes === "branch") return !vcsQuery.isPending if (reviewMode() === "git" || reviewMode() === "branch") return !vcsQuery.isPending
return true return true
} }
const loadReviewDiff = async (file: string, version?: number): Promise<VcsFileDiff | undefined> => {
const mode = vcsMode()
if (!mode) return
const root = reviewRootDirectory(sync().project?.worktree ?? sdk().directory)
const directory = reviewDiffDirectory(root, file)
const source = reviewDiffs().find((diff) => diff.file === file)
const valid = (diff: VcsFileDiff | undefined) => {
if (!diff || !source) return
if (diff.additions !== source.additions || diff.deletions !== source.deletions) return
if (reviewDiffNeedsLoad(diff)) return
return diff
}
const request = (scope: string, context?: number) =>
queryClient
.fetchQuery({
queryKey: [serverSDK().scope, ...vcsKey(), mode, "directory", scope, context, version] as const,
staleTime: Number.POSITIVE_INFINITY,
retry: 2,
queryFn: () =>
sdk()
.client.vcs.diff({ mode, directory: scope, context })
.then((result) => result.data ?? []),
})
.then((diffs) => diffs.find((diff) => diff.file === file))
if (directory !== root) {
try {
const scoped = valid(await request(directory))
if (scoped) return scoped
} catch (error) {
console.debug("[session-review] failed to load scoped vcs diff", { mode, file, directory, error })
}
}
try {
const bounded = valid(await request(root, 3))
if (bounded) return bounded
} catch (error) {
console.debug("[session-review] failed to load bounded vcs diff", { mode, file, root, error })
}
}
const newSessionWorktree = createMemo(() => { const newSessionWorktree = createMemo(() => {
if (store.newSessionWorktree === "create") return "create" if (store.newSessionWorktree === "create") return "create"
@@ -780,41 +856,6 @@ export default function Page() {
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
createEffect(
on(
() => {
const id = params.id
return [
sdk().directory,
id,
id ? (sync().data.session_status[id]?.type ?? "idle") : "idle",
id ? composer.blocked() : false,
] as const
},
([dir, id, status, blocked]) => {
if (todoFrame !== undefined) cancelAnimationFrame(todoFrame)
if (todoTimer !== undefined) window.clearTimeout(todoTimer)
todoFrame = undefined
todoTimer = undefined
if (!id) return
if (status === "idle" && !blocked) return
const cached = untrack(() => sync().data.todo[id] !== undefined)
todoFrame = requestAnimationFrame(() => {
todoFrame = undefined
todoTimer = window.setTimeout(() => {
todoTimer = undefined
if (sdk().directory !== dir || params.id !== id) return
untrack(() => {
void sync().session.todo(id, cached ? { force: true } : undefined)
})
}, 0)
})
},
{ defer: true },
),
)
createEffect( createEffect(
on( on(
() => visibleUserMessages().at(-1)?.id, () => visibleUserMessages().at(-1)?.id,
@@ -968,12 +1009,15 @@ export default function Page() {
} }
createEffect(() => { createEffect(() => {
if (!layout.ready()) return
if (sync().status !== "complete") return
if (!sync().project) return if (!sync().project) return
const list = changesOptions() const list = changesOptions()
if (list.includes(store.changes)) return const mode = reviewMode()
if (list.includes(mode)) return
const next = list[0] const next = list[0]
if (!next) return if (!next) return
setStore("changes", next) view().review.setMode(next)
}) })
createEffect( createEffect(
@@ -993,7 +1037,6 @@ export default function Page() {
const [tree, setTree] = createStore({ const [tree, setTree] = createStore({
reviewScroll: undefined as HTMLDivElement | undefined, reviewScroll: undefined as HTMLDivElement | undefined,
pendingDiff: undefined as string | undefined, pendingDiff: undefined as string | undefined,
activeDiff: undefined as string | undefined,
}) })
createEffect( createEffect(
@@ -1003,7 +1046,6 @@ export default function Page() {
setTree({ setTree({
reviewScroll: undefined, reviewScroll: undefined,
pendingDiff: undefined, pendingDiff: undefined,
activeDiff: undefined,
}) })
}, },
{ defer: true }, { defer: true },
@@ -1021,12 +1063,12 @@ export default function Page() {
} }
useComposerCommands() useComposerCommands()
useSettingsCommand()
useSessionCommands({ useSessionCommands({
navigateMessageByOffset, navigateMessageByOffset,
setActiveMessage, setActiveMessage,
focusInput, focusInput,
review: reviewTab, review: reviewTab,
fileBrowser: () => newSessionDesign() && isDesktop() && !!params.id,
}) })
const openReviewFile = createOpenReviewFile({ const openReviewFile = createOpenReviewFile({
@@ -1051,9 +1093,9 @@ export default function Page() {
return ( return (
<Select <Select
options={changesOptions()} options={changesOptions()}
current={store.changes} current={reviewMode()}
label={changesLabel} label={changesLabel}
onSelect={(option) => option && setStore("changes", option)} onSelect={(option) => option && view().review.setMode(option)}
variant="ghost" variant="ghost"
size="small" size="small"
valueClass="text-14-medium" valueClass="text-14-medium"
@@ -1070,11 +1112,11 @@ export default function Page() {
<SelectV2 <SelectV2
appearance="inline" appearance="inline"
options={changesOptions()} options={changesOptions()}
current={store.changes} current={reviewMode()}
label={changesLabel} label={changesLabel}
placement="bottom-start" placement="bottom-start"
gutter={6} gutter={6}
onSelect={(option) => option && setStore("changes", option)} onSelect={(option) => option && view().review.setMode(option)}
/> />
) )
} }
@@ -1102,18 +1144,18 @@ export default function Page() {
) )
const reviewEmptyText = createMemo(() => { const reviewEmptyText = createMemo(() => {
if (store.changes === "git") return language.t("session.review.noUncommittedChanges") if (reviewMode() === "git") return language.t("session.review.noUncommittedChanges")
if (store.changes === "branch") return language.t("session.review.noBranchChanges") if (reviewMode() === "branch") return language.t("session.review.noBranchChanges")
return language.t("session.review.noChanges") return language.t("session.review.noChanges")
}) })
const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => { const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => {
if (store.changes === "git" || store.changes === "branch") { if (reviewMode() === "git" || reviewMode() === "branch") {
if (!reviewReady()) return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> if (!reviewReady()) return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div>
return empty(reviewEmptyText()) return empty(reviewEmptyText())
} }
if (store.changes === "turn") { if (reviewMode() === "turn") {
if (nogit()) return createGit(input) if (nogit()) return createGit(input)
return empty(reviewEmptyText()) return empty(reviewEmptyText())
} }
@@ -1126,10 +1168,10 @@ export default function Page() {
} }
const reviewEmptyV2 = () => { const reviewEmptyV2 = () => {
if ((store.changes === "git" || store.changes === "branch") && !reviewReady()) { if ((reviewMode() === "git" || reviewMode() === "branch") && !reviewReady()) {
return <div class="px-6 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div> return <div class="px-6 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div>
} }
if (store.changes === "turn" && nogit()) { if (reviewMode() === "turn" && nogit()) {
return <SessionReviewEmptyNoGitV2 pending={gitMutation.isPending} onInitGit={initGit} /> return <SessionReviewEmptyNoGitV2 pending={gitMutation.isPending} onInitGit={initGit} />
} }
return <SessionReviewEmptyChangesV2 /> return <SessionReviewEmptyChangesV2 />
@@ -1151,7 +1193,7 @@ export default function Page() {
diffStyle={input.diffStyle} diffStyle={input.diffStyle}
onDiffStyleChange={input.onDiffStyleChange} onDiffStyleChange={input.onDiffStyleChange}
onScrollRef={(el) => setTree("reviewScroll", el)} onScrollRef={(el) => setTree("reviewScroll", el)}
focusedFile={tree.activeDiff} focusedFile={activeReviewFile()}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })} onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
onLineCommentUpdate={updateCommentInContext} onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext} onLineCommentDelete={removeCommentFromContext}
@@ -1182,8 +1224,12 @@ export default function Page() {
}, },
diffs: reviewDiffs, diffs: reviewDiffs,
diffsReady: reviewReady, diffsReady: reviewReady,
get diffVersion() {
return vcsQuery.dataUpdatedAt
},
loadDiff: loadReviewDiff,
get activeFile() { get activeFile() {
return tree.activeDiff return activeReviewFile()
}, },
onSelectFile: focusReviewDiff, onSelectFile: focusReviewDiff,
get diffStyle() { get diffStyle() {
@@ -1296,7 +1342,8 @@ export default function Page() {
const focusReviewDiff = (path: string) => { const focusReviewDiff = (path: string) => {
openReviewPanel() openReviewPanel()
view().review.openPath(path) view().review.openPath(path)
setTree({ activeDiff: path, pendingDiff: path }) view().review.setFile(path)
setTree("pendingDiff", path)
} }
createEffect(() => { createEffect(() => {
@@ -1890,8 +1937,6 @@ export default function Page() {
onCleanup(() => { onCleanup(() => {
if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame) if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame)
if (todoFrame !== undefined) cancelAnimationFrame(todoFrame)
if (todoTimer !== undefined) window.clearTimeout(todoTimer)
if (diffFrame !== undefined) cancelAnimationFrame(diffFrame) if (diffFrame !== undefined) cancelAnimationFrame(diffFrame)
if (diffTimer !== undefined) window.clearTimeout(diffTimer) if (diffTimer !== undefined) window.clearTimeout(diffTimer)
if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame) if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame)
@@ -1906,12 +1951,7 @@ export default function Page() {
sessionKey, sessionKey,
sessionID: () => params.id, sessionID: () => params.id,
prompt, prompt,
ready: () => !store.deferRender && messagesReady(),
centered, centered,
todo: {
collapsed: () => view().todoCollapsed.get(),
onToggle: () => view().todoCollapsed.set(!view().todoCollapsed.get()),
},
followup: () => followup: () =>
params.id && !isChildSession() params.id && !isChildSession()
? { ? {
@@ -2100,6 +2140,7 @@ export default function Page() {
<SessionRouteFrame> <SessionRouteFrame>
<SessionHeader /> <SessionHeader />
<div <div
ref={panelRow}
class="flex-1 min-h-0 flex flex-col md:flex-row" class="flex-1 min-h-0 flex flex-col md:flex-row"
classList={{ classList={{
"gap-2 p-2": settings.general.newLayoutDesigns(), "gap-2 p-2": settings.general.newLayoutDesigns(),
@@ -2138,9 +2179,9 @@ export default function Page() {
"-right-1": settings.general.newLayoutDesigns(), "-right-1": settings.general.newLayoutDesigns(),
}} }}
direction="horizontal" direction="horizontal"
size={layout.session.width()} size={sessionPanelResizedWidth()}
min={450} min={SESSION_PANEL_WIDTH_MIN}
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.45} max={sessionPanelMax()}
onResize={(width) => { onResize={(width) => {
size.touch() size.touch()
layout.session.resize(width) layout.session.resize(width)
@@ -2150,7 +2191,7 @@ export default function Page() {
</Show> </Show>
</div> </div>
<Show when={!newSessionDesign()}> <Show when={!newSessionDesign() && desktopSidePanelOpen()}>
<SessionSidePanel <SessionSidePanel
canReview={canReview} canReview={canReview}
diffs={reviewDiffs} diffs={reviewDiffs}
@@ -2160,7 +2201,7 @@ export default function Page() {
reviewHasFocusableContent={hasReview} reviewHasFocusableContent={hasReview}
reviewCount={reviewCount} reviewCount={reviewCount}
reviewPanel={reviewPanel} reviewPanel={reviewPanel}
activeDiff={tree.activeDiff} activeDiff={activeReviewFile()}
focusReviewDiff={focusReviewDiff} focusReviewDiff={focusReviewDiff}
reviewSnap={ui.reviewSnap} reviewSnap={ui.reviewSnap}
size={size} size={size}
@@ -2169,13 +2210,8 @@ export default function Page() {
<Show when={newSessionDesign()}> <Show when={newSessionDesign()}>
<Show when={isDesktop() ? desktopV2PanelLayout().visible : terminalOpen()}> <Show when={isDesktop() ? desktopV2PanelLayout().visible : terminalOpen()}>
<div class="min-w-0 h-full flex flex-1 flex-col"> <div class="min-w-0 h-full flex flex-1 flex-col">
<Show when={isDesktop()}> <Show when={isDesktop() && (desktopV2ReviewOpen() || desktopFileTreeOpen())}>
<div <div class="min-h-0 flex-1">
classList={{
"min-h-0 flex-1": desktopV2ReviewOpen() || desktopFileTreeOpen(),
"size-0 shrink-0 overflow-hidden": !(desktopV2ReviewOpen() || desktopFileTreeOpen()),
}}
>
<SessionSidePanel <SessionSidePanel
canReview={canReview} canReview={canReview}
diffs={reviewDiffs} diffs={reviewDiffs}
@@ -2185,7 +2221,8 @@ export default function Page() {
reviewHasFocusableContent={() => hasReview() || reviewV2State.sidebarOpened()} reviewHasFocusableContent={() => hasReview() || reviewV2State.sidebarOpened()}
reviewCount={reviewCount} reviewCount={reviewCount}
reviewPanel={reviewPanelV2} reviewPanel={reviewPanelV2}
activeDiff={tree.activeDiff} fileBrowserState={reviewV2State}
activeDiff={activeReviewFile()}
focusReviewDiff={focusReviewDiff} focusReviewDiff={focusReviewDiff}
reviewSnap={ui.reviewSnap} reviewSnap={ui.reviewSnap}
size={size} size={size}
@@ -1,7 +1,4 @@
import { createResizeObserver } from "@solid-primitives/resize-observer" import { type Accessor, createEffect, createMemo, createResource } from "solid-js"
import { useSpring } from "@opencode-ai/ui/motion-spring"
import { type Accessor, createEffect, createMemo, createResource, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import type { PromptInputState } from "@/components/prompt-input" import type { PromptInputState } from "@/components/prompt-input"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff" import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff"
@@ -26,12 +23,7 @@ export function createSessionComposerRegionController(input: {
sessionKey: Accessor<string> sessionKey: Accessor<string>
sessionID: Accessor<string | undefined> sessionID: Accessor<string | undefined>
prompt: PromptInputState prompt: PromptInputState
ready: Accessor<boolean>
centered: Accessor<boolean> centered: Accessor<boolean>
todo: {
collapsed: Accessor<boolean>
onToggle: () => void
}
followup: Accessor<SessionComposerFollowupDock | undefined> followup: Accessor<SessionComposerFollowupDock | undefined>
revert: Accessor<SessionComposerRevertDock | undefined> revert: Accessor<SessionComposerRevertDock | undefined>
onResponseSubmit: () => void onResponseSubmit: () => void
@@ -40,41 +32,6 @@ export function createSessionComposerRegionController(input: {
setDockRef: (el: HTMLDivElement) => void setDockRef: (el: HTMLDivElement) => void
}) { }) {
const sync = useSync() const sync = useSync()
const [store, setStore] = createStore({
ready: input.ready() || input.state.dock(),
height: 320,
body: undefined as HTMLDivElement | undefined,
})
let timer: number | undefined
let frame: number | undefined
const clear = () => {
if (timer !== undefined) window.clearTimeout(timer)
if (frame !== undefined) cancelAnimationFrame(frame)
timer = undefined
frame = undefined
}
createEffect(() => {
input.sessionKey()
const ready = input.ready()
const dock = input.state.dock()
clear()
if (store.ready || (!ready && !dock)) return
if (dock) {
setStore("ready", true)
return
}
frame = requestAnimationFrame(() => {
frame = undefined
timer = window.setTimeout(() => {
setStore("ready", true)
timer = undefined
}, 140)
})
})
createEffect(() => { createEffect(() => {
if (!input.prompt.ready()) return if (!input.prompt.ready()) return
@@ -92,27 +49,10 @@ export function createSessionComposerRegionController(input: {
}) })
}) })
createEffect(() => {
const el = store.body
if (!el) return
const update = () => setStore("height", el.getBoundingClientRect().height)
createResizeObserver(el, update)
update()
})
onCleanup(clear)
const parentID = createMemo(() => { const parentID = createMemo(() => {
const id = input.sessionID() const id = input.sessionID()
return id ? sync().session.get(id)?.parentID : undefined return id ? sync().session.get(id)?.parentID : undefined
}) })
const open = createMemo(() => store.ready && input.state.dock() && !input.state.closing())
const progress = useSpring(
() => (open() ? 1 : 0),
{ visualDuration: 0.3, bounce: 0 },
() => `${input.sessionKey()}\0${store.ready}`,
)
const value = createMemo(() => Math.max(0, Math.min(1, progress())))
const ready = Promise.resolve() const ready = Promise.resolve()
const [promptReady] = createResource( const [promptReady] = createResource(
() => input.prompt.ready.promise ?? ready, () => input.prompt.ready.promise ?? ready,
@@ -122,7 +62,6 @@ export function createSessionComposerRegionController(input: {
return { return {
state: input.state, state: input.state,
centered: input.centered, centered: input.centered,
todo: input.todo,
followup: input.followup, followup: input.followup,
revert: input.revert, revert: input.revert,
onResponseSubmit: input.onResponseSubmit, onResponseSubmit: input.onResponseSubmit,
@@ -134,11 +73,7 @@ export function createSessionComposerRegionController(input: {
showComposer: () => !input.state.blocked() || !!parentID(), showComposer: () => !input.state.blocked() || !!parentID(),
handoffPrompt: () => getSessionHandoff(input.sessionKey())?.prompt, handoffPrompt: () => getSessionHandoff(input.sessionKey())?.prompt,
promptReady: () => input.prompt.ready() || promptReady(), promptReady: () => input.prompt.ready() || promptReady(),
dock: () => (store.ready && input.state.dock()) || value() > 0.001, lift: () => (input.revert()?.items.length ? 18 : 0),
dockProgress: value,
dockHeight: () => Math.max(78, store.height),
lift: () => (input.revert()?.items.length ? 18 : 36 * value()),
setDockBodyRef: (el: HTMLDivElement) => setStore("body", el),
} }
} }
@@ -5,7 +5,6 @@ import { SessionPermissionDock } from "@/pages/session/composer/session-permissi
import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock" import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock"
import { SessionFollowupDock } from "@/pages/session/composer/session-followup-dock" import { SessionFollowupDock } from "@/pages/session/composer/session-followup-dock"
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock" import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
import { SessionTodoDock } from "@/pages/session/composer/session-todo-dock"
import type { SessionComposerRegionController } from "./session-composer-region-controller" import type { SessionComposerRegionController } from "./session-composer-region-controller"
export function SessionComposerRegion(props: { export function SessionComposerRegion(props: {
@@ -60,28 +59,6 @@ export function SessionComposerRegion(props: {
</Show> </Show>
<Show when={controller.showComposer()}> <Show when={controller.showComposer()}>
<Show when={controller.dock()}>
<div
classList={{
"overflow-hidden": true,
"pointer-events-none": controller.dockProgress() < 0.98,
}}
style={{
"max-height": `${controller.dockHeight() * controller.dockProgress()}px`,
}}
>
<div ref={controller.setDockBodyRef}>
<SessionTodoDock
todos={controller.state.todos()}
collapsed={controller.todo.collapsed()}
onToggle={controller.todo.onToggle}
collapseLabel={language.t("session.todo.collapse")}
expandLabel={language.t("session.todo.expand")}
dockProgress={controller.dockProgress()}
/>
</div>
</div>
</Show>
<Show <Show
when={controller.promptReady()} when={controller.promptReady()}
fallback={ fallback={
@@ -98,10 +75,7 @@ export function SessionComposerRegion(props: {
</div> </div>
)} )}
</Show> </Show>
<div <div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"
style={{ "margin-top": `${-36 * controller.dockProgress()}px` }}
>
{controller.handoffPrompt() || language.t("prompt.loading")} {controller.handoffPrompt() || language.t("prompt.loading")}
</div> </div>
</> </>
@@ -109,11 +83,7 @@ export function SessionComposerRegion(props: {
> >
<Show when={rolled()} keyed> <Show when={rolled()} keyed>
{(revert) => ( {(revert) => (
<div <div>
style={{
"margin-top": `${-36 * controller.dockProgress()}px`,
}}
>
<SessionRevertDock <SessionRevertDock
items={revert.items} items={revert.items}
restoring={revert.restoring} restoring={revert.restoring}
@@ -1,6 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client" import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
import { todoDockAtBoundary, todoState } from "./session-composer-state"
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree" import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
const session = (input: { id: string; parentID?: string }) => const session = (input: { id: string; parentID?: string }) =>
@@ -104,35 +103,3 @@ describe("sessionQuestionRequest", () => {
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand") expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand")
}) })
}) })
describe("todoState", () => {
test("hides when there are no todos", () => {
expect(todoState({ count: 0, done: false, live: true })).toBe("hide")
})
test("opens while the session is still working", () => {
expect(todoState({ count: 2, done: false, live: true })).toBe("open")
})
test("closes completed todos after a running turn", () => {
expect(todoState({ count: 2, done: true, live: true })).toBe("close")
})
test("clears stale todos when the turn ends", () => {
expect(todoState({ count: 2, done: false, live: false })).toBe("clear")
})
test("clears completed todos when the session is no longer live", () => {
expect(todoState({ count: 2, done: true, live: false })).toBe("clear")
})
})
describe("todoDockAtBoundary", () => {
test("shows active todos when entering a session", () => {
expect(todoDockAtBoundary("open")).toBe(true)
})
test("hides completed todos when entering a session", () => {
expect(todoDockAtBoundary("close")).toBe(false)
})
})

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