Skip to content

Commit c01d88a

Browse files
authored
Merge branch 'dev' into fix/lsp-incremental-sync
2 parents b924b70 + 69b7f3b commit c01d88a

69 files changed

Lines changed: 1733 additions & 698 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nix/hashes.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"nodeModules": {
3-
"x86_64-linux": "sha256-NczRp8MPppkqP8PQfWMUWJ/Wofvf2YVy5m4i22Pi3jg=",
4-
"aarch64-linux": "sha256-QIxGOu8Fj+sWgc9hKvm1BLiIErxEtd17SPlwZGac9sQ=",
5-
"aarch64-darwin": "sha256-Rb9qbMM+ARn0iBCaZurwcoUBCplbMXEZwrXVKextp3I=",
6-
"x86_64-darwin": "sha256-KVxOKkaVV7W+K4reEk14MTLgmtoqwCYDqDNXNeS6ync="
3+
"x86_64-linux": "sha256-AgHhYsiygxbsBo3JN4HqHXKAwh8n1qeuSCe2qqxlxW4=",
4+
"aarch64-linux": "sha256-h2lpWRQ5EDYnjpqZXtUAp1mxKLQxJ4m8MspgSY8Ev78=",
5+
"aarch64-darwin": "sha256-xnd91+WyeAqn06run2ajsekxJvTMiLsnqNPe/rR8VTM=",
6+
"x86_64-darwin": "sha256-rXpz45IOjGEk73xhP9VY86eOj2CZBg2l1vzwzTIOOOQ="
77
}
88
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "AI-powered development tool",
55
"private": true,
66
"type": "module",
7-
"packageManager": "[email protected].11",
7+
"packageManager": "[email protected].13",
88
"scripts": {
99
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
1010
"dev:desktop": "bun --cwd packages/desktop-electron dev",
@@ -30,7 +30,7 @@
3030
"@effect/opentelemetry": "4.0.0-beta.48",
3131
"@effect/platform-node": "4.0.0-beta.48",
3232
"@npmcli/arborist": "9.4.0",
33-
"@types/bun": "1.3.11",
33+
"@types/bun": "1.3.12",
3434
"@types/cross-spawn": "6.0.6",
3535
"@octokit/rest": "22.0.0",
3636
"@hono/zod-validator": "0.4.2",

packages/app/src/components/dialog-edit-project.tsx

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { type LocalProject, getAvatarColors } from "@/context/layout"
1212
import { getFilename } from "@opencode-ai/shared/util/path"
1313
import { Avatar } from "@opencode-ai/ui/avatar"
1414
import { useLanguage } from "@/context/language"
15+
import { getProjectAvatarSource } from "@/pages/layout/sidebar-items"
1516

1617
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
1718

@@ -26,8 +27,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
2627

2728
const [store, setStore] = createStore({
2829
name: defaultName(),
29-
color: props.project.icon?.color || "pink",
30-
iconUrl: props.project.icon?.override || "",
30+
color: props.project.icon?.color,
31+
iconOverride: props.project.icon?.override,
3132
startup: props.project.commands?.start ?? "",
3233
dragOver: false,
3334
iconHover: false,
@@ -39,7 +40,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
3940
if (!file.type.startsWith("image/")) return
4041
const reader = new FileReader()
4142
reader.onload = (e) => {
42-
setStore("iconUrl", e.target?.result as string)
43+
setStore("iconOverride", e.target?.result as string)
4344
setStore("iconHover", false)
4445
}
4546
reader.readAsDataURL(file)
@@ -68,7 +69,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
6869
}
6970

7071
function clearIcon() {
71-
setStore("iconUrl", "")
72+
setStore("iconOverride", "")
7273
}
7374

7475
const saveMutation = useMutation(() => ({
@@ -81,17 +82,17 @@ export function DialogEditProject(props: { project: LocalProject }) {
8182
projectID: props.project.id,
8283
directory: props.project.worktree,
8384
name,
84-
icon: { color: store.color, override: store.iconUrl },
85+
icon: { color: store.color || "", override: store.iconOverride || "" },
8586
commands: { start },
8687
})
87-
globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
88+
globalSync.project.icon(props.project.worktree, store.iconOverride || undefined)
8889
dialog.close()
8990
return
9091
}
9192

9293
globalSync.project.meta(props.project.worktree, {
9394
name,
94-
icon: { color: store.color, override: store.iconUrl || undefined },
95+
icon: { color: store.color || undefined, override: store.iconOverride || undefined },
9596
commands: { start: start || undefined },
9697
})
9798
dialog.close()
@@ -130,21 +131,25 @@ export function DialogEditProject(props: { project: LocalProject }) {
130131
classList={{
131132
"border-text-interactive-base bg-surface-info-base/20": store.dragOver,
132133
"border-border-base hover:border-border-strong": !store.dragOver,
133-
"overflow-hidden": !!store.iconUrl,
134+
"overflow-hidden": !!store.iconOverride,
134135
}}
135136
onDrop={handleDrop}
136137
onDragOver={handleDragOver}
137138
onDragLeave={handleDragLeave}
138139
onClick={() => {
139-
if (store.iconUrl && store.iconHover) {
140+
if (store.iconOverride && store.iconHover) {
140141
clearIcon()
141142
} else {
142143
iconInput?.click()
143144
}
144145
}}
145146
>
146147
<Show
147-
when={store.iconUrl}
148+
when={getProjectAvatarSource(props.project.id, {
149+
color: store.color,
150+
url: props.project.icon?.url,
151+
override: store.iconOverride,
152+
})}
148153
fallback={
149154
<div class="size-full flex items-center justify-center">
150155
<Avatar
@@ -155,27 +160,29 @@ export function DialogEditProject(props: { project: LocalProject }) {
155160
</div>
156161
}
157162
>
158-
<img
159-
src={store.iconUrl}
160-
alt={language.t("dialog.project.edit.icon.alt")}
161-
class="size-full object-cover"
162-
/>
163+
{(src) => (
164+
<img
165+
src={src()}
166+
alt={language.t("dialog.project.edit.icon.alt")}
167+
class="size-full object-cover"
168+
/>
169+
)}
163170
</Show>
164171
</div>
165172
<div
166173
class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
167174
classList={{
168-
"opacity-100": store.iconHover && !store.iconUrl,
169-
"opacity-0": !(store.iconHover && !store.iconUrl),
175+
"opacity-100": store.iconHover && !store.iconOverride,
176+
"opacity-0": !(store.iconHover && !store.iconOverride),
170177
}}
171178
>
172179
<Icon name="cloud-upload" size="large" class="text-icon-on-interactive-base drop-shadow-sm" />
173180
</div>
174181
<div
175182
class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
176183
classList={{
177-
"opacity-100": store.iconHover && !!store.iconUrl,
178-
"opacity-0": !(store.iconHover && !!store.iconUrl),
184+
"opacity-100": store.iconHover && !!store.iconOverride,
185+
"opacity-0": !(store.iconHover && !!store.iconOverride),
179186
}}
180187
>
181188
<Icon name="trash" size="large" class="text-icon-on-interactive-base drop-shadow-sm" />
@@ -198,7 +205,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
198205
</div>
199206
</div>
200207

201-
<Show when={!store.iconUrl}>
208+
<Show when={!store.iconOverride}>
202209
<div class="flex flex-col gap-2">
203210
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label>
204211
<div class="flex gap-1.5">
@@ -215,7 +222,10 @@ export function DialogEditProject(props: { project: LocalProject }) {
215222
"bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base":
216223
store.color !== color,
217224
}}
218-
onClick={() => setStore("color", color)}
225+
onClick={() => {
226+
if (store.color === color && !props.project.icon?.url) return
227+
setStore("color", store.color === color ? undefined : color)
228+
}}
219229
>
220230
<Avatar
221231
fallback={store.name || defaultName()}

packages/app/src/context/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
516516
}
517517

518518
for (const project of projects) {
519-
if (project.icon?.color) continue
519+
if (project.icon?.color || project.icon.url) continue
520520
const worktree = project.worktree
521521
const existing = colors[worktree]
522522
const color = existing ?? pickAvailableColor(used)

packages/app/src/pages/layout/sidebar-items.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ import { childSessionOnPath, hasProjectPermissions } from "./helpers"
1919

2020
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
2121

22+
export function getProjectAvatarSource(id?: string, icon?: { color?: string; url?: string; override?: string }) {
23+
return id === OPENCODE_PROJECT_ID
24+
? "https://opencode.ai/favicon.svg"
25+
: icon?.color
26+
? undefined
27+
: icon?.override || icon?.url
28+
}
29+
2230
export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => {
2331
const globalSync = useGlobalSync()
2432
const notification = useNotification()
@@ -42,11 +50,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
4250
<div class="size-full rounded overflow-clip">
4351
<Avatar
4452
fallback={name()}
45-
src={
46-
props.project.id === OPENCODE_PROJECT_ID
47-
? "https://opencode.ai/favicon.svg"
48-
: props.project.icon?.override || props.project.icon?.url
49-
}
53+
src={getProjectAvatarSource(props.project.id, props.project.icon)}
5054
{...getAvatarColors(props.project.icon?.color)}
5155
class="size-full rounded"
5256
classList={{ "badge-mask": notify() }}

packages/console/app/src/i18n/ar.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export const dict = {
249249

250250
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
251251
"go.meta.description":
252-
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
252+
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
253253
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
254254
"go.hero.body":
255255
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
@@ -300,7 +300,7 @@ export const dict = {
300300
"go.problem.item2": "حدود سخية ووصول موثوق",
301301
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
302302
"go.problem.item4":
303-
"يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7",
303+
"يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7",
304304
"go.how.title": "كيف يعمل Go",
305305
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
306306
"go.how.step1.title": "أنشئ حسابًا",
@@ -324,7 +324,7 @@ export const dict = {
324324
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
325325
"go.faq.q3": "هل Go هو نفسه Zen؟",
326326
"go.faq.a3":
327-
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
327+
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.",
328328
"go.faq.q4": "كم تكلفة Go؟",
329329
"go.faq.a4.p1.beforePricing": "تكلفة Go",
330330
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
@@ -347,7 +347,7 @@ export const dict = {
347347

348348
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
349349
"go.faq.a9":
350-
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
350+
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
351351

352352
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
353353
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",

0 commit comments

Comments
 (0)