Compare commits
125
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af79d77aa8 | ||
|
|
fc46cef5fd | ||
|
|
799996db76 | ||
|
|
841e2fc1a8 | ||
|
|
df75bfe07c | ||
|
|
c818c9dcb6 | ||
|
|
c36ab3f935 | ||
|
|
e3c983c21f | ||
|
|
a196569f51 | ||
|
|
19da27e1cb | ||
|
|
4a737493ac | ||
|
|
ec301f6a2c | ||
|
|
ac7a885ae9 | ||
|
|
7f2b5ee8c2 | ||
|
|
15784aa036 | ||
|
|
edbc02855d | ||
|
|
a43d3e0e1e | ||
|
|
ae25278eda | ||
|
|
6869186fc6 | ||
|
|
f8c6742e54 | ||
|
|
014dbd34c4 | ||
|
|
21ae91b4f2 | ||
|
|
bb3f14119b | ||
|
|
6f165e23de | ||
|
|
cef0c8ac84 | ||
|
|
dd8bb44d1d | ||
|
|
30868f52ea | ||
|
|
9c88235121 | ||
|
|
4e14f79511 | ||
|
|
e0396b809a | ||
|
|
319498e2fd | ||
|
|
2ba9aa2196 | ||
|
|
114eeb21dc | ||
|
|
1cf8123bc6 | ||
|
|
db6a038829 | ||
|
|
6ff833a22b | ||
|
|
5c401673b2 | ||
|
|
e8ce5df414 | ||
|
|
b8799be3c8 | ||
|
|
7ded0ec9e9 | ||
|
|
f5d0371efe | ||
|
|
22e64cac67 | ||
|
|
2a1305f231 | ||
|
|
e691e8f274 | ||
|
|
cc6dd5321c | ||
|
|
2c17e3a4db | ||
|
|
98f5e6e713 | ||
|
|
474e311f6f | ||
|
|
626a488fb8 | ||
|
|
a300a6cc7a | ||
|
|
fe594693a4 | ||
|
|
98e091796b | ||
|
|
3c4b4d5faf | ||
|
|
b6ff1b18c7 | ||
|
|
9c9bc09f52 | ||
|
|
d6e06c8950 | ||
|
|
844fb71938 | ||
|
|
fbb7b5b1bf | ||
|
|
95280ebec9 | ||
|
|
fea9a0bd4c | ||
|
|
30c4fcb1a5 | ||
|
|
193c169ca5 | ||
|
|
cee04f2924 | ||
|
|
1219691c11 | ||
|
|
b2cc40f09c | ||
|
|
0b2e65f16d | ||
|
|
1ea01fdad0 | ||
|
|
f8aa4a3be0 | ||
|
|
293bb422fa | ||
|
|
54a78c9224 | ||
|
|
9b30ee2db2 | ||
|
|
ba1ec62caf | ||
|
|
72ec05d0be | ||
|
|
0b702704ae | ||
|
|
3480cef52e | ||
|
|
dcfe4b0d51 | ||
|
|
b2e3dc87ea | ||
|
|
233fc5b910 | ||
|
|
2dffdfff4a | ||
|
|
a4ab1408eb | ||
|
|
e41843eaf7 | ||
|
|
bf979413f9 | ||
|
|
38b0cdc149 | ||
|
|
344ccc647b | ||
|
|
b9b854bf9f | ||
|
|
d9c18381a6 | ||
|
|
63a175b50d | ||
|
|
889f979c0b | ||
|
|
2abc4507b2 | ||
|
|
aa3c99a3c0 | ||
|
|
d49d217e9d | ||
|
|
043a5c7c0d | ||
|
|
901d1171a6 | ||
|
|
518503b29b | ||
|
|
c235ba1bef | ||
|
|
754a1fb712 | ||
|
|
acca2e92dc | ||
|
|
9d178e0944 | ||
|
|
7c8cf6ca5b | ||
|
|
89afac3d9d | ||
|
|
b4c60e1b21 | ||
|
|
efd8024430 | ||
|
|
2f05676e04 | ||
|
|
5013e8a8ec | ||
|
|
6e7c9eb820 | ||
|
|
8555de8189 | ||
|
|
f5c3d352a1 | ||
|
|
e117397d0f | ||
|
|
1fbc13a1b4 | ||
|
|
6409aceb1a | ||
|
|
837cc92586 | ||
|
|
ca77b8f8e9 | ||
|
|
25547e9337 | ||
|
|
12f3d1f505 | ||
|
|
8e182c7782 | ||
|
|
8a797ed9a1 | ||
|
|
25ecf0af6b | ||
|
|
576480b5dc | ||
|
|
fdb4b7c4a5 | ||
|
|
726ae6f541 | ||
|
|
773078e81f | ||
|
|
811954880e | ||
|
|
465c83cf82 | ||
|
|
bb9b81aa37 | ||
|
|
a20446fcc8 |
@@ -13,3 +13,4 @@ R44VC0RP
|
|||||||
rekram1-node
|
rekram1-node
|
||||||
thdxr
|
thdxr
|
||||||
simonklee
|
simonklee
|
||||||
|
vimtor
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ jobs:
|
|||||||
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
||||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||||
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||||
|
HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"provider": {},
|
"provider": {},
|
||||||
"permission": {
|
"permission": {
|
||||||
"edit": {
|
"edit": {
|
||||||
"packages/opencode/migration/*": "deny",
|
"packages/opencode/migration/*": "ask",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"mcp": {},
|
"mcp": {},
|
||||||
|
|||||||
+324
-212
@@ -1,37 +1,89 @@
|
|||||||
/** @jsxImportSource @opentui/solid */
|
/** @jsxImportSource @opentui/solid */
|
||||||
import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid"
|
import { useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||||
import { RGBA, VignetteEffect } from "@opentui/core"
|
import { useBindings, useKeymapSelector } from "@opentui/keymap/solid"
|
||||||
import type {
|
import { RGBA, VignetteEffect, type KeyEvent, type Renderable } from "@opentui/core"
|
||||||
TuiKeybindSet,
|
import { resolveBindingSections, type BindingSectionsConfig, type BindingValue } from "@opentui/keymap/extras"
|
||||||
TuiPlugin,
|
import type { Binding } from "@opentui/keymap"
|
||||||
TuiPluginApi,
|
import type { TuiPlugin, TuiPluginApi, TuiPluginMeta, TuiPluginModule, TuiSlotPlugin } from "@opencode-ai/plugin/tui"
|
||||||
TuiPluginMeta,
|
|
||||||
TuiPluginModule,
|
|
||||||
TuiSlotPlugin,
|
|
||||||
} from "@opencode-ai/plugin/tui"
|
|
||||||
|
|
||||||
const tabs = ["overview", "counter", "help"]
|
const tabs = ["overview", "counter", "help"]
|
||||||
const bind = {
|
const command = {
|
||||||
modal: "ctrl+shift+m",
|
modal: "plugin.smoke.modal",
|
||||||
screen: "ctrl+shift+o",
|
screen: "plugin.smoke.screen",
|
||||||
home: "escape,ctrl+h",
|
alert: "plugin.smoke.alert",
|
||||||
left: "left,h",
|
confirm: "plugin.smoke.confirm",
|
||||||
right: "right,l",
|
prompt: "plugin.smoke.prompt",
|
||||||
up: "up,k",
|
select: "plugin.smoke.select",
|
||||||
down: "down,j",
|
host: "plugin.smoke.host",
|
||||||
alert: "a",
|
home: "plugin.smoke.home",
|
||||||
confirm: "c",
|
toast: "plugin.smoke.toast",
|
||||||
prompt: "p",
|
dialog_close: "plugin.smoke.dialog.close",
|
||||||
select: "s",
|
local_push: "plugin.smoke.local.push",
|
||||||
modal_accept: "enter,return",
|
local_pop: "plugin.smoke.local.pop",
|
||||||
modal_close: "escape",
|
screen_home: "plugin.smoke.screen.home",
|
||||||
dialog_close: "escape",
|
screen_left: "plugin.smoke.screen.left",
|
||||||
local: "x",
|
screen_right: "plugin.smoke.screen.right",
|
||||||
local_push: "enter,return",
|
screen_up: "plugin.smoke.screen.up",
|
||||||
local_close: "q,backspace",
|
screen_down: "plugin.smoke.screen.down",
|
||||||
host: "z",
|
screen_modal: "plugin.smoke.screen.modal",
|
||||||
|
screen_local: "plugin.smoke.screen.local",
|
||||||
|
screen_host: "plugin.smoke.screen.host",
|
||||||
|
screen_alert: "plugin.smoke.screen.alert",
|
||||||
|
screen_confirm: "plugin.smoke.screen.confirm",
|
||||||
|
screen_prompt: "plugin.smoke.screen.prompt",
|
||||||
|
screen_select: "plugin.smoke.screen.select",
|
||||||
|
modal_accept: "plugin.smoke.modal.accept",
|
||||||
|
modal_close: "plugin.smoke.modal.close",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const sectionNames = ["global", "dialog", "local", "screen", "modal"] as const
|
||||||
|
type SectionName = (typeof sectionNames)[number]
|
||||||
|
type SectionConfig = Record<string, BindingValue<Renderable, KeyEvent>>
|
||||||
|
type ResolvedSections = Record<SectionName, Binding<Renderable, KeyEvent>[]>
|
||||||
|
type SmokeKeymap = {
|
||||||
|
sections?: Partial<Record<SectionName, SectionConfig>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SmokeOptions = {
|
||||||
|
enabled?: boolean
|
||||||
|
label?: unknown
|
||||||
|
route?: unknown
|
||||||
|
vignette?: unknown
|
||||||
|
keymap?: SmokeKeymap
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultKeymap = {
|
||||||
|
global: {
|
||||||
|
[command.modal]: "ctrl+shift+m",
|
||||||
|
[command.screen]: "ctrl+shift+o",
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
[command.dialog_close]: "escape",
|
||||||
|
},
|
||||||
|
local: {
|
||||||
|
[command.local_push]: "enter,return",
|
||||||
|
[command.local_pop]: "escape,q,backspace",
|
||||||
|
},
|
||||||
|
screen: {
|
||||||
|
[command.screen_home]: "escape,ctrl+h",
|
||||||
|
[command.screen_left]: "left,h",
|
||||||
|
[command.screen_right]: "right,l",
|
||||||
|
[command.screen_up]: "up,k",
|
||||||
|
[command.screen_down]: "down,j",
|
||||||
|
[command.screen_modal]: "ctrl+shift+m",
|
||||||
|
[command.screen_local]: "x",
|
||||||
|
[command.screen_host]: "z",
|
||||||
|
[command.screen_alert]: "a",
|
||||||
|
[command.screen_confirm]: "c",
|
||||||
|
[command.screen_prompt]: "p",
|
||||||
|
[command.screen_select]: "s",
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
[command.modal_accept]: "enter,return",
|
||||||
|
[command.modal_close]: "escape",
|
||||||
|
},
|
||||||
|
} satisfies Record<SectionName, SectionConfig>
|
||||||
|
|
||||||
const pick = (value: unknown, fallback: string) => {
|
const pick = (value: unknown, fallback: string) => {
|
||||||
if (typeof value !== "string") return fallback
|
if (typeof value !== "string") return fallback
|
||||||
if (!value.trim()) return fallback
|
if (!value.trim()) return fallback
|
||||||
@@ -43,16 +95,11 @@ const num = (value: unknown, fallback: number) => {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
const rec = (value: unknown) => {
|
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) return
|
|
||||||
return Object.fromEntries(Object.entries(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cfg = {
|
type Cfg = {
|
||||||
label: string
|
label: string
|
||||||
route: string
|
route: string
|
||||||
vignette: number
|
vignette: number
|
||||||
keybinds: Record<string, unknown> | undefined
|
keymap: SmokeKeymap | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
type Route = {
|
type Route = {
|
||||||
@@ -69,12 +116,12 @@ type State = {
|
|||||||
local: number
|
local: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = (options: Record<string, unknown> | undefined) => {
|
const cfg = (options: SmokeOptions | undefined) => {
|
||||||
return {
|
return {
|
||||||
label: pick(options?.label, "smoke"),
|
label: pick(options?.label, "smoke"),
|
||||||
route: pick(options?.route, "workspace-smoke"),
|
route: pick(options?.route, "workspace-smoke"),
|
||||||
vignette: Math.max(0, num(options?.vignette, 0.35)),
|
vignette: Math.max(0, num(options?.vignette, 0.35)),
|
||||||
keybinds: rec(options?.keybinds),
|
keymap: options?.keymap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +132,25 @@ const names = (input: Cfg) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Keys = TuiKeybindSet
|
function createKeys(input: SmokeKeymap | undefined): { sections: ResolvedSections } {
|
||||||
|
const sections = resolveBindingSections(
|
||||||
|
{
|
||||||
|
global: { ...defaultKeymap.global, ...input?.sections?.global },
|
||||||
|
dialog: { ...defaultKeymap.dialog, ...input?.sections?.dialog },
|
||||||
|
local: { ...defaultKeymap.local, ...input?.sections?.local },
|
||||||
|
screen: { ...defaultKeymap.screen, ...input?.sections?.screen },
|
||||||
|
modal: { ...defaultKeymap.modal, ...input?.sections?.modal },
|
||||||
|
} satisfies BindingSectionsConfig<Renderable, KeyEvent>,
|
||||||
|
{ sections: sectionNames },
|
||||||
|
).sections
|
||||||
|
|
||||||
|
return {
|
||||||
|
sections,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Keys = ReturnType<typeof createKeys>
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
panel: "#1d1d1d",
|
panel: "#1d1d1d",
|
||||||
border: "#4a4a4a",
|
border: "#4a4a4a",
|
||||||
@@ -292,125 +357,161 @@ const Screen = (props: {
|
|||||||
}
|
}
|
||||||
const pop = (base?: State) => {
|
const pop = (base?: State) => {
|
||||||
const next = base ?? current(props.api, props.route)
|
const next = base ?? current(props.api, props.route)
|
||||||
const local = Math.max(0, next.local - 1)
|
set(Math.max(0, next.local - 1), next)
|
||||||
set(local, next)
|
|
||||||
}
|
}
|
||||||
const show = () => {
|
const show = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
open()
|
open()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
useKeyboard((evt) => {
|
const screenActive = () => props.api.route.current.name === props.route.screen
|
||||||
if (props.api.route.current.name !== props.route.screen) return
|
|
||||||
const next = current(props.api, props.route)
|
useBindings(() => ({
|
||||||
if (props.api.ui.dialog.open) {
|
enabled: () => screenActive() && props.api.ui.dialog.open,
|
||||||
if (props.keys.match("dialog_close", evt)) {
|
commands: [
|
||||||
evt.preventDefault()
|
{
|
||||||
evt.stopPropagation()
|
name: command.dialog_close,
|
||||||
|
run() {
|
||||||
props.api.ui.dialog.clear()
|
props.api.ui.dialog.clear()
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
return
|
],
|
||||||
}
|
bindings: props.keys.sections.dialog,
|
||||||
|
}))
|
||||||
|
|
||||||
if (next.local > 0) {
|
useBindings(() => ({
|
||||||
if (evt.name === "escape" || props.keys.match("local_close", evt)) {
|
enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local > 0,
|
||||||
evt.preventDefault()
|
commands: [
|
||||||
evt.stopPropagation()
|
{
|
||||||
pop(next)
|
name: command.local_push,
|
||||||
return
|
run() {
|
||||||
}
|
push(current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: command.local_pop,
|
||||||
|
run() {
|
||||||
|
pop(current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bindings: props.keys.sections.local,
|
||||||
|
}))
|
||||||
|
|
||||||
if (props.keys.match("local_push", evt)) {
|
useBindings(() => ({
|
||||||
evt.preventDefault()
|
enabled: () => screenActive() && !props.api.ui.dialog.open && current(props.api, props.route).local === 0,
|
||||||
evt.stopPropagation()
|
commands: [
|
||||||
push(next)
|
{
|
||||||
return
|
name: command.screen_home,
|
||||||
}
|
run() {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("home", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate("home")
|
props.api.route.navigate("home")
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("left", evt)) {
|
name: command.screen_left,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
const next = current(props.api, props.route)
|
||||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length })
|
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab - 1 + tabs.length) % tabs.length })
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("right", evt)) {
|
name: command.screen_right,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
const next = current(props.api, props.route)
|
||||||
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length })
|
props.api.route.navigate(props.route.screen, { ...next, tab: (next.tab + 1) % tabs.length })
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("up", evt)) {
|
name: command.screen_up,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
const next = current(props.api, props.route)
|
||||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 })
|
props.api.route.navigate(props.route.screen, { ...next, count: next.count + 1 })
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("down", evt)) {
|
name: command.screen_down,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
const next = current(props.api, props.route)
|
||||||
props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 })
|
props.api.route.navigate(props.route.screen, { ...next, count: next.count - 1 })
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("modal", evt)) {
|
name: command.screen_modal,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
props.api.route.navigate(props.route.modal, current(props.api, props.route))
|
||||||
props.api.route.navigate(props.route.modal, next)
|
},
|
||||||
return
|
},
|
||||||
}
|
{
|
||||||
|
name: command.screen_local,
|
||||||
if (props.keys.match("local", evt)) {
|
run() {
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
open()
|
open()
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("host", evt)) {
|
name: command.screen_host,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
|
||||||
host(props.api, props.input, skin)
|
host(props.api, props.input, skin)
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: command.screen_alert,
|
||||||
|
run() {
|
||||||
|
warn(props.api, props.route, current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: command.screen_confirm,
|
||||||
|
run() {
|
||||||
|
check(props.api, props.route, current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: command.screen_prompt,
|
||||||
|
run() {
|
||||||
|
entry(props.api, props.route, current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: command.screen_select,
|
||||||
|
run() {
|
||||||
|
picker(props.api, props.route, current(props.api, props.route))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bindings: props.keys.sections.screen,
|
||||||
|
}))
|
||||||
|
const shortcuts = useKeymapSelector((keymap) => {
|
||||||
|
const bindings = keymap.getCommandBindings({
|
||||||
|
visibility: "registered",
|
||||||
|
commands: [
|
||||||
|
command.screen_home,
|
||||||
|
command.screen_up,
|
||||||
|
command.screen_down,
|
||||||
|
command.screen_modal,
|
||||||
|
command.screen_alert,
|
||||||
|
command.screen_confirm,
|
||||||
|
command.screen_prompt,
|
||||||
|
command.screen_select,
|
||||||
|
command.screen_local,
|
||||||
|
command.screen_host,
|
||||||
|
command.local_push,
|
||||||
|
command.local_pop,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
if (props.keys.match("alert", evt)) {
|
return {
|
||||||
evt.preventDefault()
|
screen_home: props.api.keys.formatBindings(bindings.get(command.screen_home)) ?? "",
|
||||||
evt.stopPropagation()
|
screen_up: props.api.keys.formatBindings(bindings.get(command.screen_up)) ?? "",
|
||||||
warn(props.api, props.route, next)
|
screen_down: props.api.keys.formatBindings(bindings.get(command.screen_down)) ?? "",
|
||||||
return
|
screen_modal: props.api.keys.formatBindings(bindings.get(command.screen_modal)) ?? "",
|
||||||
}
|
screen_alert: props.api.keys.formatBindings(bindings.get(command.screen_alert)) ?? "",
|
||||||
|
screen_confirm: props.api.keys.formatBindings(bindings.get(command.screen_confirm)) ?? "",
|
||||||
if (props.keys.match("confirm", evt)) {
|
screen_prompt: props.api.keys.formatBindings(bindings.get(command.screen_prompt)) ?? "",
|
||||||
evt.preventDefault()
|
screen_select: props.api.keys.formatBindings(bindings.get(command.screen_select)) ?? "",
|
||||||
evt.stopPropagation()
|
screen_local: props.api.keys.formatBindings(bindings.get(command.screen_local)) ?? "",
|
||||||
check(props.api, props.route, next)
|
screen_host: props.api.keys.formatBindings(bindings.get(command.screen_host)) ?? "",
|
||||||
return
|
local_push: props.api.keys.formatBindings(bindings.get(command.local_push)) ?? "",
|
||||||
}
|
local_pop: props.api.keys.formatBindings(bindings.get(command.local_pop)) ?? "",
|
||||||
|
|
||||||
if (props.keys.match("prompt", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
entry(props.api, props.route, next)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.keys.match("select", evt)) {
|
|
||||||
evt.preventDefault()
|
|
||||||
evt.stopPropagation()
|
|
||||||
picker(props.api, props.route, next)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -430,7 +531,7 @@ const Screen = (props: {
|
|||||||
<b>{props.input.label} screen</b>
|
<b>{props.input.label} screen</b>
|
||||||
<span style={{ fg: skin.muted }}> plugin route</span>
|
<span style={{ fg: skin.muted }}> plugin route</span>
|
||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>{props.keys.print("home")} home</text>
|
<text fg={skin.muted}>{shortcuts().screen_home} home</text>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
<box flexDirection="row" gap={1} paddingBottom={1}>
|
<box flexDirection="row" gap={1} paddingBottom={1}>
|
||||||
@@ -477,7 +578,7 @@ const Screen = (props: {
|
|||||||
<box flexDirection="column" gap={1}>
|
<box flexDirection="column" gap={1}>
|
||||||
<text fg={skin.text}>Counter: {value.count}</text>
|
<text fg={skin.text}>Counter: {value.count}</text>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
{props.keys.print("up")} / {props.keys.print("down")} change value
|
{shortcuts().screen_up} / {shortcuts().screen_down} change value
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -485,17 +586,16 @@ const Screen = (props: {
|
|||||||
{value.tab === 2 ? (
|
{value.tab === 2 ? (
|
||||||
<box flexDirection="column" gap={1}>
|
<box flexDirection="column" gap={1}>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
{props.keys.print("modal")} modal | {props.keys.print("alert")} alert | {props.keys.print("confirm")}{" "}
|
{shortcuts().screen_modal} modal | {shortcuts().screen_alert} alert | {shortcuts().screen_confirm}{" "}
|
||||||
confirm | {props.keys.print("prompt")} prompt | {props.keys.print("select")} select
|
confirm | {shortcuts().screen_prompt} prompt | {shortcuts().screen_select} select
|
||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
{props.keys.print("local")} local stack | {props.keys.print("host")} host stack
|
{shortcuts().screen_local} local stack | {shortcuts().screen_host} host stack
|
||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
local open: {props.keys.print("local_push")} push nested · esc or {props.keys.print("local_close")}{" "}
|
local open: {shortcuts().local_push} push nested · {shortcuts().local_pop} close
|
||||||
close
|
|
||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>{props.keys.print("home")} returns home</text>
|
<text fg={skin.muted}>{shortcuts().screen_home} returns home</text>
|
||||||
</box>
|
</box>
|
||||||
) : null}
|
) : null}
|
||||||
</box>
|
</box>
|
||||||
@@ -548,7 +648,7 @@ const Screen = (props: {
|
|||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>Plugin-owned stack depth: {value.local}</text>
|
<text fg={skin.muted}>Plugin-owned stack depth: {value.local}</text>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
{props.keys.print("local_push")} push nested · {props.keys.print("local_close")} pop/close
|
{shortcuts().local_push} push nested · {shortcuts().local_pop} pop/close
|
||||||
</text>
|
</text>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<Btn txt="push" run={push} skin={skin} on />
|
<Btn txt="push" run={push} skin={skin} on />
|
||||||
@@ -571,20 +671,35 @@ const Modal = (props: {
|
|||||||
const value = parse(props.params)
|
const value = parse(props.params)
|
||||||
const skin = tone(props.api)
|
const skin = tone(props.api)
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useBindings(() => ({
|
||||||
if (props.api.route.current.name !== props.route.modal) return
|
enabled: () => props.api.route.current.name === props.route.modal,
|
||||||
|
commands: [
|
||||||
if (props.keys.match("modal_accept", evt)) {
|
{
|
||||||
evt.preventDefault()
|
name: command.modal_accept,
|
||||||
evt.stopPropagation()
|
run() {
|
||||||
props.api.route.navigate(props.route.screen, { ...value, source: "modal" })
|
props.api.route.navigate(props.route.screen, { ...parse(props.params), source: "modal" })
|
||||||
return
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
if (props.keys.match("modal_close", evt)) {
|
name: command.modal_close,
|
||||||
evt.preventDefault()
|
run() {
|
||||||
evt.stopPropagation()
|
|
||||||
props.api.route.navigate("home")
|
props.api.route.navigate("home")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bindings: props.keys.sections.modal,
|
||||||
|
}))
|
||||||
|
const shortcuts = useKeymapSelector((keymap) => {
|
||||||
|
const bindings = keymap.getCommandBindings({
|
||||||
|
visibility: "registered",
|
||||||
|
commands: [command.modal, command.screen, command.modal_accept, command.modal_close],
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
modal: props.api.keys.formatBindings(bindings.get(command.modal)) ?? "",
|
||||||
|
screen: props.api.keys.formatBindings(bindings.get(command.screen)) ?? "",
|
||||||
|
modal_accept: props.api.keys.formatBindings(bindings.get(command.modal_accept)) ?? "",
|
||||||
|
modal_close: props.api.keys.formatBindings(bindings.get(command.modal_close)) ?? "",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -595,10 +710,10 @@ const Modal = (props: {
|
|||||||
<text fg={skin.text}>
|
<text fg={skin.text}>
|
||||||
<b>{props.input.label} modal</b>
|
<b>{props.input.label} modal</b>
|
||||||
</text>
|
</text>
|
||||||
<text fg={skin.muted}>{props.keys.print("modal")} modal command</text>
|
<text fg={skin.muted}>{shortcuts().modal} modal command</text>
|
||||||
<text fg={skin.muted}>{props.keys.print("screen")} screen command</text>
|
<text fg={skin.muted}>{shortcuts().screen} screen command</text>
|
||||||
<text fg={skin.muted}>
|
<text fg={skin.muted}>
|
||||||
{props.keys.print("modal_accept")} opens screen · {props.keys.print("modal_close")} closes
|
{shortcuts().modal_accept} opens screen · {shortcuts().modal_close} closes
|
||||||
</text>
|
</text>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<Btn
|
<Btn
|
||||||
@@ -791,100 +906,94 @@ const slot = (api: TuiPluginApi, input: Cfg): TuiSlotPlugin[] => [
|
|||||||
|
|
||||||
const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
|
const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
|
||||||
const route = names(input)
|
const route = names(input)
|
||||||
api.command.register(() => [
|
api.keymap.registerLayer({
|
||||||
|
commands: [
|
||||||
{
|
{
|
||||||
|
name: command.modal,
|
||||||
title: `${input.label} modal`,
|
title: `${input.label} modal`,
|
||||||
value: "plugin.smoke.modal",
|
|
||||||
keybind: keys.get("modal"),
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke",
|
slashName: "smoke",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
api.route.navigate(route.modal, { source: "command" })
|
api.route.navigate(route.modal, { source: "command" })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.screen,
|
||||||
title: `${input.label} screen`,
|
title: `${input.label} screen`,
|
||||||
value: "plugin.smoke.screen",
|
|
||||||
keybind: keys.get("screen"),
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-screen",
|
slashName: "smoke-screen",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 })
|
api.route.navigate(route.screen, { source: "command", tab: 0, count: 0 })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.alert,
|
||||||
title: `${input.label} alert dialog`,
|
title: `${input.label} alert dialog`,
|
||||||
value: "plugin.smoke.alert",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-alert",
|
slashName: "smoke-alert",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
warn(api, route, current(api, route))
|
warn(api, route, current(api, route))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.confirm,
|
||||||
title: `${input.label} confirm dialog`,
|
title: `${input.label} confirm dialog`,
|
||||||
value: "plugin.smoke.confirm",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-confirm",
|
slashName: "smoke-confirm",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
check(api, route, current(api, route))
|
check(api, route, current(api, route))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.prompt,
|
||||||
title: `${input.label} prompt dialog`,
|
title: `${input.label} prompt dialog`,
|
||||||
value: "plugin.smoke.prompt",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-prompt",
|
slashName: "smoke-prompt",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
entry(api, route, current(api, route))
|
entry(api, route, current(api, route))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.select,
|
||||||
title: `${input.label} select dialog`,
|
title: `${input.label} select dialog`,
|
||||||
value: "plugin.smoke.select",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-select",
|
slashName: "smoke-select",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
picker(api, route, current(api, route))
|
picker(api, route, current(api, route))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.host,
|
||||||
title: `${input.label} host overlay`,
|
title: `${input.label} host overlay`,
|
||||||
value: "plugin.smoke.host",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
slash: {
|
namespace: "palette",
|
||||||
name: "smoke-host",
|
slashName: "smoke-host",
|
||||||
},
|
run() {
|
||||||
onSelect: () => {
|
|
||||||
host(api, input, tone(api))
|
host(api, input, tone(api))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.home,
|
||||||
title: `${input.label} go home`,
|
title: `${input.label} go home`,
|
||||||
value: "plugin.smoke.home",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
enabled: api.route.current.name !== "home",
|
namespace: "palette",
|
||||||
onSelect: () => {
|
enabled: () => api.route.current.name !== "home",
|
||||||
|
run() {
|
||||||
api.route.navigate("home")
|
api.route.navigate("home")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: command.toast,
|
||||||
title: `${input.label} toast`,
|
title: `${input.label} toast`,
|
||||||
value: "plugin.smoke.toast",
|
|
||||||
category: "Plugin",
|
category: "Plugin",
|
||||||
onSelect: () => {
|
namespace: "palette",
|
||||||
|
run() {
|
||||||
api.ui.toast({
|
api.ui.toast({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
title: "Smoke",
|
title: "Smoke",
|
||||||
@@ -893,18 +1002,21 @@ const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
],
|
||||||
|
bindings: keys.sections.global,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tui: TuiPlugin = async (api, options, meta) => {
|
const tui: TuiPlugin = async (api, options, meta) => {
|
||||||
if (options?.enabled === false) return
|
const input = options as SmokeOptions | undefined
|
||||||
|
if (input?.enabled === false) return
|
||||||
|
|
||||||
await api.theme.install("./smoke-theme.json")
|
await api.theme.install("./smoke-theme.json")
|
||||||
api.theme.set("smoke-theme")
|
api.theme.set("smoke-theme")
|
||||||
|
|
||||||
const value = cfg(options ?? undefined)
|
const value = cfg(input)
|
||||||
const route = names(value)
|
const route = names(value)
|
||||||
const keys = api.keybind.create(bind, value.keybinds)
|
const keys = createKeys(value.keymap)
|
||||||
const fx = new VignetteEffect(value.vignette)
|
const fx = new VignetteEffect(value.vignette)
|
||||||
const post = fx.apply.bind(fx)
|
const post = fx.apply.bind(fx)
|
||||||
api.renderer.addPostProcessFn(post)
|
api.renderer.addPostProcessFn(post)
|
||||||
|
|||||||
+14
-5
@@ -6,11 +6,20 @@
|
|||||||
{
|
{
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"label": "workspace",
|
"label": "workspace",
|
||||||
"keybinds": {
|
"keymap": {
|
||||||
"modal": "ctrl+alt+m",
|
"sections": {
|
||||||
"screen": "ctrl+alt+o",
|
"global": {
|
||||||
"home": "escape,ctrl+shift+h",
|
"plugin.smoke.modal": "ctrl+alt+m",
|
||||||
"dialog_close": "escape,q"
|
"plugin.smoke.screen": "ctrl+alt+o"
|
||||||
|
},
|
||||||
|
"screen": {
|
||||||
|
"plugin.smoke.screen.home": "escape,ctrl+shift+h",
|
||||||
|
"plugin.smoke.screen.modal": "ctrl+alt+m"
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"plugin.smoke.dialog.close": "escape,q"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+6
-18
@@ -73,7 +73,7 @@ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
|
|||||||
- `packages/opencode`: OpenCode core business logic & server.
|
- `packages/opencode`: OpenCode core business logic & server.
|
||||||
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
||||||
- `packages/app`: The shared web UI components, written in SolidJS
|
- `packages/app`: The shared web UI components, written in SolidJS
|
||||||
- `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`)
|
- `packages/desktop`: The native desktop app, built with Electron (wraps `packages/app`)
|
||||||
- `packages/plugin`: Source for `@opencode-ai/plugin`
|
- `packages/plugin`: Source for `@opencode-ai/plugin`
|
||||||
|
|
||||||
### Understanding bun dev vs opencode
|
### Understanding bun dev vs opencode
|
||||||
@@ -123,33 +123,21 @@ This starts a local dev server at http://localhost:5173 (or similar port shown i
|
|||||||
|
|
||||||
### Running the Desktop App
|
### Running the Desktop App
|
||||||
|
|
||||||
The desktop app is a native Tauri application that wraps the web UI.
|
The desktop app is an Electron application that wraps the web UI.
|
||||||
|
|
||||||
To run the native desktop app:
|
To run the desktop app in development:
|
||||||
|
|
||||||
```bash
|
|
||||||
bun run --cwd packages/desktop tauri dev
|
|
||||||
```
|
|
||||||
|
|
||||||
This starts the web dev server on http://localhost:1420 and opens the native window.
|
|
||||||
|
|
||||||
If you only want the web dev server (no native shell):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run --cwd packages/desktop dev
|
bun run --cwd packages/desktop dev
|
||||||
```
|
```
|
||||||
|
|
||||||
To create a production `dist/` and build the native app bundle:
|
To create a production build and package the app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run --cwd packages/desktop tauri build
|
bun run --cwd packages/desktop build
|
||||||
|
bun run --cwd packages/desktop package
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs `bun run --cwd packages/desktop build` automatically via Tauri’s `beforeBuildCommand`.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
|
> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # او github:anomalyco/opencode لاحدث
|
|||||||
يتوفر OpenCode ايضا كتطبيق سطح مكتب. قم بالتنزيل مباشرة من [صفحة الاصدارات](https://github.com/anomalyco/opencode/releases) او من [opencode.ai/download](https://opencode.ai/download).
|
يتوفر OpenCode ايضا كتطبيق سطح مكتب. قم بالتنزيل مباشرة من [صفحة الاصدارات](https://github.com/anomalyco/opencode/releases) او من [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| المنصة | التنزيل |
|
| المنصة | التنزيل |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb` او `.rpm` او AppImage |
|
| Linux | `.deb` او `.rpm` او AppImage |
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -69,11 +69,11 @@ nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev
|
|||||||
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
|
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
|
||||||
|
|
||||||
| প্ল্যাটফর্ম | ডাউনলোড |
|
| প্ল্যাটফর্ম | ডাউনলোড |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
| Linux | `.deb`, `.rpm`, or `.AppImage` |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS (Homebrew)
|
# macOS (Homebrew)
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # ou github:anomalyco/opencode para a branch
|
|||||||
O OpenCode também está disponível como aplicativo desktop. Baixe diretamente pela [página de releases](https://github.com/anomalyco/opencode/releases) ou em [opencode.ai/download](https://opencode.ai/download).
|
O OpenCode também está disponível como aplicativo desktop. Baixe diretamente pela [página de releases](https://github.com/anomalyco/opencode/releases) ou em [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Plataforma | Download |
|
| Plataforma | Download |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` ou AppImage |
|
| Linux | `.deb`, `.rpm` ou AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # ili github:anomalyco/opencode za najnoviji
|
|||||||
OpenCode je dostupan i kao desktop aplikacija. Preuzmi je direktno sa [stranice izdanja](https://github.com/anomalyco/opencode/releases) ili sa [opencode.ai/download](https://opencode.ai/download).
|
OpenCode je dostupan i kao desktop aplikacija. Preuzmi je direktno sa [stranice izdanja](https://github.com/anomalyco/opencode/releases) ili sa [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Platforma | Preuzimanje |
|
| Platforma | Preuzimanje |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, ili AppImage |
|
| Linux | `.deb`, `.rpm`, ili AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste
|
|||||||
OpenCode findes også som desktop-app. Download direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
OpenCode findes også som desktop-app. Download direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Platform | Download |
|
| Platform | Download |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, eller AppImage |
|
| Linux | `.deb`, `.rpm`, eller AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # oder github:anomalyco/opencode für den neu
|
|||||||
OpenCode ist auch als Desktop-Anwendung verfügbar. Lade sie direkt von der [Releases-Seite](https://github.com/anomalyco/opencode/releases) oder [opencode.ai/download](https://opencode.ai/download) herunter.
|
OpenCode ist auch als Desktop-Anwendung verfügbar. Lade sie direkt von der [Releases-Seite](https://github.com/anomalyco/opencode/releases) oder [opencode.ai/download](https://opencode.ai/download) herunter.
|
||||||
|
|
||||||
| Plattform | Download |
|
| Plattform | Download |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` oder AppImage |
|
| Linux | `.deb`, `.rpm` oder AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # o github:anomalyco/opencode para la rama de
|
|||||||
OpenCode también está disponible como aplicación de escritorio. Descárgala directamente desde la [página de releases](https://github.com/anomalyco/opencode/releases) o desde [opencode.ai/download](https://opencode.ai/download).
|
OpenCode también está disponible como aplicación de escritorio. Descárgala directamente desde la [página de releases](https://github.com/anomalyco/opencode/releases) o desde [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Plataforma | Descarga |
|
| Plataforma | Descarga |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, o AppImage |
|
| Linux | `.deb`, `.rpm`, o AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # ou github:anomalyco/opencode pour la branch
|
|||||||
OpenCode est aussi disponible en application de bureau. Téléchargez-la directement depuis la [page des releases](https://github.com/anomalyco/opencode/releases) ou [opencode.ai/download](https://opencode.ai/download).
|
OpenCode est aussi disponible en application de bureau. Téléchargez-la directement depuis la [page des releases](https://github.com/anomalyco/opencode/releases) ou [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Plateforme | Téléchargement |
|
| Plateforme | Téléchargement |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, ou AppImage |
|
| Linux | `.deb`, `.rpm`, ou AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # ή github:anomalyco/opencode με βάση
|
|||||||
Το OpenCode είναι επίσης διαθέσιμο ως εφαρμογή. Κατέβασε το απευθείας από τη [σελίδα εκδόσεων](https://github.com/anomalyco/opencode/releases) ή το [opencode.ai/download](https://opencode.ai/download).
|
Το OpenCode είναι επίσης διαθέσιμο ως εφαρμογή. Κατέβασε το απευθείας από τη [σελίδα εκδόσεων](https://github.com/anomalyco/opencode/releases) ή το [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Πλατφόρμα | Λήψη |
|
| Πλατφόρμα | Λήψη |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, ή AppImage |
|
| Linux | `.deb`, `.rpm`, ή AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # oppure github:anomalyco/opencode per l’ul
|
|||||||
OpenCode è disponibile anche come applicazione desktop. Puoi scaricarla direttamente dalla [pagina delle release](https://github.com/anomalyco/opencode/releases) oppure da [opencode.ai/download](https://opencode.ai/download).
|
OpenCode è disponibile anche come applicazione desktop. Puoi scaricarla direttamente dalla [pagina delle release](https://github.com/anomalyco/opencode/releases) oppure da [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Piattaforma | Download |
|
| Piattaforma | Download |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, oppure AppImage |
|
| Linux | `.deb`, `.rpm`, oppure AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # または github:anomalyco/opencode で最
|
|||||||
OpenCode はデスクトップアプリとしても利用できます。[releases page](https://github.com/anomalyco/opencode/releases) から直接ダウンロードするか、[opencode.ai/download](https://opencode.ai/download) を利用してください。
|
OpenCode はデスクトップアプリとしても利用できます。[releases page](https://github.com/anomalyco/opencode/releases) から直接ダウンロードするか、[opencode.ai/download](https://opencode.ai/download) を利用してください。
|
||||||
|
|
||||||
| プラットフォーム | ダウンロード |
|
| プラットフォーム | ダウンロード |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`、`.rpm`、または AppImage |
|
| Linux | `.deb`、`.rpm`、または AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # 또는 github:anomalyco/opencode 로 최신
|
|||||||
OpenCode 는 데스크톱 앱으로도 제공됩니다. [releases page](https://github.com/anomalyco/opencode/releases) 에서 직접 다운로드하거나 [opencode.ai/download](https://opencode.ai/download) 를 이용하세요.
|
OpenCode 는 데스크톱 앱으로도 제공됩니다. [releases page](https://github.com/anomalyco/opencode/releases) 에서 직접 다운로드하거나 [opencode.ai/download](https://opencode.ai/download) 를 이용하세요.
|
||||||
|
|
||||||
| 플랫폼 | 다운로드 |
|
| 플랫폼 | 다운로드 |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, 또는 AppImage |
|
| Linux | `.deb`, `.rpm`, 또는 AppImage |
|
||||||
|
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev
|
|||||||
OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
|
OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Platform | Download |
|
| Platform | Download |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
| Linux | `.deb`, `.rpm`, or `.AppImage` |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS (Homebrew)
|
# macOS (Homebrew)
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste
|
|||||||
OpenCode er også tilgjengelig som en desktop-app. Last ned direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
OpenCode er også tilgjengelig som en desktop-app. Last ned direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Plattform | Nedlasting |
|
| Plattform | Nedlasting |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` eller AppImage |
|
| Linux | `.deb`, `.rpm` eller AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # lub github:anomalyco/opencode dla najnowsze
|
|||||||
OpenCode jest także dostępny jako aplikacja desktopowa. Pobierz ją bezpośrednio ze strony [releases](https://github.com/anomalyco/opencode/releases) lub z [opencode.ai/download](https://opencode.ai/download).
|
OpenCode jest także dostępny jako aplikacja desktopowa. Pobierz ją bezpośrednio ze strony [releases](https://github.com/anomalyco/opencode/releases) lub z [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Platforma | Pobieranie |
|
| Platforma | Pobieranie |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` lub AppImage |
|
| Linux | `.deb`, `.rpm` lub AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # или github:anomalyco/opencode для с
|
|||||||
OpenCode также доступен как десктопное приложение. Скачайте его со [страницы релизов](https://github.com/anomalyco/opencode/releases) или с [opencode.ai/download](https://opencode.ai/download).
|
OpenCode также доступен как десктопное приложение. Скачайте его со [страницы релизов](https://github.com/anomalyco/opencode/releases) или с [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Платформа | Загрузка |
|
| Платформа | Загрузка |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` или AppImage |
|
| Linux | `.deb`, `.rpm` или AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # หรือ github:anomalyco/opencode ส
|
|||||||
OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/opencode/releases) หรือ [opencode.ai/download](https://opencode.ai/download)
|
OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/opencode/releases) หรือ [opencode.ai/download](https://opencode.ai/download)
|
||||||
|
|
||||||
| แพลตฟอร์ม | ดาวน์โหลด |
|
| แพลตฟอร์ม | ดาวน์โหลด |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, หรือ AppImage |
|
| Linux | `.deb`, `.rpm`, หรือ AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # veya en güncel geliştirme dalı için git
|
|||||||
OpenCode ayrıca masaüstü uygulaması olarak da mevcuttur. Doğrudan [sürüm sayfasından](https://github.com/anomalyco/opencode/releases) veya [opencode.ai/download](https://opencode.ai/download) adresinden indirebilirsiniz.
|
OpenCode ayrıca masaüstü uygulaması olarak da mevcuttur. Doğrudan [sürüm sayfasından](https://github.com/anomalyco/opencode/releases) veya [opencode.ai/download](https://opencode.ai/download) adresinden indirebilirsiniz.
|
||||||
|
|
||||||
| Platform | İndirme |
|
| Platform | İndirme |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` veya AppImage |
|
| Linux | `.deb`, `.rpm` veya AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # або github:anomalyco/opencode для н
|
|||||||
OpenCode також доступний як десктопний застосунок. Завантажуйте напряму зі [сторінки релізів](https://github.com/anomalyco/opencode/releases) або [opencode.ai/download](https://opencode.ai/download).
|
OpenCode також доступний як десктопний застосунок. Завантажуйте напряму зі [сторінки релізів](https://github.com/anomalyco/opencode/releases) або [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Платформа | Завантаження |
|
| Платформа | Завантаження |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm` або AppImage |
|
| Linux | `.deb`, `.rpm` або AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # hoặc github:anomalyco/opencode cho nhánh
|
|||||||
OpenCode cũng có sẵn dưới dạng ứng dụng desktop. Tải trực tiếp từ [trang releases](https://github.com/anomalyco/opencode/releases) hoặc [opencode.ai/download](https://opencode.ai/download).
|
OpenCode cũng có sẵn dưới dạng ứng dụng desktop. Tải trực tiếp từ [trang releases](https://github.com/anomalyco/opencode/releases) hoặc [opencode.ai/download](https://opencode.ai/download).
|
||||||
|
|
||||||
| Nền tảng | Tải xuống |
|
| Nền tảng | Tải xuống |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, hoặc AppImage |
|
| Linux | `.deb`, `.rpm`, hoặc AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最
|
|||||||
OpenCode 也提供桌面版应用。可直接从 [发布页 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下载。
|
OpenCode 也提供桌面版应用。可直接从 [发布页 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下载。
|
||||||
|
|
||||||
| 平台 | 下载文件 |
|
| 平台 | 下载文件 |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`、`.rpm` 或 AppImage |
|
| Linux | `.deb`、`.rpm` 或 AppImage |
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -69,9 +69,9 @@ nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取
|
|||||||
OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。
|
OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。
|
||||||
|
|
||||||
| 平台 | 下載連結 |
|
| 平台 | 下載連結 |
|
||||||
| --------------------- | ------------------------------------- |
|
| --------------------- | ---------------------------------- |
|
||||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||||
| Linux | `.deb`, `.rpm`, 或 AppImage |
|
| Linux | `.deb`, `.rpm`, 或 AppImage |
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"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",
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.64",
|
"@ai-sdk/anthropic": "3.0.64",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"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",
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -270,11 +270,19 @@
|
|||||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
||||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
||||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.10",
|
"@lydell/node-pty-win32-x64": "1.2.0-beta.10",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-x64": "2.5.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -303,7 +311,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -319,14 +327,14 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "1.11.1",
|
||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.16.1",
|
"@agentclientprotocol/sdk": "0.21.0",
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
"@ai-sdk/amazon-bedrock": "4.0.96",
|
||||||
"@ai-sdk/anthropic": "3.0.71",
|
"@ai-sdk/anthropic": "3.0.71",
|
||||||
@@ -371,6 +379,7 @@
|
|||||||
"@opentelemetry/sdk-trace-base": "2.6.1",
|
"@opentelemetry/sdk-trace-base": "2.6.1",
|
||||||
"@opentelemetry/sdk-trace-node": "2.6.1",
|
"@opentelemetry/sdk-trace-node": "2.6.1",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
|
"@opentui/keymap": "catalog:",
|
||||||
"@opentui/solid": "catalog:",
|
"@opentui/solid": "catalog:",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
@@ -461,7 +470,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -469,6 +478,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
|
"@opentui/keymap": "catalog:",
|
||||||
"@opentui/solid": "catalog:",
|
"@opentui/solid": "catalog:",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
@@ -476,11 +486,13 @@
|
|||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentui/core": ">=0.2.2",
|
"@opentui/core": ">=0.2.5",
|
||||||
"@opentui/solid": ">=0.2.2",
|
"@opentui/keymap": ">=0.2.5",
|
||||||
|
"@opentui/solid": ">=0.2.5",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
"@opentui/core",
|
"@opentui/core",
|
||||||
|
"@opentui/keymap",
|
||||||
"@opentui/solid",
|
"@opentui/solid",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -496,7 +508,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -511,7 +523,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -546,7 +558,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -595,7 +607,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -655,8 +667,9 @@
|
|||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@opentui/core": "0.2.2",
|
"@opentui/core": "0.2.5",
|
||||||
"@opentui/solid": "0.2.2",
|
"@opentui/keymap": "0.2.5",
|
||||||
|
"@opentui/solid": "0.2.5",
|
||||||
"@pierre/diffs": "1.1.0-beta.18",
|
"@pierre/diffs": "1.1.0-beta.18",
|
||||||
"@playwright/test": "1.59.1",
|
"@playwright/test": "1.59.1",
|
||||||
"@sentry/solid": "10.36.0",
|
"@sentry/solid": "10.36.0",
|
||||||
@@ -719,7 +732,7 @@
|
|||||||
|
|
||||||
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
||||||
|
|
||||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.16.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw=="],
|
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.21.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw=="],
|
||||||
|
|
||||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "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-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "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-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||||
|
|
||||||
@@ -1581,21 +1594,23 @@
|
|||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.2.2", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.2", "@opentui/core-darwin-x64": "0.2.2", "@opentui/core-linux-arm64": "0.2.2", "@opentui/core-linux-x64": "0.2.2", "@opentui/core-win32-arm64": "0.2.2", "@opentui/core-win32-x64": "0.2.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wxg1CD58SVrowu+WgbhZNi3UP/wWxPio2Kj2IeTjomoIE+6EXLxR8eCCxHYVuQUd9E4fknrKkY5HmiSsp6oPow=="],
|
"@opentui/core": ["@opentui/core@0.2.5", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.5", "@opentui/core-darwin-x64": "0.2.5", "@opentui/core-linux-arm64": "0.2.5", "@opentui/core-linux-x64": "0.2.5", "@opentui/core-win32-arm64": "0.2.5", "@opentui/core-win32-x64": "0.2.5" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-A5DNOW39S60LtOcBdWYx7fuIGsPcClzbdKz9WuLp+wgy0Bt/jPw5XX6dk3k4dCX4jmhA1nX7x7680+GXLHPL6Q=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tY5n3ZRQx+b0kyhQJJLsyJMeZ+0w4FV37YZc/Qqv3qvOqE9kZPw/7adR77FYwWDm/7fax94mLMrR8Y5bKUkDmw=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jdl8TN7oxV8NTaKZsUAt0B/A4hIYiyUKwXNSe4w1OchNMlgjwF1fx/7RhgHXSvWh1Fcqi1IH5FfhsmO89Aed1A=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-W/R7OnqY30FXcTG0tiP2JkQFmgtYbIte5afQ5PC12TliRoee1RqG3iCG6kY1jxW+3Vg6jge88uiSjUEDpeV2gA=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-78sKg0ZvwFHzZZGJCeaSNIVi2dadDxQymHAmrK698zEgnQr4eLVVB+MxNpxJx55/z9Y+YqbfSZaobC6w6Q3y5A=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1pzTYFEZauYuw6AGycw2TYGtAlZVGjuUtSdxH1fP51kBPS3oVWduUY2j7GKREz3SU5NulvO2Wc6HWsm3feMqwQ=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-BtqbOjP64hKQaVd0ApHunt0MjkEEKTvxpaBwk7OhwVCoYakQBDZTZXUQ9zuPXvaHc9IF286z1PnJGLu0t11BAw=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ucVwUtUYeOYGVFPBLbPoxzbrPdhD0PDyKNQ2X4n1AJ9jlQX4gqBZRcXMEF8hiXDjFxsikZwef7De0ciCcWvAMg=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-c3sEXtmOd1E5R4wfWh/MejplxgApYKqzyJ0AVMTU8pU1MHRAMwD8UFDMSVQhl7rYMTuBYPWok3IoCK2u8a2A4A=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-MPhYdJNdxmC5Bqsq6sis/+VkjRgkEjm+bQ1Tl++NSKLuiTU32Re0ImcZlgHbe+LZtZoGMZHVSgZlkGd3oYXO2g=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WlgpYkgmuvMPc2mYGJSwN7c+VGAxiZvMKwZEbS+w9PMj7sJhvY+zFrOJNFpvjbAFw8vS3Kz39km4Nj7GF8JH6w=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-19BroLfn2h0RDYfJS5o96Fc8kYCDhRBcseIXtHIkoKIsKMxx62KiDLo/byVye6rp+yQRRB7Xkd2uWqsbdiWo9w=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-4X7BHJ7Wztzj7p0E+SsN0d4goUVU7Dy2VnhnD4n65ODgVbW59iqasAvbnPLbX3ghjgKiwQ+2SD+ImCIHE6uCAA=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.2.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-ZBVfCoVAhcUGQWPAWOTdzuVldMaRkuPpCu4U1VZCqmIw9DtbCuiVr0WnDocDxKhJLbTu8bl3qEWtVCf6lTSi3w=="],
|
"@opentui/keymap": ["@opentui/keymap@0.2.5", "", { "dependencies": { "@opentui/core": "0.2.5" }, "peerDependencies": { "@opentui/react": "0.2.5", "@opentui/solid": "0.2.5", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-/B6Gy9LLRRKhvyDV1rFX0p7BUN8NQOcXwTV8E0xb7ym1yREvVmij+hCRkXXddMme2HW9NmV0+RRHo4kJzJxkNQ=="],
|
||||||
|
|
||||||
|
"@opentui/solid": ["@opentui/solid@0.2.5", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.5", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-M8MxDYJzjtF8TvxB6Q7656GOSS+QIg89jD0jf/asfF4qeip5TQhNZ3ba+R1v2fVuIkQCyRJzTtOtMZiglzGKPQ=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const api = new sst.cloudflare.Worker("Api", {
|
|||||||
transform: {
|
transform: {
|
||||||
worker: (args) => {
|
worker: (args) => {
|
||||||
args.logpush = true
|
args.logpush = true
|
||||||
|
if ($app.stage === "vimtor") return
|
||||||
args.bindings = $resolve(args.bindings).apply((bindings) => [
|
args.bindings = $resolve(args.bindings).apply((bindings) => [
|
||||||
...bindings,
|
...bindings,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { domain } from "./stage"
|
import { domain } from "./stage"
|
||||||
import { EMAILOCTOPUS_API_KEY } from "./app"
|
import { EMAILOCTOPUS_API_KEY } from "./app"
|
||||||
|
import { SECRET } from "./secret"
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// DATABASE
|
// DATABASE
|
||||||
@@ -221,6 +222,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
|
|||||||
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||||
properties: { value: stripeWebhook.secret },
|
properties: { value: stripeWebhook.secret },
|
||||||
})
|
})
|
||||||
|
|
||||||
const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
@@ -230,6 +232,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
|
|||||||
const bucket = new sst.cloudflare.Bucket("ZenData")
|
const bucket = new sst.cloudflare.Bucket("ZenData")
|
||||||
const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
|
const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
|
||||||
|
|
||||||
|
const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL")
|
||||||
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
|
||||||
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
|
||||||
|
|
||||||
@@ -251,6 +254,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
database,
|
database,
|
||||||
AUTH_API_URL,
|
AUTH_API_URL,
|
||||||
STRIPE_WEBHOOK_SECRET,
|
STRIPE_WEBHOOK_SECRET,
|
||||||
|
DISCORD_INCIDENT_WEBHOOK_URL,
|
||||||
|
SECRET.HoneycombWebhookSecret,
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
EMAILOCTOPUS_API_KEY,
|
EMAILOCTOPUS_API_KEY,
|
||||||
AWS_SES_ACCESS_KEY_ID,
|
AWS_SES_ACCESS_KEY_ID,
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { SECRET } from "./secret"
|
||||||
|
import { domain } from "./stage"
|
||||||
|
|
||||||
|
const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", {
|
||||||
|
name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
|
||||||
|
url: `https://${domain}/honeycomb/webhook`,
|
||||||
|
secret: SECRET.HoneycombWebhookSecret.result,
|
||||||
|
templates: [
|
||||||
|
{
|
||||||
|
type: "trigger",
|
||||||
|
body: `{
|
||||||
|
"url": {{ .Result.URL | quote }},
|
||||||
|
"type": {{ .Vars.type | quote }},
|
||||||
|
"name": {{ .Name | quote }},
|
||||||
|
"status": {{ .Alert.Status | quote }},
|
||||||
|
"isTest": {{ .Alert.IsTest }},
|
||||||
|
"groups": {{ .Result.GroupsTriggered | toJson }}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variables: [
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const modelHttpErrorsQuery = (product: "go" | "zen") => {
|
||||||
|
const filters = [
|
||||||
|
{ column: "model", op: "exists" },
|
||||||
|
{ column: "event_type", op: "=", value: "completions" },
|
||||||
|
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||||
|
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return honeycomb.getQuerySpecificationOutput({
|
||||||
|
breakdowns: ["model"],
|
||||||
|
calculatedFields: [
|
||||||
|
{
|
||||||
|
name: "is_failed_http_status",
|
||||||
|
expression: `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
calculations: [
|
||||||
|
{ op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
|
||||||
|
{ op: "SUM", name: "FAILED", column: "is_failed_http_status", filterCombination: "AND", filters },
|
||||||
|
],
|
||||||
|
formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 100), DIV($FAILED, $TOTAL), 0)" }],
|
||||||
|
timeRange: 900,
|
||||||
|
}).json
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerHttpErrorsQuery = (product: "go" | "zen") => {
|
||||||
|
const filters = [
|
||||||
|
{ column: "provider", op: "exists" },
|
||||||
|
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||||
|
{ column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return honeycomb.getQuerySpecificationOutput({
|
||||||
|
breakdowns: ["provider"],
|
||||||
|
calculatedFields: [
|
||||||
|
{
|
||||||
|
name: "is_success_http_status",
|
||||||
|
expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "is_failed_provider_http_status",
|
||||||
|
expression: `IF(GTE($llm.error.code, "400"), 1, 0)`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
calculations: [
|
||||||
|
{
|
||||||
|
op: "SUM",
|
||||||
|
name: "SUCCESS",
|
||||||
|
column: "is_success_http_status",
|
||||||
|
filterCombination: "AND",
|
||||||
|
filters: [...filters, { column: "event_type", op: "=", value: "completions" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "SUM",
|
||||||
|
name: "FAILED",
|
||||||
|
column: "is_failed_provider_http_status",
|
||||||
|
filterCombination: "AND",
|
||||||
|
filters: [...filters, { column: "event_type", op: "=", value: "llm.error" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
formulas: [
|
||||||
|
{ name: "ERROR", expression: "IF(GTE(SUM($SUCCESS, $FAILED), 50), DIV($FAILED, SUM($SUCCESS, $FAILED)), 0)" },
|
||||||
|
],
|
||||||
|
timeRange: 900,
|
||||||
|
}).json
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = "Managed by SST (Don't edit in Honeycomb UI)"
|
||||||
|
|
||||||
|
new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
|
||||||
|
name: "Increased Model HTTP Errors [Go]",
|
||||||
|
description,
|
||||||
|
queryJson: modelHttpErrorsQuery("go"),
|
||||||
|
alertType: "on_change",
|
||||||
|
frequency: 300,
|
||||||
|
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: webhookRecipient.id,
|
||||||
|
notificationDetails: [
|
||||||
|
{
|
||||||
|
variables: [{ name: "type", value: "model_http_errors" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
|
||||||
|
name: "Increased Model HTTP Errors [Zen]",
|
||||||
|
description,
|
||||||
|
queryJson: modelHttpErrorsQuery("zen"),
|
||||||
|
alertType: "on_change",
|
||||||
|
frequency: 300,
|
||||||
|
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: webhookRecipient.id,
|
||||||
|
notificationDetails: [
|
||||||
|
{
|
||||||
|
variables: [{ name: "type", value: "model_http_errors" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
new honeycomb.Trigger("IncreasedProviderHttpErrorsGo", {
|
||||||
|
name: "Increased Provider HTTP Errors [Go]",
|
||||||
|
description,
|
||||||
|
queryJson: providerHttpErrorsQuery("go"),
|
||||||
|
alertType: "on_change",
|
||||||
|
frequency: 300,
|
||||||
|
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: webhookRecipient.id,
|
||||||
|
notificationDetails: [
|
||||||
|
{
|
||||||
|
variables: [{ name: "type", value: "provider_http_errors" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
new honeycomb.Trigger("IncreasedProviderHttpErrorsZen", {
|
||||||
|
name: "Increased Provider HTTP Errors [Zen]",
|
||||||
|
description,
|
||||||
|
queryJson: providerHttpErrorsQuery("zen"),
|
||||||
|
alertType: "on_change",
|
||||||
|
frequency: 300,
|
||||||
|
thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: webhookRecipient.id,
|
||||||
|
notificationDetails: [
|
||||||
|
{
|
||||||
|
variables: [{ name: "type", value: "provider_http_errors" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
new honeycomb.Trigger("IncreasedFreeTierRequests", {
|
||||||
|
name: "Increased Free Tier Requests",
|
||||||
|
description,
|
||||||
|
queryJson: honeycomb.getQuerySpecificationOutput({
|
||||||
|
calculations: [{ op: "COUNT" }],
|
||||||
|
filters: [
|
||||||
|
{ column: "event_type", op: "=", value: "completions" },
|
||||||
|
{ column: "user_agent", op: "contains", value: "opencode" },
|
||||||
|
{ column: "isFreeTier", op: "=", value: "true" },
|
||||||
|
],
|
||||||
|
timeRange: 3600,
|
||||||
|
}).json,
|
||||||
|
alertType: "on_change",
|
||||||
|
frequency: 900,
|
||||||
|
thresholds: [{ op: ">=", value: 50, exceededLimit: 1 }],
|
||||||
|
baselineDetails: [{ type: "percentage", offsetMinutes: 1440 }],
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
id: webhookRecipient.id,
|
||||||
|
notificationDetails: [
|
||||||
|
{
|
||||||
|
variables: [{ name: "type", value: "custom" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
|
sst.Linkable.wrap(random.RandomPassword, (resource) => ({
|
||||||
|
properties: {
|
||||||
|
value: resource.result,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
export const SECRET = {
|
export const SECRET = {
|
||||||
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
|
R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
|
||||||
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
|
R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
|
||||||
|
HoneycombWebhookSecret: new random.RandomPassword("HoneycombWebhookSecret", { length: 24 }),
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-YBTnGuKDthi9wM4UrY0CMNqAzwnM6rN5XROyJOqYbQ8=",
|
"x86_64-linux": "sha256-UxWxALOCC/n6JNFcu/IKjC/B9bySQmcr2riWO1Doc3s=",
|
||||||
"aarch64-linux": "sha256-7B1dxtYOc9t+e3lzF8O02YtjsvogyuZjHSanWw1XPio=",
|
"aarch64-linux": "sha256-QLM8fPkPOukwOLR26zgZHhWEbfaEhmIqIJSjoQYOvfg=",
|
||||||
"aarch64-darwin": "sha256-y0CzJRL4WHMxVbZPg3O7Dd+66TbITJbiv0oqhZ6URWw=",
|
"aarch64-darwin": "sha256-GjgCPpkTzqkeiLsp2P+Awtm0K0XKTJV7v9QJoGC02YU=",
|
||||||
"x86_64-darwin": "sha256-KW0Cx/ddKM4sQcpKhKwYu8qL6zYlm12kcUlgp66Wf50="
|
"x86_64-darwin": "sha256-pm7xhKAUBgp+zDh1KzyOlKS2TYJpSdDPnZFqFHrflSA="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -13,6 +13,7 @@
|
|||||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||||
"lint": "oxlint",
|
"lint": "oxlint",
|
||||||
"typecheck": "bun turbo typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
|
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||||
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
|
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"random": "echo 'Random script'",
|
"random": "echo 'Random script'",
|
||||||
@@ -34,8 +35,9 @@
|
|||||||
"@types/cross-spawn": "6.0.6",
|
"@types/cross-spawn": "6.0.6",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@opentui/core": "0.2.2",
|
"@opentui/core": "0.2.5",
|
||||||
"@opentui/solid": "0.2.2",
|
"@opentui/keymap": "0.2.5",
|
||||||
|
"@opentui/solid": "0.2.5",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
|
|||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { loadMcpQuery } from "@/context/global-sync"
|
import { mcpQueryKey } from "@/context/global-sync"
|
||||||
|
|
||||||
const statusLabels = {
|
const statusLabels = {
|
||||||
connected: "mcp.status.connected",
|
connected: "mcp.status.connected",
|
||||||
@@ -32,7 +32,7 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
|
if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name })
|
||||||
else await sdk.client.mcp.connect({ name })
|
else await sdk.client.mcp.connect({ name })
|
||||||
},
|
},
|
||||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from "@/context/prompt"
|
} from "@/context/prompt"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -102,6 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
|
|||||||
|
|
||||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
const globalSDK = useGlobalSDK()
|
||||||
|
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
@@ -1253,7 +1255,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({
|
||||||
queries: [loadAgentsQuery(sdk.directory), loadProvidersQuery(null), loadProvidersQuery(sdk.directory)],
|
queries: [
|
||||||
|
loadAgentsQuery(sdk.directory, sdk.client),
|
||||||
|
loadProvidersQuery(null, globalSDK.client),
|
||||||
|
loadProvidersQuery(sdk.directory, sdk.client),
|
||||||
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const agentsLoading = () => agentsQuery.isLoading
|
const agentsLoading = () => agentsQuery.isLoading
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useSDK } from "@/context/sdk"
|
|||||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||||
import { loadMcpQuery } from "@/context/global-sync"
|
import { mcpQueryKey } from "@/context/global-sync"
|
||||||
|
|
||||||
const pollMs = 10_000
|
const pollMs = 10_000
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ const useMcpToggleMutation = () => {
|
|||||||
const status = sync.data.mcp[name]
|
const status = sync.data.mcp[name]
|
||||||
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
||||||
},
|
},
|
||||||
onSuccess: () => queryClient.refetchQueries({ queryKey: loadMcpQuery(sync.directory).queryKey }),
|
onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }),
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
clearProviderRev,
|
clearProviderRev,
|
||||||
loadGlobalConfigQuery,
|
loadGlobalConfigQuery,
|
||||||
loadPathQuery,
|
loadPathQuery,
|
||||||
loadProjectsQuery,
|
|
||||||
loadProvidersQuery,
|
loadProvidersQuery,
|
||||||
} from "./global-sync/bootstrap"
|
} from "./global-sync/bootstrap"
|
||||||
import { createChildStoreManager } from "./global-sync/child-store"
|
import { createChildStoreManager } from "./global-sync/child-store"
|
||||||
@@ -31,7 +30,7 @@ import { trimSessions } from "./global-sync/session-trim"
|
|||||||
import type { ProjectMeta } from "./global-sync/types"
|
import type { ProjectMeta } from "./global-sync/types"
|
||||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
import { queryOptions, skipToken, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
|
import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query"
|
||||||
import { createRefreshQueue } from "./global-sync/queue"
|
import { createRefreshQueue } from "./global-sync/queue"
|
||||||
import { directoryKey } from "./global-sync/utils"
|
import { directoryKey } from "./global-sync/utils"
|
||||||
|
|
||||||
@@ -49,19 +48,22 @@ type GlobalStore = {
|
|||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadSessionsQuery = (directory: string) =>
|
export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const
|
||||||
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
|
|
||||||
|
|
||||||
export const loadMcpQuery = (directory: string, sdk?: OpencodeClient) =>
|
export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const
|
||||||
|
|
||||||
|
export const loadMcpQuery = (directory: string, sdk: OpencodeClient) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [directory, "mcp"],
|
queryKey: mcpQueryKey(directory),
|
||||||
queryFn: sdk ? () => sdk.mcp.status().then((r) => r.data ?? {}) : skipToken,
|
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadLspQuery = (directory: string, sdk?: OpencodeClient) =>
|
export const lspQueryKey = (directory: string) => [directory, "lsp"] as const
|
||||||
|
|
||||||
|
export const loadLspQuery = (directory: string, sdk: OpencodeClient) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [directory, "lsp"],
|
queryKey: lspQueryKey(directory),
|
||||||
queryFn: sdk ? () => sdk.lsp.status().then((r) => r.data ?? []) : skipToken,
|
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
|
||||||
})
|
})
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
@@ -76,7 +78,11 @@ function createGlobalSync() {
|
|||||||
const sessionMeta = new Map<string, { limit: number }>()
|
const sessionMeta = new Map<string, { limit: number }>()
|
||||||
|
|
||||||
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
|
const [configQuery, providerQuery, pathQuery] = useQueries(() => ({
|
||||||
queries: [loadGlobalConfigQuery(), loadProvidersQuery(null), loadPathQuery(null), loadProjectsQuery()],
|
queries: [
|
||||||
|
loadGlobalConfigQuery(globalSDK.client),
|
||||||
|
loadProvidersQuery(null, globalSDK.client),
|
||||||
|
loadPathQuery(null, globalSDK.client),
|
||||||
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
|
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
|
||||||
@@ -233,7 +239,7 @@ function createGlobalSync() {
|
|||||||
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
|
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
|
||||||
const promise = queryClient
|
const promise = queryClient
|
||||||
.fetchQuery({
|
.fetchQuery({
|
||||||
...loadSessionsQuery(key),
|
queryKey: loadSessionsQueryKey(key),
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
loadRootSessionsWithFallback({
|
loadRootSessionsWithFallback({
|
||||||
directory,
|
directory,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
|||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
|
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||||
import { loadMcpQuery } from "../global-sync"
|
import { loadMcpQuery } from "../global-sync"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
@@ -83,44 +83,25 @@ function showErrors(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadGlobalConfigQuery = (
|
export const loadGlobalConfigQuery = (sdk: OpencodeClient) =>
|
||||||
sdk?: OpencodeClient,
|
|
||||||
transform?: (x: Awaited<ReturnType<OpencodeClient["global"]["config"]["get"]>>) => void,
|
|
||||||
) =>
|
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["config"],
|
queryKey: ["config"],
|
||||||
queryFn: sdk
|
queryFn: () => retry(() => sdk.global.config.get().then((x) => x.data!)),
|
||||||
? () =>
|
|
||||||
retry(() =>
|
|
||||||
sdk.global.config.get().then((x) => {
|
|
||||||
transform?.(x)
|
|
||||||
return x.data!
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: skipToken,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadProjectsQuery = (
|
export const loadProjectsQuery = (sdk: OpencodeClient) =>
|
||||||
sdk?: OpencodeClient,
|
|
||||||
transform?: (x: Awaited<ReturnType<OpencodeClient["project"]["list"]>>["data"]) => void,
|
|
||||||
) =>
|
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["project"],
|
queryKey: ["project"],
|
||||||
queryFn: sdk
|
queryFn: () =>
|
||||||
? () =>
|
|
||||||
retry(() =>
|
retry(() =>
|
||||||
sdk.project
|
sdk.project.list().then((x) => {
|
||||||
.list()
|
|
||||||
.then((x) => {
|
|
||||||
return (x.data ?? [])
|
return (x.data ?? [])
|
||||||
.filter((p) => !!p?.id)
|
.filter((p) => !!p?.id)
|
||||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => cmp(a.id, b.id))
|
.sort((a, b) => cmp(a.id, b.id))
|
||||||
})
|
}),
|
||||||
.then(transform),
|
),
|
||||||
)
|
|
||||||
: skipToken,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function bootstrapGlobal(input: {
|
export async function bootstrapGlobal(input: {
|
||||||
@@ -136,9 +117,9 @@ export async function bootstrapGlobal(input: {
|
|||||||
() => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
|
() => input.queryClient.fetchQuery(loadProvidersQuery(null, input.globalSDK)),
|
||||||
() => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
|
() => input.queryClient.fetchQuery(loadPathQuery(null, input.globalSDK)),
|
||||||
() =>
|
() =>
|
||||||
input.queryClient.fetchQuery(
|
input.queryClient
|
||||||
loadProjectsQuery(input.globalSDK, (data) => input.setGlobalStore("project", data ?? [])),
|
.fetchQuery(loadProjectsQuery(input.globalSDK))
|
||||||
),
|
.then((data) => input.setGlobalStore("project", data)),
|
||||||
]
|
]
|
||||||
await runAll(slow)
|
await runAll(slow)
|
||||||
// showErrors({
|
// showErrors({
|
||||||
@@ -197,46 +178,22 @@ function warmSessions(input: {
|
|||||||
).then(() => undefined)
|
).then(() => undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadProvidersQuery = (directory: string | null, sdk?: OpencodeClient) =>
|
export const loadProvidersQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [directory, "providers"],
|
queryKey: [directory, "providers"],
|
||||||
queryFn: sdk ? () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))) : skipToken,
|
queryFn: () => retry(() => sdk.provider.list().then((x) => normalizeProviderList(x.data!))),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadAgentsQuery = (
|
export const loadAgentsQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||||
directory: string | null,
|
|
||||||
sdk?: OpencodeClient,
|
|
||||||
transform?: (x: Awaited<ReturnType<OpencodeClient["app"]["agents"]>>) => void,
|
|
||||||
) =>
|
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: [directory, "agents"],
|
queryKey: [directory, "agents"],
|
||||||
queryFn: sdk
|
queryFn: () => retry(() => sdk.app.agents().then((x) => normalizeAgentList(x.data))),
|
||||||
? () =>
|
|
||||||
retry(() =>
|
|
||||||
sdk.app.agents().then((x) => {
|
|
||||||
transform?.(x)
|
|
||||||
return x.data!
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: skipToken,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadPathQuery = (
|
export const loadPathQuery = (directory: string | null, sdk: OpencodeClient) =>
|
||||||
directory: string | null,
|
|
||||||
sdk?: OpencodeClient,
|
|
||||||
transform?: (x: Awaited<ReturnType<OpencodeClient["path"]["get"]>>) => void,
|
|
||||||
) =>
|
|
||||||
queryOptions<Path>({
|
queryOptions<Path>({
|
||||||
queryKey: [directory, "path"],
|
queryKey: [directory, "path"],
|
||||||
queryFn: sdk
|
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
||||||
? () =>
|
|
||||||
retry(() =>
|
|
||||||
sdk.path.get().then(async (x) => {
|
|
||||||
transform?.(x)
|
|
||||||
return x.data!
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: skipToken,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function bootstrapDirectory(input: {
|
export async function bootstrapDirectory(input: {
|
||||||
@@ -271,9 +228,9 @@ export async function bootstrapDirectory(input: {
|
|||||||
const slow = [
|
const slow = [
|
||||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||||
() =>
|
() =>
|
||||||
input.queryClient.ensureQueryData(
|
input.queryClient
|
||||||
loadAgentsQuery(input.directory, input.sdk, (x) => input.setStore("agent", normalizeAgentList(x.data))),
|
.ensureQueryData(loadAgentsQuery(input.directory, input.sdk))
|
||||||
),
|
.then((data) => input.setStore("agent", data)),
|
||||||
() =>
|
() =>
|
||||||
retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
|
retry(() => input.sdk.config.get().then((x) => input.setStore("config", reconcile(x.data!, { merge: false })))),
|
||||||
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
||||||
@@ -281,12 +238,10 @@ export async function bootstrapDirectory(input: {
|
|||||||
(() => retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id))),
|
(() => retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id))),
|
||||||
!seededPath &&
|
!seededPath &&
|
||||||
(() =>
|
(() =>
|
||||||
input.queryClient.ensureQueryData(
|
input.queryClient.ensureQueryData(loadPathQuery(input.directory, input.sdk)).then((data) => {
|
||||||
loadPathQuery(input.directory, input.sdk, (x) => {
|
const next = projectID(data.directory ?? input.directory, input.global.project)
|
||||||
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
|
||||||
if (next) input.setStore("project", next)
|
if (next) input.setStore("project", next)
|
||||||
}),
|
})),
|
||||||
)),
|
|
||||||
() =>
|
() =>
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.sdk.vcs.get().then((x) => {
|
input.sdk.vcs.get().then((x) => {
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ export function getProjectAvatarSource(id?: string, icon?: { color?: string; url
|
|||||||
return icon?.url
|
return icon?.url
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => {
|
export const ProjectIcon = (props: {
|
||||||
|
project: LocalProject
|
||||||
|
class?: string
|
||||||
|
notify?: boolean
|
||||||
|
working?: boolean
|
||||||
|
}): JSX.Element => {
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
@@ -65,6 +70,11 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={props.working}>
|
||||||
|
<div class="absolute bottom-px right-px size-3 rounded-full bg-background-base z-10 flex items-center justify-center">
|
||||||
|
<Spinner class="size-[9px]" />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const ProjectTile = (props: {
|
|||||||
sidebarHovering: Accessor<boolean>
|
sidebarHovering: Accessor<boolean>
|
||||||
selected: Accessor<boolean>
|
selected: Accessor<boolean>
|
||||||
active: Accessor<boolean>
|
active: Accessor<boolean>
|
||||||
|
isWorking: Accessor<boolean>
|
||||||
overlay: Accessor<boolean>
|
overlay: Accessor<boolean>
|
||||||
suppressHover: Accessor<boolean>
|
suppressHover: Accessor<boolean>
|
||||||
dirs: Accessor<string[]>
|
dirs: Accessor<string[]>
|
||||||
@@ -143,7 +144,7 @@ const ProjectTile = (props: {
|
|||||||
}}
|
}}
|
||||||
onBlur={() => props.setOpen(false)}
|
onBlur={() => props.setOpen(false)}
|
||||||
>
|
>
|
||||||
<ProjectIcon project={props.project} notify />
|
<ProjectIcon project={props.project} notify working={props.isWorking()} />
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
<ContextMenu.Portal>
|
<ContextMenu.Portal>
|
||||||
<ContextMenu.Content>
|
<ContextMenu.Content>
|
||||||
@@ -301,6 +302,12 @@ export const SortableProject = (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0])
|
const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0])
|
||||||
|
const isWorking = createMemo(() =>
|
||||||
|
dirs().some((directory) => {
|
||||||
|
const [store] = globalSync.child(directory, { bootstrap: false })
|
||||||
|
return Object.values(store.session_status).some((status) => status?.type === "busy" || status?.type === "retry")
|
||||||
|
}),
|
||||||
|
)
|
||||||
const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow()))
|
const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow()))
|
||||||
const workspaceSessions = (directory: string) => {
|
const workspaceSessions = (directory: string) => {
|
||||||
const [data] = globalSync.child(directory, { bootstrap: false })
|
const [data] = globalSync.child(directory, { bootstrap: false })
|
||||||
@@ -313,6 +320,7 @@ export const SortableProject = (props: {
|
|||||||
sidebarHovering={props.ctx.sidebarHovering}
|
sidebarHovering={props.ctx.sidebarHovering}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
active={active}
|
active={active}
|
||||||
|
isWorking={isWorking}
|
||||||
overlay={overlay}
|
overlay={overlay}
|
||||||
suppressHover={() => state.suppressHover}
|
suppressHover={() => state.suppressHover}
|
||||||
dirs={dirs}
|
dirs={dirs}
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import { Spinner } from "@opencode-ai/ui/spinner"
|
|||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { type Session } from "@opencode-ai/sdk/v2/client"
|
import { type Session } from "@opencode-ai/sdk/v2/client"
|
||||||
import { type LocalProject } from "@/context/layout"
|
import { type LocalProject } from "@/context/layout"
|
||||||
import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
|
import { loadSessionsQueryKey, useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { pathKey } from "@/utils/path-key"
|
import { pathKey } from "@/utils/path-key"
|
||||||
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
|
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
|
||||||
import { sortedRootSessions } from "./helpers"
|
import { sortedRootSessions } from "./helpers"
|
||||||
import { useQuery } from "@tanstack/solid-query"
|
import { useIsFetching } from "@tanstack/solid-query"
|
||||||
|
|
||||||
type InlineEditorComponent = (props: {
|
type InlineEditorComponent = (props: {
|
||||||
id: string
|
id: string
|
||||||
@@ -320,9 +320,9 @@ export const SortableWorkspace = (props: {
|
|||||||
const boot = createMemo(() => open() || active())
|
const boot = createMemo(() => open() || active())
|
||||||
const count = createMemo(() => sessions()?.length ?? 0)
|
const count = createMemo(() => sessions()?.length ?? 0)
|
||||||
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
||||||
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
|
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.directory) }))
|
||||||
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
||||||
const loading = () => query.isLoading && count() === 0
|
const loading = () => fetching() > 0 && count() === 0
|
||||||
const touch = createMediaQuery("(hover: none)")
|
const touch = createMediaQuery("(hover: none)")
|
||||||
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
@@ -427,7 +427,7 @@ export const SortableWorkspace = (props: {
|
|||||||
mobile={props.mobile}
|
mobile={props.mobile}
|
||||||
ctx={props.ctx}
|
ctx={props.ctx}
|
||||||
showNew={showNew}
|
showNew={showNew}
|
||||||
loading={() => query.isLoading && count() === 0}
|
loading={loading}
|
||||||
sessions={sessions}
|
sessions={sessions}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
@@ -454,9 +454,9 @@ export const LocalWorkspace = (props: {
|
|||||||
const slug = createMemo(() => base64Encode(props.project.worktree))
|
const slug = createMemo(() => base64Encode(props.project.worktree))
|
||||||
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
||||||
const count = createMemo(() => sessions()?.length ?? 0)
|
const count = createMemo(() => sessions()?.length ?? 0)
|
||||||
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
|
const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.project.worktree) }))
|
||||||
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
||||||
const loading = () => query.isLoading && count() === 0
|
const loading = () => fetching() > 0 && count() === 0
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
||||||
await globalSync.project.loadSessions(props.project.worktree)
|
await globalSync.project.loadSessions(props.project.worktree)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"يبدأ 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 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
||||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
||||||
@@ -261,8 +261,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 للشهر الأول",
|
"go.cta.promo": "$5 للشهر الأول",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
"استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: حد الاستخدام 3 أضعاف حتى 27 أبريل",
|
|
||||||
"go.graph.free": "مجاني",
|
"go.graph.free": "مجاني",
|
||||||
"go.graph.freePill": "Big Pickle ونماذج مجانية",
|
"go.graph.freePill": "Big Pickle ونماذج مجانية",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -300,7 +298,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "حدود سخية ووصول موثوق",
|
"go.problem.item2": "حدود سخية ووصول موثوق",
|
||||||
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"يتضمن 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 وDeepSeek V4 Pro وDeepSeek V4 Flash",
|
"يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash",
|
||||||
"go.how.title": "كيف يعمل Go",
|
"go.how.title": "كيف يعمل Go",
|
||||||
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
||||||
"go.how.step1.title": "أنشئ حسابًا",
|
"go.how.step1.title": "أنشئ حسابًا",
|
||||||
@@ -324,7 +322,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
||||||
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"لا. 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 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
"لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "كم تكلفة Go؟",
|
"go.faq.q4": "كم تكلفة Go؟",
|
||||||
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
||||||
@@ -347,7 +345,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
"go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"تشمل النماذج المجانية 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 وDeepSeek V4 Pro وDeepSeek V4 Flash مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
"تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
|
"zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||||
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
|
"zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم",
|
||||||
@@ -357,8 +355,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "مفتاح API مفقود.",
|
"zen.api.error.missingApiKey": "مفتاح API مفقود.",
|
||||||
"zen.api.error.invalidApiKey": "مفتاح API غير صالح.",
|
"zen.api.error.invalidApiKey": "مفتاح API غير صالح.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "تم تجاوز حصة الاشتراك. أعد المحاولة خلال {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "تم تجاوز حصة الاشتراك. أعد المحاولة خلال {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"تم تجاوز حصة الاشتراك. يمكنك الاستمرار في استخدام النماذج المجانية.",
|
"تم الوصول إلى حد الاستخدام لمدة 5 ساعات. تتم إعادة التعيين خلال {{retryIn}}. لمواصلة استخدام هذا النموذج الآن، فعّل الاستخدام من رصيدك المتاح: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"تم الوصول إلى حد الاستخدام الأسبوعي. تتم إعادة التعيين خلال {{retryIn}}. لمواصلة استخدام هذا النموذج الآن، فعّل الاستخدام من رصيدك المتاح: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"تم الوصول إلى حد الاستخدام الشهري. تتم إعادة التعيين خلال {{retryIn}}. لمواصلة استخدام هذا النموذج الآن، فعّل الاستخدام من رصيدك المتاح: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "لا توجد طريقة دفع. أضف طريقة دفع هنا: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "لا توجد طريقة دفع. أضف طريقة دفع هنا: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "رصيد غير كاف. إدارة فواتيرك هنا: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "رصيد غير كاف. إدارة فواتيرك هنا: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para 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, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
||||||
@@ -265,8 +265,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 no primeiro mês",
|
"go.cta.promo": "$5 no primeiro mês",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
"Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: limite de uso 3x maior até 27 de abril",
|
|
||||||
"go.graph.free": "Grátis",
|
"go.graph.free": "Grátis",
|
||||||
"go.graph.freePill": "Big Pickle e modelos gratuitos",
|
"go.graph.freePill": "Big Pickle e modelos gratuitos",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -305,7 +303,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Limites generosos e acesso confiável",
|
"go.problem.item2": "Limites generosos e acesso confiável",
|
||||||
"go.problem.item3": "Feito para o maior número possível de programadores",
|
"go.problem.item3": "Feito para o maior número possível de programadores",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Inclui 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, DeepSeek V4 Pro e DeepSeek V4 Flash",
|
"Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash",
|
||||||
"go.how.title": "Como o Go funciona",
|
"go.how.title": "Como o Go funciona",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
||||||
@@ -331,7 +329,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
||||||
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source 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, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Quanto custa o Go?",
|
"go.faq.q4": "Quanto custa o Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "O Go custa",
|
"go.faq.a4.p1.beforePricing": "O Go custa",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
||||||
@@ -355,7 +353,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
"go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui 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, DeepSeek V4 Pro e DeepSeek V4 Flash com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
"Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
|
"zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.",
|
||||||
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
|
"zen.api.error.modelNotSupported": "Modelo {{model}} não suportado",
|
||||||
@@ -365,8 +363,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Chave de API ausente.",
|
"zen.api.error.missingApiKey": "Chave de API ausente.",
|
||||||
"zen.api.error.invalidApiKey": "Chave de API inválida.",
|
"zen.api.error.invalidApiKey": "Chave de API inválida.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Cota de assinatura excedida. Tente novamente em {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Cota de assinatura excedida. Tente novamente em {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Cota de assinatura excedida. Você pode continuar usando modelos gratuitos.",
|
"Limite de uso de 5 horas atingido. Será reiniciado em {{retryIn}}. Para continuar usando este modelo agora, habilite o uso a partir do seu saldo disponível: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Limite de uso semanal atingido. Será reiniciado em {{retryIn}}. Para continuar usando este modelo agora, habilite o uso a partir do seu saldo disponível: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Limite de uso mensal atingido. Será reiniciado em {{retryIn}}. Para continuar usando este modelo agora, habilite o uso a partir do seu saldo disponível: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Nenhuma forma de pagamento. Adicione uma forma de pagamento aqui: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Nenhuma forma de pagamento. Adicione uma forma de pagamento aqui: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gerencie seu faturamento aqui: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gerencie seu faturamento aqui: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for 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, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
||||||
@@ -263,8 +263,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 første måned",
|
"go.cta.promo": "$5 første måned",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
"Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: brugsgrænsen tredoblet til 27. april",
|
|
||||||
"go.graph.free": "Gratis",
|
"go.graph.free": "Gratis",
|
||||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -302,7 +300,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
||||||
"go.problem.item3": "Bygget til så mange programmører som muligt",
|
"go.problem.item3": "Bygget til så mange programmører som muligt",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Inkluderer 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, DeepSeek V4 Pro og DeepSeek V4 Flash",
|
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash",
|
||||||
"go.how.title": "Hvordan Go virker",
|
"go.how.title": "Hvordan Go virker",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
||||||
@@ -328,7 +326,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
||||||
"go.faq.q3": "Er Go det samme som Zen?",
|
"go.faq.q3": "Er Go det samme som Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne 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, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Hvad koster Go?",
|
"go.faq.q4": "Hvad koster Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||||
@@ -351,7 +349,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
"go.faq.q9": "Hvad er forskellen på gratis modeller og Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer 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, DeepSeek V4 Pro og DeepSeek V4 Flash med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).",
|
"Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
|
"zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.",
|
||||||
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
|
"zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke",
|
||||||
@@ -361,8 +359,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Manglende API-nøgle.",
|
"zen.api.error.missingApiKey": "Manglende API-nøgle.",
|
||||||
"zen.api.error.invalidApiKey": "Ugyldig API-nøgle.",
|
"zen.api.error.invalidApiKey": "Ugyldig API-nøgle.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igen om {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igen om {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Abonnementskvote overskredet. Du kan fortsætte med at bruge gratis modeller.",
|
"Forbrugsgrænsen for 5 timer er nået. Nulstilles om {{retryIn}}. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Ugentlig forbrugsgrænse er nået. Nulstilles om {{retryIn}}. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Månedlig forbrugsgrænse er nået. Nulstilles om {{retryIn}}. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Tilføj en betalingsmetode her: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Tilføj en betalingsmetode her: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Utilstrækkelig saldo. Administrer din fakturering her: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Utilstrækkelig saldo. Administrer din fakturering her: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für 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, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
||||||
@@ -265,8 +265,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 im ersten Monat",
|
"go.cta.promo": "$5 im ersten Monat",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
"Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: Nutzungslimit bis zum 27. April verdreifacht",
|
|
||||||
"go.graph.free": "Kostenlos",
|
"go.graph.free": "Kostenlos",
|
||||||
"go.graph.freePill": "Big Pickle und kostenlose Modelle",
|
"go.graph.freePill": "Big Pickle und kostenlose Modelle",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -304,7 +302,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
||||||
"go.problem.item3": "Für so viele Programmierer wie möglich gebaut",
|
"go.problem.item3": "Für so viele Programmierer wie möglich gebaut",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Beinhaltet 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, DeepSeek V4 Pro und DeepSeek V4 Flash",
|
"Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash",
|
||||||
"go.how.title": "Wie Go funktioniert",
|
"go.how.title": "Wie Go funktioniert",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
||||||
@@ -330,7 +328,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
||||||
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen 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, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Wie viel kostet Go?",
|
"go.faq.q4": "Wie viel kostet Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go kostet",
|
"go.faq.a4.p1.beforePricing": "Go kostet",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
||||||
@@ -354,7 +352,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
"go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet 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, DeepSeek V4 Pro und DeepSeek V4 Flash mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
"Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
|
"zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.",
|
||||||
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
|
"zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt",
|
||||||
@@ -364,8 +362,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Fehlender API-Key.",
|
"zen.api.error.missingApiKey": "Fehlender API-Key.",
|
||||||
"zen.api.error.invalidApiKey": "Ungültiger API-Key.",
|
"zen.api.error.invalidApiKey": "Ungültiger API-Key.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnement-Quote überschritten. Erneuter Versuch in {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnement-Quote überschritten. Erneuter Versuch in {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Abonnement-Quote überschritten. Du kannst weiterhin kostenlose Modelle nutzen.",
|
"5-Stunden-Nutzungslimit erreicht. Wird in {{retryIn}} zurückgesetzt. Um dieses Modell jetzt weiter zu nutzen, aktiviere die Nutzung über dein verfügbares Guthaben: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Wöchentliches Nutzungslimit erreicht. Wird in {{retryIn}} zurückgesetzt. Um dieses Modell jetzt weiter zu nutzen, aktiviere die Nutzung über dein verfügbares Guthaben: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Monatliches Nutzungslimit erreicht. Wird in {{retryIn}} zurückgesetzt. Um dieses Modell jetzt weiter zu nutzen, aktiviere die Nutzung über dein verfügbares Guthaben: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Keine Zahlungsmethode. Füge hier eine Zahlungsmethode hinzu: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Keine Zahlungsmethode. Füge hier eine Zahlungsmethode hinzu: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Unzureichendes Guthaben. Verwalte deine Abrechnung hier: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Unzureichendes Guthaben. Verwalte deine Abrechnung hier: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -248,9 +248,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for 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, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
"Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6 gets 3× usage limits through April 27",
|
|
||||||
"go.hero.title": "Low cost coding models for everyone",
|
"go.hero.title": "Low cost coding models for everyone",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
||||||
@@ -298,7 +296,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Generous limits and reliable access",
|
"go.problem.item2": "Generous limits and reliable access",
|
||||||
"go.problem.item3": "Built for as many programmers as possible",
|
"go.problem.item3": "Built for as many programmers as possible",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Includes 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, DeepSeek V4 Pro, and DeepSeek V4 Flash",
|
"Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash",
|
||||||
"go.how.title": "How Go works",
|
"go.how.title": "How Go works",
|
||||||
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
||||||
"go.how.step1.title": "Create an account",
|
"go.how.step1.title": "Create an account",
|
||||||
@@ -323,7 +321,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
||||||
"go.faq.q3": "Is Go the same as Zen?",
|
"go.faq.q3": "Is Go the same as Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models 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, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "How much does Go cost?",
|
"go.faq.q4": "How much does Go cost?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go costs",
|
"go.faq.a4.p1.beforePricing": "Go costs",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 first month",
|
"go.faq.a4.p1.pricingLink": "$5 first month",
|
||||||
@@ -347,7 +345,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "What is the difference between free models and Go?",
|
"go.faq.q9": "What is the difference between free models and Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes 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, DeepSeek V4 Pro, and DeepSeek V4 Flash with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
"Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
"zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.",
|
||||||
"zen.api.error.modelNotSupported": "Model {{model}} not supported",
|
"zen.api.error.modelNotSupported": "Model {{model}} not supported",
|
||||||
@@ -357,8 +355,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Missing API key.",
|
"zen.api.error.missingApiKey": "Missing API key.",
|
||||||
"zen.api.error.invalidApiKey": "Invalid API key.",
|
"zen.api.error.invalidApiKey": "Invalid API key.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Subscription quota exceeded. You can continue using free models.",
|
"5-hour usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Weekly usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Monthly usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "No payment method. Add a payment method here: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "No payment method. Add a payment method here: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Insufficient balance. Manage your billing here: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Insufficient balance. Manage your billing here: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para 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, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
"Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
||||||
@@ -266,8 +266,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 el primer mes",
|
"go.cta.promo": "$5 el primer mes",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
"Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: límite de uso triplicado hasta el 27 de abril",
|
|
||||||
"go.graph.free": "Gratis",
|
"go.graph.free": "Gratis",
|
||||||
"go.graph.freePill": "Big Pickle y modelos gratuitos",
|
"go.graph.freePill": "Big Pickle y modelos gratuitos",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -306,7 +304,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Límites generosos y acceso fiable",
|
"go.problem.item2": "Límites generosos y acceso fiable",
|
||||||
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Incluye 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, DeepSeek V4 Pro y DeepSeek V4 Flash",
|
"Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash",
|
||||||
"go.how.title": "Cómo funciona Go",
|
"go.how.title": "Cómo funciona Go",
|
||||||
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
||||||
"go.how.step1.title": "Crear una cuenta",
|
"go.how.step1.title": "Crear una cuenta",
|
||||||
@@ -331,7 +329,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
||||||
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto 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, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
"No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "¿Cuánto cuesta Go?",
|
"go.faq.q4": "¿Cuánto cuesta Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
||||||
@@ -355,7 +353,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
"go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye 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, DeepSeek V4 Pro y DeepSeek V4 Flash con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).",
|
"Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
|
"zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.",
|
||||||
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
|
"zen.api.error.modelNotSupported": "Modelo {{model}} no soportado",
|
||||||
@@ -365,8 +363,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Falta la clave API.",
|
"zen.api.error.missingApiKey": "Falta la clave API.",
|
||||||
"zen.api.error.invalidApiKey": "Clave API inválida.",
|
"zen.api.error.invalidApiKey": "Clave API inválida.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Cuota de suscripción excedida. Reintenta en {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Cuota de suscripción excedida. Reintenta en {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Cuota de suscripción excedida. Puedes continuar usando modelos gratuitos.",
|
"Límite de uso de 5 horas alcanzado. Se restablece en {{retryIn}}. Para seguir usando este modelo ahora, habilita el uso desde tu saldo disponible: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Límite de uso semanal alcanzado. Se restablece en {{retryIn}}. Para seguir usando este modelo ahora, habilita el uso desde tu saldo disponible: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Límite de uso mensual alcanzado. Se restablece en {{retryIn}}. Para seguir usando este modelo ahora, habilita el uso desde tu saldo disponible: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Sin método de pago. Añade un método de pago aquí: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Sin método de pago. Añade un método de pago aquí: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gestiona tu facturación aquí: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Saldo insuficiente. Gestiona tu facturación aquí: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour 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, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modèles de code à faible coût pour tous",
|
"go.hero.title": "Modèles de code à faible coût pour tous",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.",
|
"Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.",
|
||||||
@@ -267,8 +267,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 le premier mois",
|
"go.cta.promo": "$5 le premier mois",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
"Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6 : limites d’utilisation triplées jusqu’au 27 avril",
|
|
||||||
"go.graph.free": "Gratuit",
|
"go.graph.free": "Gratuit",
|
||||||
"go.graph.freePill": "Big Pickle et modèles gratuits",
|
"go.graph.freePill": "Big Pickle et modèles gratuits",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -306,7 +304,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Limites généreuses et accès fiable",
|
"go.problem.item2": "Limites généreuses et accès fiable",
|
||||||
"go.problem.item3": "Conçu pour autant de programmeurs que possible",
|
"go.problem.item3": "Conçu pour autant de programmeurs que possible",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Inclut 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, DeepSeek V4 Pro et DeepSeek V4 Flash",
|
"Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash",
|
||||||
"go.how.title": "Comment fonctionne Go",
|
"go.how.title": "Comment fonctionne Go",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.",
|
"Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.",
|
||||||
@@ -332,7 +330,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.",
|
"go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.",
|
||||||
"go.faq.q3": "Est-ce que Go est la même chose que Zen ?",
|
"go.faq.q3": "Est-ce que Go est la même chose que Zen ?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source 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, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
"Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Combien coûte Go ?",
|
"go.faq.q4": "Combien coûte Go ?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go coûte",
|
"go.faq.a4.p1.beforePricing": "Go coûte",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 le premier mois",
|
"go.faq.a4.p1.pricingLink": "$5 le premier mois",
|
||||||
@@ -355,7 +353,7 @@ export const dict = {
|
|||||||
"Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.",
|
"Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.",
|
||||||
"go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?",
|
"go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut 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, DeepSeek V4 Pro et DeepSeek V4 Flash avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
"Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.",
|
"zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.",
|
||||||
"zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge",
|
"zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge",
|
||||||
@@ -365,8 +363,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Clé API manquante.",
|
"zen.api.error.missingApiKey": "Clé API manquante.",
|
||||||
"zen.api.error.invalidApiKey": "Clé API invalide.",
|
"zen.api.error.invalidApiKey": "Clé API invalide.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Quota d'abonnement dépassé. Réessayez dans {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Quota d'abonnement dépassé. Réessayez dans {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Quota d'abonnement dépassé. Vous pouvez continuer à utiliser les modèles gratuits.",
|
"Limite d'utilisation sur 5 heures atteinte. Réinitialisation dans {{retryIn}}. Pour continuer à utiliser ce modèle dès maintenant, activez l'utilisation depuis votre solde disponible : {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Limite d'utilisation hebdomadaire atteinte. Réinitialisation dans {{retryIn}}. Pour continuer à utiliser ce modèle dès maintenant, activez l'utilisation depuis votre solde disponible : {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Limite d'utilisation mensuelle atteinte. Réinitialisation dans {{retryIn}}. Pour continuer à utiliser ce modèle dès maintenant, activez l'utilisation depuis votre solde disponible : {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Aucune méthode de paiement. Ajoutez une méthode de paiement ici : {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Aucune méthode de paiement. Ajoutez une méthode de paiement ici : {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Solde insuffisant. Gérez votre facturation ici : {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Solde insuffisant. Gérez votre facturation ici : {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per 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, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.",
|
"Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.",
|
||||||
@@ -263,8 +263,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 il primo mese",
|
"go.cta.promo": "$5 il primo mese",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
"Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: limite d'uso triplicato fino al 27 aprile",
|
|
||||||
"go.graph.free": "Gratis",
|
"go.graph.free": "Gratis",
|
||||||
"go.graph.freePill": "Big Pickle e modelli gratuiti",
|
"go.graph.freePill": "Big Pickle e modelli gratuiti",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -302,7 +300,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Limiti generosi e accesso affidabile",
|
"go.problem.item2": "Limiti generosi e accesso affidabile",
|
||||||
"go.problem.item3": "Costruito per il maggior numero possibile di programmatori",
|
"go.problem.item3": "Costruito per il maggior numero possibile di programmatori",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Include 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, DeepSeek V4 Pro e DeepSeek V4 Flash",
|
"Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash",
|
||||||
"go.how.title": "Come funziona Go",
|
"go.how.title": "Come funziona Go",
|
||||||
"go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.",
|
"go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.",
|
||||||
"go.how.step1.title": "Crea un account",
|
"go.how.step1.title": "Crea un account",
|
||||||
@@ -327,7 +325,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.",
|
"go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.",
|
||||||
"go.faq.q3": "Go è lo stesso di Zen?",
|
"go.faq.q3": "Go è lo stesso di Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source 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, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
"No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Quanto costa Go?",
|
"go.faq.q4": "Quanto costa Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go costa",
|
"go.faq.a4.p1.beforePricing": "Go costa",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 il primo mese",
|
"go.faq.a4.p1.pricingLink": "$5 il primo mese",
|
||||||
@@ -351,7 +349,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?",
|
"go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include 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, DeepSeek V4 Pro e DeepSeek V4 Flash con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
"I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.",
|
"zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.",
|
||||||
"zen.api.error.modelNotSupported": "Modello {{model}} non supportato",
|
"zen.api.error.modelNotSupported": "Modello {{model}} non supportato",
|
||||||
@@ -361,8 +359,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Chiave API mancante.",
|
"zen.api.error.missingApiKey": "Chiave API mancante.",
|
||||||
"zen.api.error.invalidApiKey": "Chiave API non valida.",
|
"zen.api.error.invalidApiKey": "Chiave API non valida.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Quota dell'abbonamento superata. Riprova tra {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Quota dell'abbonamento superata. Riprova tra {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Quota dell'abbonamento superata. Puoi continuare a utilizzare modelli gratuiti.",
|
"Limite di utilizzo di 5 ore raggiunto. Si reimposta tra {{retryIn}}. Per continuare a usare questo modello ora, abilita l'utilizzo dal tuo saldo disponibile: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Limite di utilizzo settimanale raggiunto. Si reimposta tra {{retryIn}}. Per continuare a usare questo modello ora, abilita l'utilizzo dal tuo saldo disponibile: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Limite di utilizzo mensile raggiunto. Si reimposta tra {{retryIn}}. Per continuare a usare questo modello ora, abilita l'utilizzo dal tuo saldo disponibile: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Nessun metodo di pagamento. Aggiungi un metodo di pagamento qui: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Nessun metodo di pagamento. Aggiungi un metodo di pagamento qui: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Saldo insufficiente. Gestisci la tua fatturazione qui: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Saldo insufficiente. Gestisci la tua fatturazione qui: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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、DeepSeek V4 Pro、DeepSeek V4 Flashに対して5時間のゆとりあるリクエスト上限があります。",
|
"Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashに対して5時間のゆとりあるリクエスト上限があります。",
|
||||||
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。",
|
"Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。",
|
||||||
@@ -262,8 +262,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "初月 $5",
|
"go.cta.promo": "初月 $5",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
"どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6、4月27日まで利用上限が3倍に",
|
|
||||||
"go.graph.free": "無料",
|
"go.graph.free": "無料",
|
||||||
"go.graph.freePill": "Big Pickleと無料モデル",
|
"go.graph.freePill": "Big Pickleと無料モデル",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -302,7 +300,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "十分な制限と安定したアクセス",
|
"go.problem.item2": "十分な制限と安定したアクセス",
|
||||||
"go.problem.item3": "できるだけ多くのプログラマーのために構築",
|
"go.problem.item3": "できるだけ多くのプログラマーのために構築",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"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、DeepSeek V4 Pro、DeepSeek V4 Flashを含む",
|
"GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashを含む",
|
||||||
"go.how.title": "Goの仕組み",
|
"go.how.title": "Goの仕組み",
|
||||||
"go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。",
|
"go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。",
|
||||||
"go.how.step1.title": "アカウントを作成",
|
"go.how.step1.title": "アカウントを作成",
|
||||||
@@ -327,7 +325,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。",
|
"go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。",
|
||||||
"go.faq.q3": "GoはZenと同じですか?",
|
"go.faq.q3": "GoはZenと同じですか?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"いいえ。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、DeepSeek V4 Pro、DeepSeek V4 Flashのオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。",
|
"いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashのオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。",
|
||||||
"go.faq.q4": "Goの料金は?",
|
"go.faq.q4": "Goの料金は?",
|
||||||
"go.faq.a4.p1.beforePricing": "Goは",
|
"go.faq.a4.p1.beforePricing": "Goは",
|
||||||
"go.faq.a4.p1.pricingLink": "最初の月$5",
|
"go.faq.a4.p1.pricingLink": "最初の月$5",
|
||||||
@@ -351,7 +349,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "無料モデルとGoの違いは何ですか?",
|
"go.faq.q9": "無料モデルとGoの違いは何ですか?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日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、DeepSeek V4 Pro、DeepSeek V4 Flashが含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。",
|
"無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashが含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。",
|
"zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。",
|
||||||
"zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません",
|
"zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません",
|
||||||
@@ -362,8 +360,12 @@ export const dict = {
|
|||||||
"zen.api.error.invalidApiKey": "無効なAPIキーです。",
|
"zen.api.error.invalidApiKey": "無効なAPIキーです。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded":
|
"zen.api.error.subscriptionQuotaExceeded":
|
||||||
"サブスクリプションの制限を超えました。{{retryIn}} 後に再試行してください。",
|
"サブスクリプションの制限を超えました。{{retryIn}} 後に再試行してください。",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"サブスクリプションの制限を超えました。無料モデルは引き続きご利用いただけます。",
|
"5時間の利用上限に達しました。{{retryIn}} 後にリセットされます。今すぐこのモデルの利用を続けるには、利用可能な残高からの利用を有効化してください: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"週間の利用上限に達しました。{{retryIn}} 後にリセットされます。今すぐこのモデルの利用を続けるには、利用可能な残高からの利用を有効化してください: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"月間の利用上限に達しました。{{retryIn}} 後にリセットされます。今すぐこのモデルの利用を続けるには、利用可能な残高からの利用を有効化してください: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "お支払い方法がありません。こちらからお支払い方法を追加してください: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "お支払い方法がありません。こちらからお支払い方法を追加してください: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "残高が不足しています。こちらから請求を管理してください: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "残高が不足しています。こちらから請求を管理してください: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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, DeepSeek V4 Pro, DeepSeek V4 Flash에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
"Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash에 대해 넉넉한 5시간 요청 한도를 제공합니다.",
|
||||||
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.",
|
"Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.",
|
||||||
@@ -259,8 +259,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "첫 달 $5",
|
"go.cta.promo": "첫 달 $5",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
"어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6, 4월 27일까지 사용 한도 3배 확대",
|
|
||||||
"go.graph.free": "무료",
|
"go.graph.free": "무료",
|
||||||
"go.graph.freePill": "Big Pickle 및 무료 모델",
|
"go.graph.freePill": "Big Pickle 및 무료 모델",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -299,7 +297,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "넉넉한 한도와 안정적인 액세스",
|
"go.problem.item2": "넉넉한 한도와 안정적인 액세스",
|
||||||
"go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨",
|
"go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"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, DeepSeek V4 Pro, DeepSeek V4 Flash 포함",
|
"GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash 포함",
|
||||||
"go.how.title": "Go 작동 방식",
|
"go.how.title": "Go 작동 방식",
|
||||||
"go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.",
|
"go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.",
|
||||||
"go.how.step1.title": "계정 생성",
|
"go.how.step1.title": "계정 생성",
|
||||||
@@ -323,7 +321,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.",
|
"go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.",
|
||||||
"go.faq.q3": "Go는 Zen과 같은가요?",
|
"go.faq.q3": "Go는 Zen과 같은가요?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"아니요. 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, DeepSeek V4 Pro, DeepSeek V4 Flash 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
"아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
||||||
"go.faq.q4": "Go 비용은 얼마인가요?",
|
"go.faq.q4": "Go 비용은 얼마인가요?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go 비용은",
|
"go.faq.a4.p1.beforePricing": "Go 비용은",
|
||||||
"go.faq.a4.p1.pricingLink": "첫 달 $5",
|
"go.faq.a4.p1.pricingLink": "첫 달 $5",
|
||||||
@@ -346,7 +344,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?",
|
"go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"무료 모델에는 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, DeepSeek V4 Pro, DeepSeek V4 Flash를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
"무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.",
|
"zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.",
|
||||||
"zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다",
|
"zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다",
|
||||||
@@ -356,8 +354,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "API 키가 누락되었습니다.",
|
"zen.api.error.missingApiKey": "API 키가 누락되었습니다.",
|
||||||
"zen.api.error.invalidApiKey": "유효하지 않은 API 키입니다.",
|
"zen.api.error.invalidApiKey": "유효하지 않은 API 키입니다.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "구독 할당량을 초과했습니다. {{retryIn}} 후 다시 시도해 주세요.",
|
"zen.api.error.subscriptionQuotaExceeded": "구독 할당량을 초과했습니다. {{retryIn}} 후 다시 시도해 주세요.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"구독 할당량을 초과했습니다. 무료 모델은 계속 사용할 수 있습니다.",
|
"5시간 사용 한도에 도달했습니다. {{retryIn}} 후 초기화됩니다. 이 모델을 지금 계속 사용하려면 사용 가능한 잔액에서 사용을 활성화하세요: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"주간 사용 한도에 도달했습니다. {{retryIn}} 후 초기화됩니다. 이 모델을 지금 계속 사용하려면 사용 가능한 잔액에서 사용을 활성화하세요: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"월간 사용 한도에 도달했습니다. {{retryIn}} 후 초기화됩니다. 이 모델을 지금 계속 사용하려면 사용 가능한 잔액에서 사용을 활성화하세요: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "결제 수단이 없습니다. 결제 수단을 추가하세요: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "결제 수단이 없습니다. 결제 수단을 추가하세요: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "잔액이 부족합니다. 결제 관리를 여기서 하세요: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "잔액이 부족합니다. 결제 관리를 여기서 하세요: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for 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, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Rimelige kodemodeller for alle",
|
"go.hero.title": "Rimelige kodemodeller for alle",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.",
|
"Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.",
|
||||||
@@ -263,8 +263,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 første måned",
|
"go.cta.promo": "$5 første måned",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
"Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: bruksgrensen er tredoblet til 27. april",
|
|
||||||
"go.graph.free": "Gratis",
|
"go.graph.free": "Gratis",
|
||||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -302,7 +300,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Rause grenser og pålitelig tilgang",
|
"go.problem.item2": "Rause grenser og pålitelig tilgang",
|
||||||
"go.problem.item3": "Bygget for så mange programmerere som mulig",
|
"go.problem.item3": "Bygget for så mange programmerere som mulig",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Inkluderer 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, DeepSeek V4 Pro og DeepSeek V4 Flash",
|
"Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash",
|
||||||
"go.how.title": "Hvordan Go fungerer",
|
"go.how.title": "Hvordan Go fungerer",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.",
|
"Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.",
|
||||||
@@ -328,7 +326,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.",
|
"go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.",
|
||||||
"go.faq.q3": "Er Go det samme som Zen?",
|
"go.faq.q3": "Er Go det samme som Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene 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, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
"Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Hva koster Go?",
|
"go.faq.q4": "Hva koster Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||||
@@ -352,7 +350,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?",
|
"go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer 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, DeepSeek V4 Pro og DeepSeek V4 Flash med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).",
|
"Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.",
|
"zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.",
|
||||||
"zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke",
|
"zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke",
|
||||||
@@ -362,8 +360,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Mangler API-nøkkel.",
|
"zen.api.error.missingApiKey": "Mangler API-nøkkel.",
|
||||||
"zen.api.error.invalidApiKey": "Ugyldig API-nøkkel.",
|
"zen.api.error.invalidApiKey": "Ugyldig API-nøkkel.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igjen om {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igjen om {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Abonnementskvote overskredet. Du kan fortsette å bruke gratis modeller.",
|
"5-timers bruksgrense nådd. Tilbakestilles om {{retryIn}}. For å fortsette å bruke denne modellen nå, aktiver bruk fra din tilgjengelige saldo: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Ukentlig bruksgrense nådd. Tilbakestilles om {{retryIn}}. For å fortsette å bruke denne modellen nå, aktiver bruk fra din tilgjengelige saldo: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Månedlig bruksgrense nådd. Tilbakestilles om {{retryIn}}. For å fortsette å bruke denne modellen nå, aktiver bruk fra din tilgjengelige saldo: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Legg til en betalingsmetode her: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Ingen betalingsmetode. Legg til en betalingsmetode her: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Utilstrekkelig saldo. Administrer faktureringen din her: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Utilstrekkelig saldo. Administrer faktureringen din her: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla 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, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.",
|
"Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.",
|
||||||
@@ -264,8 +264,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 pierwszy miesiąc",
|
"go.cta.promo": "$5 pierwszy miesiąc",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
"Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: limit użycia zwiększony 3× do 27 kwietnia",
|
|
||||||
"go.graph.free": "Darmowe",
|
"go.graph.free": "Darmowe",
|
||||||
"go.graph.freePill": "Big Pickle i darmowe modele",
|
"go.graph.freePill": "Big Pickle i darmowe modele",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -303,7 +301,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Hojne limity i niezawodny dostęp",
|
"go.problem.item2": "Hojne limity i niezawodny dostęp",
|
||||||
"go.problem.item3": "Stworzony dla jak największej liczby programistów",
|
"go.problem.item3": "Stworzony dla jak największej liczby programistów",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Zawiera 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, DeepSeek V4 Pro i DeepSeek V4 Flash",
|
"Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash",
|
||||||
"go.how.title": "Jak działa Go",
|
"go.how.title": "Jak działa Go",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.",
|
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.",
|
||||||
@@ -329,7 +327,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.",
|
"go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.",
|
||||||
"go.faq.q3": "Czy Go to to samo co Zen?",
|
"go.faq.q3": "Czy Go to to samo co Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source 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, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
"Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Ile kosztuje Go?",
|
"go.faq.q4": "Ile kosztuje Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go kosztuje",
|
"go.faq.a4.p1.beforePricing": "Go kosztuje",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc",
|
"go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc",
|
||||||
@@ -353,7 +351,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?",
|
"go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera 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, DeepSeek V4 Pro i DeepSeek V4 Flash z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
"Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.",
|
"zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.",
|
||||||
"zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany",
|
"zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany",
|
||||||
@@ -363,8 +361,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Brak klucza API.",
|
"zen.api.error.missingApiKey": "Brak klucza API.",
|
||||||
"zen.api.error.invalidApiKey": "Nieprawidłowy klucz API.",
|
"zen.api.error.invalidApiKey": "Nieprawidłowy klucz API.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Przekroczono limit subskrypcji. Spróbuj ponownie za {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Przekroczono limit subskrypcji. Spróbuj ponownie za {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Przekroczono limit subskrypcji. Możesz kontynuować korzystanie z darmowych modeli.",
|
"Osiągnięto 5-godzinny limit użycia. Resetuje się za {{retryIn}}. Aby nadal korzystać z tego modelu, włącz użycie z dostępnego salda: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Osiągnięto tygodniowy limit użycia. Resetuje się za {{retryIn}}. Aby nadal korzystać z tego modelu, włącz użycie z dostępnego salda: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Osiągnięto miesięczny limit użycia. Resetuje się za {{retryIn}}. Aby nadal korzystać z tego modelu, włącz użycie z dostępnego salda: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Brak metody płatności. Dodaj metodę płatności tutaj: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Brak metody płatności. Dodaj metodę płatności tutaj: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Niewystarczające saldo. Zarządzaj swoimi płatnościami tutaj: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Niewystarczające saldo. Zarządzaj swoimi płatnościami tutaj: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
"Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
||||||
"go.hero.title": "Недорогие модели для кодинга для всех",
|
"go.hero.title": "Недорогие модели для кодинга для всех",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.",
|
"Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.",
|
||||||
@@ -267,8 +267,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "$5 первый месяц",
|
"go.cta.promo": "$5 первый месяц",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
"Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: лимит использования увеличен в 3 раза до 27 апреля",
|
|
||||||
"go.graph.free": "Бесплатно",
|
"go.graph.free": "Бесплатно",
|
||||||
"go.graph.freePill": "Big Pickle и бесплатные модели",
|
"go.graph.freePill": "Big Pickle и бесплатные модели",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -307,7 +305,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Щедрые лимиты и надежный доступ",
|
"go.problem.item2": "Щедрые лимиты и надежный доступ",
|
||||||
"go.problem.item3": "Создан для максимального числа программистов",
|
"go.problem.item3": "Создан для максимального числа программистов",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"Включает 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, DeepSeek V4 Pro и DeepSeek V4 Flash",
|
"Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash",
|
||||||
"go.how.title": "Как работает Go",
|
"go.how.title": "Как работает Go",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.",
|
"Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.",
|
||||||
@@ -333,7 +331,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.",
|
"go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.",
|
||||||
"go.faq.q3": "Go — это то же самое, что и Zen?",
|
"go.faq.q3": "Go — это то же самое, что и Zen?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Нет. 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, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
"Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash.",
|
||||||
"go.faq.q4": "Сколько стоит Go?",
|
"go.faq.q4": "Сколько стоит Go?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go стоит",
|
"go.faq.a4.p1.beforePricing": "Go стоит",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 за первый месяц",
|
"go.faq.a4.p1.pricingLink": "$5 за первый месяц",
|
||||||
@@ -357,7 +355,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "В чем разница между бесплатными моделями и Go?",
|
"go.faq.q9": "В чем разница между бесплатными моделями и Go?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Бесплатные модели включают 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, DeepSeek V4 Pro и DeepSeek V4 Flash с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).",
|
"Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.",
|
"zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.",
|
||||||
"zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается",
|
"zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается",
|
||||||
@@ -367,8 +365,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "Отсутствует API ключ.",
|
"zen.api.error.missingApiKey": "Отсутствует API ключ.",
|
||||||
"zen.api.error.invalidApiKey": "Неверный API ключ.",
|
"zen.api.error.invalidApiKey": "Неверный API ключ.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Квота подписки превышена. Повторите попытку через {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Квота подписки превышена. Повторите попытку через {{retryIn}}.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Квота подписки превышена. Вы можете продолжить использовать бесплатные модели.",
|
"Достигнут лимит использования за 5 часов. Сбросится через {{retryIn}}. Чтобы продолжить использовать эту модель сейчас, включите оплату с доступного баланса: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Достигнут недельный лимит использования. Сбросится через {{retryIn}}. Чтобы продолжить использовать эту модель сейчас, включите оплату с доступного баланса: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Достигнут месячный лимит использования. Сбросится через {{retryIn}}. Чтобы продолжить использовать эту модель сейчас, включите оплату с доступного баланса: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Нет способа оплаты. Добавьте способ оплаты здесь: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Нет способа оплаты. Добавьте способ оплаты здесь: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Недостаточно средств. Управляйте оплатой здесь: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Недостаточно средств. Управляйте оплатой здесь: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
||||||
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน",
|
"Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน",
|
||||||
@@ -261,8 +261,6 @@ export const dict = {
|
|||||||
"go.cta.price": "$10/เดือน",
|
"go.cta.price": "$10/เดือน",
|
||||||
"go.cta.promo": "$5 เดือนแรก",
|
"go.cta.promo": "$5 เดือนแรก",
|
||||||
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6 โควตาการใช้งานเพิ่มเป็น 3 เท่า ถึง 27 เม.ย.",
|
|
||||||
"go.graph.free": "ฟรี",
|
"go.graph.free": "ฟรี",
|
||||||
"go.graph.freePill": "Big Pickle และโมเดลฟรี",
|
"go.graph.freePill": "Big Pickle และโมเดลฟรี",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -300,7 +298,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้",
|
"go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้",
|
||||||
"go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้",
|
"go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"รวมถึง 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, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
"รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash",
|
||||||
"go.how.title": "Go ทำงานอย่างไร",
|
"go.how.title": "Go ทำงานอย่างไร",
|
||||||
"go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้",
|
"go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้",
|
||||||
"go.how.step1.title": "สร้างบัญชี",
|
"go.how.step1.title": "สร้างบัญชี",
|
||||||
@@ -325,7 +323,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้",
|
"go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้",
|
||||||
"go.faq.q3": "Go เหมือนกับ Zen หรือไม่?",
|
"go.faq.q3": "Go เหมือนกับ Zen หรือไม่?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"ไม่ 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, DeepSeek V4 Pro และ DeepSeek V4 Flash อย่างเชื่อถือได้",
|
"ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash อย่างเชื่อถือได้",
|
||||||
"go.faq.q4": "Go ราคาเท่าไหร่?",
|
"go.faq.q4": "Go ราคาเท่าไหร่?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go ราคา",
|
"go.faq.a4.p1.beforePricing": "Go ราคา",
|
||||||
"go.faq.a4.p1.pricingLink": "$5 เดือนแรก",
|
"go.faq.a4.p1.pricingLink": "$5 เดือนแรก",
|
||||||
@@ -348,7 +346,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?",
|
"go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"โมเดลฟรีรวมถึง 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, DeepSeek V4 Pro และ DeepSeek V4 Flash ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
"โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง",
|
"zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง",
|
||||||
"zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}",
|
"zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}",
|
||||||
@@ -358,8 +356,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "ไม่มี API key",
|
"zen.api.error.missingApiKey": "ไม่มี API key",
|
||||||
"zen.api.error.invalidApiKey": "API key ไม่ถูกต้อง",
|
"zen.api.error.invalidApiKey": "API key ไม่ถูกต้อง",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "โควต้าการสมัครสมาชิกเกินขีดจำกัด ลองใหม่ในอีก {{retryIn}}",
|
"zen.api.error.subscriptionQuotaExceeded": "โควต้าการสมัครสมาชิกเกินขีดจำกัด ลองใหม่ในอีก {{retryIn}}",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"โควต้าการสมัครสมาชิกเกินขีดจำกัด คุณสามารถดำเนินการต่อโดยใช้โมเดลฟรี",
|
"ถึงขีดจำกัดการใช้งานในรอบ 5 ชั่วโมงแล้ว จะรีเซ็ตในอีก {{retryIn}} หากต้องการใช้โมเดลนี้ต่อทันที ให้เปิดใช้งานจากยอดเงินคงเหลือของคุณ: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"ถึงขีดจำกัดการใช้งานรายสัปดาห์แล้ว จะรีเซ็ตในอีก {{retryIn}} หากต้องการใช้โมเดลนี้ต่อทันที ให้เปิดใช้งานจากยอดเงินคงเหลือของคุณ: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"ถึงขีดจำกัดการใช้งานรายเดือนแล้ว จะรีเซ็ตในอีก {{retryIn}} หากต้องการใช้โมเดลนี้ต่อทันที ให้เปิดใช้งานจากยอดเงินคงเหลือของคุณ: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "ไม่มีวิธีการชำระเงิน เพิ่มวิธีการชำระเงินที่นี่: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "ไม่มีวิธีการชำระเงิน เพิ่มวิธีการชำระเงินที่นี่: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "ยอดเงินคงเหลือไม่เพียงพอ จัดการการเรียกเก็บเงินของคุณที่นี่: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "ยอดเงินคงเหลือไม่เพียงพอ จัดการการเรียกเก็บเงินของคุณที่นี่: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; 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, DeepSeek V4 Pro ve DeepSeek V4 Flash için cömert 5 saatlik istek limitleri sunar.",
|
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash için cömert 5 saatlik istek limitleri sunar.",
|
||||||
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.",
|
"Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.",
|
||||||
@@ -265,8 +265,6 @@ export const dict = {
|
|||||||
"go.cta.promo": "İlk ay $5",
|
"go.cta.promo": "İlk ay $5",
|
||||||
"go.pricing.body":
|
"go.pricing.body":
|
||||||
"Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
"Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6: kullanım limiti 27 Nisan'a kadar 3 katına çıktı",
|
|
||||||
"go.graph.free": "Ücretsiz",
|
"go.graph.free": "Ücretsiz",
|
||||||
"go.graph.freePill": "Big Pickle ve ücretsiz modeller",
|
"go.graph.freePill": "Big Pickle ve ücretsiz modeller",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -305,7 +303,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "Cömert limitler ve güvenilir erişim",
|
"go.problem.item2": "Cömert limitler ve güvenilir erişim",
|
||||||
"go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi",
|
"go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"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, DeepSeek V4 Pro ve DeepSeek V4 Flash içerir",
|
"GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash içerir",
|
||||||
"go.how.title": "Go nasıl çalışır?",
|
"go.how.title": "Go nasıl çalışır?",
|
||||||
"go.how.body":
|
"go.how.body":
|
||||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.",
|
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.",
|
||||||
@@ -331,7 +329,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.",
|
"go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.",
|
||||||
"go.faq.q3": "Go, Zen ile aynı mı?",
|
"go.faq.q3": "Go, Zen ile aynı mı?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; 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, DeepSeek V4 Pro ve DeepSeek V4 Flash açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.",
|
"Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.",
|
||||||
"go.faq.q4": "Go ne kadar?",
|
"go.faq.q4": "Go ne kadar?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go'nun maliyeti",
|
"go.faq.a4.p1.beforePricing": "Go'nun maliyeti",
|
||||||
"go.faq.a4.p1.pricingLink": "İlk ay $5",
|
"go.faq.a4.p1.pricingLink": "İlk ay $5",
|
||||||
@@ -355,7 +353,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?",
|
"go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise 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, DeepSeek V4 Pro ve DeepSeek V4 Flash modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
"Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.",
|
"zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.",
|
||||||
"zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor",
|
"zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor",
|
||||||
@@ -365,8 +363,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "API anahtarı eksik.",
|
"zen.api.error.missingApiKey": "API anahtarı eksik.",
|
||||||
"zen.api.error.invalidApiKey": "Geçersiz API anahtarı.",
|
"zen.api.error.invalidApiKey": "Geçersiz API anahtarı.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonelik kotası aşıldı. {{retryIn}} içinde tekrar deneyin.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonelik kotası aşıldı. {{retryIn}} içinde tekrar deneyin.",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Abonelik kotası aşıldı. Ücretsiz modelleri kullanmaya devam edebilirsiniz.",
|
"5 saatlik kullanım limitine ulaşıldı. {{retryIn}} içinde sıfırlanır. Bu modeli şimdi kullanmaya devam etmek için kullanılabilir bakiyenizden kullanımı etkinleştirin: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"Haftalık kullanım limitine ulaşıldı. {{retryIn}} içinde sıfırlanır. Bu modeli şimdi kullanmaya devam etmek için kullanılabilir bakiyenizden kullanımı etkinleştirin: {{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"Aylık kullanım limitine ulaşıldı. {{retryIn}} içinde sıfırlanır. Bu modeli şimdi kullanmaya devam etmek için kullanılabilir bakiyenizden kullanımı etkinleştirin: {{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "Ödeme yöntemi bulunamadı. Buradan bir ödeme yöntemi ekleyin: {{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "Ödeme yöntemi bulunamadı. Buradan bir ödeme yöntemi ekleyin: {{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "Yetersiz bakiye. Faturalandırmanızı buradan yönetin: {{billingUrl}}",
|
"zen.api.error.insufficientBalance": "Yetersiz bakiye. Faturalandırmanızı buradan yönetin: {{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小时充裕请求额度。",
|
"Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小时充裕请求额度。",
|
||||||
"go.hero.title": "人人可用的低成本编程模型",
|
"go.hero.title": "人人可用的低成本编程模型",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。",
|
"Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。",
|
||||||
@@ -252,8 +252,6 @@ export const dict = {
|
|||||||
"go.cta.price": "$10/月",
|
"go.cta.price": "$10/月",
|
||||||
"go.cta.promo": "首月 $5",
|
"go.cta.promo": "首月 $5",
|
||||||
"go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。",
|
"go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6 使用额度提升至 3 倍,限时至 4 月 27 日",
|
|
||||||
"go.graph.free": "免费",
|
"go.graph.free": "免费",
|
||||||
"go.graph.freePill": "Big Pickle 和免费模型",
|
"go.graph.freePill": "Big Pickle 和免费模型",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -291,7 +289,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "充裕的限额和可靠的访问",
|
"go.problem.item2": "充裕的限额和可靠的访问",
|
||||||
"go.problem.item3": "为尽可能多的程序员打造",
|
"go.problem.item3": "为尽可能多的程序员打造",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"包含 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、DeepSeek V4 Pro 和 DeepSeek V4 Flash",
|
"包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash",
|
||||||
"go.how.title": "Go 如何工作",
|
"go.how.title": "Go 如何工作",
|
||||||
"go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。",
|
"go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。",
|
||||||
"go.how.step1.title": "创建账户",
|
"go.how.step1.title": "创建账户",
|
||||||
@@ -313,7 +311,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。",
|
"go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。",
|
||||||
"go.faq.q3": "Go 和 Zen 一样吗?",
|
"go.faq.q3": "Go 和 Zen 一样吗?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"不。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、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等开源模型。",
|
"不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等开源模型。",
|
||||||
"go.faq.q4": "Go 多少钱?",
|
"go.faq.q4": "Go 多少钱?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go 费用为",
|
"go.faq.a4.p1.beforePricing": "Go 费用为",
|
||||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||||
@@ -335,7 +333,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "免费模型和 Go 之间的区别是什么?",
|
"go.faq.q9": "免费模型和 Go 之间的区别是什么?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"免费模型包含 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、DeepSeek V4 Pro 和 DeepSeek V4 Flash,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。",
|
"免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。",
|
"zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。",
|
||||||
"zen.api.error.modelNotSupported": "不支持模型 {{model}}",
|
"zen.api.error.modelNotSupported": "不支持模型 {{model}}",
|
||||||
@@ -345,7 +343,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "缺少 API 密钥。",
|
"zen.api.error.missingApiKey": "缺少 API 密钥。",
|
||||||
"zen.api.error.invalidApiKey": "无效的 API 密钥。",
|
"zen.api.error.invalidApiKey": "无效的 API 密钥。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "超出订阅配额。请在 {{retryIn}} 后重试。",
|
"zen.api.error.subscriptionQuotaExceeded": "超出订阅配额。请在 {{retryIn}} 后重试。",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels": "超出订阅配额。您可以继续使用免费模型。",
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
|
"已达到 5 小时使用限额。将在 {{retryIn}} 后重置。如需立即继续使用该模型,请启用从可用余额扣费:{{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"已达到每周使用限额。将在 {{retryIn}} 后重置。如需立即继续使用该模型,请启用从可用余额扣费:{{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"已达到每月使用限额。将在 {{retryIn}} 后重置。如需立即继续使用该模型,请启用从可用余额扣费:{{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "没有付款方式。请在此处添加付款方式:{{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "没有付款方式。请在此处添加付款方式:{{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "余额不足。请在此处管理您的计费:{{billingUrl}}",
|
"zen.api.error.insufficientBalance": "余额不足。请在此处管理您的计费:{{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
||||||
"go.meta.description":
|
"go.meta.description":
|
||||||
"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、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小時充裕請求額度。",
|
"Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小時充裕請求額度。",
|
||||||
"go.hero.title": "低成本全民編碼模型",
|
"go.hero.title": "低成本全民編碼模型",
|
||||||
"go.hero.body":
|
"go.hero.body":
|
||||||
"Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。",
|
"Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。",
|
||||||
@@ -252,8 +252,6 @@ export const dict = {
|
|||||||
"go.cta.price": "$10/月",
|
"go.cta.price": "$10/月",
|
||||||
"go.cta.promo": "首月 $5",
|
"go.cta.promo": "首月 $5",
|
||||||
"go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。",
|
"go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。",
|
||||||
"go.banner.badge": "3x",
|
|
||||||
"go.banner.text": "Kimi K2.6 使用額度提升至 3 倍,限時至 4 月 27 日",
|
|
||||||
"go.graph.free": "免費",
|
"go.graph.free": "免費",
|
||||||
"go.graph.freePill": "Big Pickle 與免費模型",
|
"go.graph.freePill": "Big Pickle 與免費模型",
|
||||||
"go.graph.go": "Go",
|
"go.graph.go": "Go",
|
||||||
@@ -291,7 +289,7 @@ export const dict = {
|
|||||||
"go.problem.item2": "寬裕的限額與穩定存取",
|
"go.problem.item2": "寬裕的限額與穩定存取",
|
||||||
"go.problem.item3": "專為盡可能多的程式設計師打造",
|
"go.problem.item3": "專為盡可能多的程式設計師打造",
|
||||||
"go.problem.item4":
|
"go.problem.item4":
|
||||||
"包含 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、DeepSeek V4 Pro 與 DeepSeek V4 Flash",
|
"包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 與 DeepSeek V4 Flash",
|
||||||
"go.how.title": "Go 如何運作",
|
"go.how.title": "Go 如何運作",
|
||||||
"go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。",
|
"go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。",
|
||||||
"go.how.step1.title": "建立帳號",
|
"go.how.step1.title": "建立帳號",
|
||||||
@@ -313,7 +311,7 @@ export const dict = {
|
|||||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。",
|
"go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。",
|
||||||
"go.faq.q3": "Go 與 Zen 一樣嗎?",
|
"go.faq.q3": "Go 與 Zen 一樣嗎?",
|
||||||
"go.faq.a3":
|
"go.faq.a3":
|
||||||
"不。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、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等開源模型。",
|
"不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等開源模型。",
|
||||||
"go.faq.q4": "Go 費用是多少?",
|
"go.faq.q4": "Go 費用是多少?",
|
||||||
"go.faq.a4.p1.beforePricing": "Go 費用為",
|
"go.faq.a4.p1.beforePricing": "Go 費用為",
|
||||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||||
@@ -335,7 +333,7 @@ export const dict = {
|
|||||||
|
|
||||||
"go.faq.q9": "免費模型與 Go 有什麼區別?",
|
"go.faq.q9": "免費模型與 Go 有什麼區別?",
|
||||||
"go.faq.a9":
|
"go.faq.a9":
|
||||||
"免費模型包括 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、DeepSeek V4 Pro 與 DeepSeek V4 Flash,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。",
|
"免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 與 DeepSeek V4 Flash,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。",
|
||||||
|
|
||||||
"zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。",
|
"zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。",
|
||||||
"zen.api.error.modelNotSupported": "不支援模型 {{model}}",
|
"zen.api.error.modelNotSupported": "不支援模型 {{model}}",
|
||||||
@@ -345,7 +343,12 @@ export const dict = {
|
|||||||
"zen.api.error.missingApiKey": "缺少 API 金鑰。",
|
"zen.api.error.missingApiKey": "缺少 API 金鑰。",
|
||||||
"zen.api.error.invalidApiKey": "無效的 API 金鑰。",
|
"zen.api.error.invalidApiKey": "無效的 API 金鑰。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "超出訂閱配額。請在 {{retryIn}} 後重試。",
|
"zen.api.error.subscriptionQuotaExceeded": "超出訂閱配額。請在 {{retryIn}} 後重試。",
|
||||||
"zen.api.error.subscriptionQuotaExceededUseFreeModels": "超出訂閱配額。你可以繼續使用免費模型。",
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
|
"已達 5 小時使用上限,將在 {{retryIn}} 後重置。若要立即繼續使用此模型,請從可用餘額啟用使用量:{{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionWeeklyLimitExceeded":
|
||||||
|
"已達每週使用上限,將在 {{retryIn}} 後重置。若要立即繼續使用此模型,請從可用餘額啟用使用量:{{consoleGoUrl}}",
|
||||||
|
"zen.api.error.goSubscriptionMonthlyLimitExceeded":
|
||||||
|
"已達每月使用上限,將在 {{retryIn}} 後重置。若要立即繼續使用此模型,請從可用餘額啟用使用量:{{consoleGoUrl}}",
|
||||||
"zen.api.error.noPaymentMethod": "無付款方式。請在此處新增付款方式:{{billingUrl}}",
|
"zen.api.error.noPaymentMethod": "無付款方式。請在此處新增付款方式:{{billingUrl}}",
|
||||||
"zen.api.error.insufficientBalance": "餘額不足。請在此處管理你的帳務:{{billingUrl}}",
|
"zen.api.error.insufficientBalance": "餘額不足。請在此處管理你的帳務:{{billingUrl}}",
|
||||||
"zen.api.error.workspaceMonthlyLimitReached":
|
"zen.api.error.workspaceMonthlyLimitReached":
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
|
||||||
import { json } from "@solidjs/router"
|
|
||||||
import { Database } from "@opencode-ai/console-core/drizzle/index.js"
|
|
||||||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
|
||||||
|
|
||||||
export async function GET(_evt: APIEvent) {
|
|
||||||
return json({
|
|
||||||
data: await Database.use(async (tx) => {
|
|
||||||
const result = await tx.$count(UserTable)
|
|
||||||
return result
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -326,37 +326,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="desktop-app-banner"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
|
|
||||||
[data-slot="badge"] {
|
|
||||||
background: var(--color-background-strong);
|
|
||||||
color: var(--color-text-inverted);
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 4px 8px;
|
|
||||||
line-height: 1;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="content"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="text"] {
|
|
||||||
color: var(--color-text-strong);
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
@media (max-width: 30.625rem) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="hero-copy"] {
|
[data-slot="hero-copy"] {
|
||||||
img {
|
img {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@@ -662,10 +631,6 @@ body {
|
|||||||
fill: var(--color-text-strong);
|
fill: var(--color-text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-bar][data-kind="promo"] {
|
|
||||||
fill: color-mix(in srgb, var(--bar-go) 50%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-val] {
|
[data-val] {
|
||||||
fill: var(--color-text-strong);
|
fill: var(--color-text-strong);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ const models = [
|
|||||||
{ name: "GLM-5", provider: "DeepInfra, Fireworks AI, Z.ai" },
|
{ name: "GLM-5", provider: "DeepInfra, Fireworks AI, Z.ai" },
|
||||||
{ name: "Kimi K2.5", provider: "Moonshot AI" },
|
{ name: "Kimi K2.5", provider: "Moonshot AI" },
|
||||||
{ name: "Kimi K2.6", provider: "Moonshot AI" },
|
{ name: "Kimi K2.6", provider: "Moonshot AI" },
|
||||||
{ name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" },
|
|
||||||
{ name: "MiMo-V2-Omni", provider: "Xiaomi MiMo" },
|
|
||||||
{ name: "MiMo-V2.5-Pro", provider: "Xiaomi MiMo" },
|
{ name: "MiMo-V2.5-Pro", provider: "Xiaomi MiMo" },
|
||||||
{ name: "MiMo-V2.5", provider: "Xiaomi MiMo" },
|
{ name: "MiMo-V2.5", provider: "Xiaomi MiMo" },
|
||||||
{ name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" },
|
{ name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" },
|
||||||
@@ -63,7 +61,7 @@ function LimitsGraph(props: { href: string }) {
|
|||||||
const free = 200
|
const free = 200
|
||||||
const graph = [
|
const graph = [
|
||||||
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
|
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
|
||||||
{ id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" },
|
{ id: "kimi-k2.6", name: "Kimi K2.6", req: 1150, d: "150ms" },
|
||||||
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 1290, d: "150ms" },
|
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 1290, d: "150ms" },
|
||||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" },
|
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" },
|
||||||
{ id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" },
|
{ id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" },
|
||||||
@@ -157,24 +155,12 @@ function LimitsGraph(props: { href: string }) {
|
|||||||
<rect
|
<rect
|
||||||
x={left}
|
x={left}
|
||||||
y={gy(i()) - bh / 2}
|
y={gy(i()) - bh / 2}
|
||||||
width={Math.max(0, x(ratio(m.baseReq ?? m.req)) - left)}
|
width={Math.max(0, x(ratio(m.req)) - left)}
|
||||||
height={bh}
|
height={bh}
|
||||||
data-bar
|
data-bar
|
||||||
data-kind="go"
|
data-kind="go"
|
||||||
data-model={m.id}
|
data-model={m.id}
|
||||||
data-segment={m.baseReq ? "base" : undefined}
|
|
||||||
/>
|
/>
|
||||||
{m.baseReq && (
|
|
||||||
<rect
|
|
||||||
x={x(ratio(m.baseReq)) + 2}
|
|
||||||
y={gy(i()) - bh / 2}
|
|
||||||
width={Math.max(0, x(ratio(m.req)) - x(ratio(m.baseReq)) - 2)}
|
|
||||||
height={bh}
|
|
||||||
data-bar
|
|
||||||
data-kind="promo"
|
|
||||||
data-model={m.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</g>
|
</g>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -264,12 +250,6 @@ export default function Home() {
|
|||||||
|
|
||||||
<div data-component="content">
|
<div data-component="content">
|
||||||
<section data-component="hero">
|
<section data-component="hero">
|
||||||
<div data-component="desktop-app-banner">
|
|
||||||
<span data-slot="badge">{i18n.t("home.banner.badge")}</span>
|
|
||||||
<div data-slot="content">
|
|
||||||
<span data-slot="text">{i18n.t("go.banner.text")}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-slot="hero-copy">
|
<div data-slot="hero-copy">
|
||||||
<img data-slot="zen logo light" src={goLogoLight} alt="" />
|
<img data-slot="zen logo light" src={goLogoLight} alt="" />
|
||||||
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
|
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Resource } from "@opencode-ai/console-resource"
|
||||||
|
import { safeEqual } from "@opencode-ai/console-core/util/crypto.js"
|
||||||
|
|
||||||
|
const DISCORD_ALERT_ROLE_ID = "1501447160175136838"
|
||||||
|
|
||||||
|
const basePayload = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
status: z.string().optional(),
|
||||||
|
isTest: z.boolean().optional(),
|
||||||
|
url: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const groups = z.object({ group: z.object({ key: z.string(), value: z.string() }).array() }).array()
|
||||||
|
|
||||||
|
const honeycombWebhookPayload = z.discriminatedUnion("type", [
|
||||||
|
basePayload.extend({
|
||||||
|
type: z.literal("model_http_errors"),
|
||||||
|
groups,
|
||||||
|
}),
|
||||||
|
basePayload.extend({
|
||||||
|
type: z.literal("provider_http_errors"),
|
||||||
|
groups,
|
||||||
|
}),
|
||||||
|
basePayload.extend({
|
||||||
|
type: z.literal("custom"),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
const postDiscordMessage = async (payload: z.infer<typeof honeycombWebhookPayload>) => {
|
||||||
|
const group =
|
||||||
|
payload.type === "model_http_errors" ? "model" : payload.type === "provider_http_errors" ? "provider" : undefined
|
||||||
|
const names = payload.type === "custom" ? [] : payload.groups.flatMap((item) => item.group.map((g) => g.value))
|
||||||
|
|
||||||
|
const content = [
|
||||||
|
`[**${payload.isTest ? "[TEST] " : ""}${payload.name ?? "Honeycomb alert"}**](${payload.url})`,
|
||||||
|
group && names.length > 0 ? `Affected ${group}s:` : undefined,
|
||||||
|
...names.map((name) => `- ${name}`),
|
||||||
|
"",
|
||||||
|
`<@&${DISCORD_ALERT_ROLE_ID}>`,
|
||||||
|
]
|
||||||
|
.filter((line) => line !== undefined)
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
return fetch(Resource.DISCORD_INCIDENT_WEBHOOK_URL.value, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
content,
|
||||||
|
allowed_mentions: { roles: [DISCORD_ALERT_ROLE_ID] },
|
||||||
|
flags: 4,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(input: APIEvent) {
|
||||||
|
const token = input.request.headers.get("X-Honeycomb-Webhook-Token")
|
||||||
|
if (!safeEqual(token ?? "", Resource.HoneycombWebhookSecret.value)) {
|
||||||
|
console.debug("Invalid Honeycomb webhook token")
|
||||||
|
return Response.json({ message: "invalid token" }, { status: 401 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await input.request.json()
|
||||||
|
console.log(body, JSON.stringify(body, null, 2))
|
||||||
|
|
||||||
|
const parsed = honeycombWebhookPayload.safeParse(body)
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
console.error(parsed.error)
|
||||||
|
return Response.json({ message: "invalid payload" }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.data.status !== "TRIGGERED") {
|
||||||
|
console.debug("Skipping resolved alert Honeycomb webhook")
|
||||||
|
return Response.json({ message: "ignored" }, { status: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await postDiscordMessage(parsed.data)
|
||||||
|
if (!response.ok) {
|
||||||
|
return Response.json({ message: "discord webhook failed" }, { status: 502 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json({ message: "sent" }, { status: 200 })
|
||||||
|
}
|
||||||
@@ -289,8 +289,6 @@ export function LiteSection() {
|
|||||||
<li>Kimi K2.6</li>
|
<li>Kimi K2.6</li>
|
||||||
<li>GLM-5</li>
|
<li>GLM-5</li>
|
||||||
<li>GLM-5.1</li>
|
<li>GLM-5.1</li>
|
||||||
<li>MiMo-V2-Pro</li>
|
|
||||||
<li>MiMo-V2-Omni</li>
|
|
||||||
<li>MiMo-V2.5-Pro</li>
|
<li>MiMo-V2.5-Pro</li>
|
||||||
<li>MiMo-V2.5</li>
|
<li>MiMo-V2.5</li>
|
||||||
<li>MiniMax M2.5</li>
|
<li>MiniMax M2.5</li>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
|||||||
all: Object.entries(ZenData.list("full").models)
|
all: Object.entries(ZenData.list("full").models)
|
||||||
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
.filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
|
||||||
.filter(([id, _model]) => !id.startsWith("alpha-"))
|
.filter(([id, _model]) => !id.startsWith("alpha-"))
|
||||||
|
.filter(([id, _model]) => !id.endsWith(":global"))
|
||||||
.sort(([idA, modelA], [idB, modelB]) => {
|
.sort(([idA, modelA], [idB, modelB]) => {
|
||||||
const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"]
|
const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"]
|
||||||
const getPriority = (id: string) => {
|
const getPriority = (id: string) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseOpenAiVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "lite",
|
modelList: "lite",
|
||||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||||
parseModel: (url: string, body: any) => body.model,
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseOpenAiVariant(body),
|
||||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseAnthropicVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "lite",
|
modelList: "lite",
|
||||||
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
||||||
parseModel: (url: string, body: any) => body.model,
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseAnthropicVariant(body),
|
||||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,15 @@ class LimitError extends Error {
|
|||||||
}
|
}
|
||||||
export class RateLimitError extends LimitError {}
|
export class RateLimitError extends LimitError {}
|
||||||
export class FreeUsageLimitError extends LimitError {}
|
export class FreeUsageLimitError extends LimitError {}
|
||||||
export class SubscriptionUsageLimitError extends LimitError {}
|
export class BlackUsageLimitError extends LimitError {}
|
||||||
|
|
||||||
|
type LimitName = "5 hour" | "weekly" | "monthly"
|
||||||
|
export class GoUsageLimitError extends LimitError {
|
||||||
|
workspace: string
|
||||||
|
limitName: LimitName
|
||||||
|
constructor(message: string, workspace: string, limitName: LimitName, retryAfter?: number) {
|
||||||
|
super(message, retryAfter)
|
||||||
|
this.workspace = workspace
|
||||||
|
this.limitName = limitName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import {
|
|||||||
ModelError,
|
ModelError,
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
FreeUsageLimitError,
|
FreeUsageLimitError,
|
||||||
SubscriptionUsageLimitError,
|
GoUsageLimitError,
|
||||||
|
BlackUsageLimitError,
|
||||||
} from "./error"
|
} from "./error"
|
||||||
import {
|
import {
|
||||||
buildCostChunk,
|
buildCostChunk,
|
||||||
@@ -70,6 +71,7 @@ export async function handler(
|
|||||||
modelList: "lite" | "full"
|
modelList: "lite" | "full"
|
||||||
parseApiKey: (headers: Headers) => string | undefined
|
parseApiKey: (headers: Headers) => string | undefined
|
||||||
parseModel: (url: string, body: any) => string
|
parseModel: (url: string, body: any) => string
|
||||||
|
parseVariant: (url: string, body: any) => string | undefined
|
||||||
parseIsStream: (url: string, body: any) => boolean
|
parseIsStream: (url: string, body: any) => boolean
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -92,6 +94,7 @@ export async function handler(
|
|||||||
const url = input.request.url
|
const url = input.request.url
|
||||||
const body = await input.request.json()
|
const body = await input.request.json()
|
||||||
const model = opts.parseModel(url, body)
|
const model = opts.parseModel(url, body)
|
||||||
|
const variant = opts.parseVariant(url, body)
|
||||||
const isStream = opts.parseIsStream(url, body)
|
const isStream = opts.parseIsStream(url, body)
|
||||||
const rawIp = input.request.headers.get("x-real-ip") ?? ""
|
const rawIp = input.request.headers.get("x-real-ip") ?? ""
|
||||||
const ip = rawIp.includes(":") ? rawIp.split(":").slice(0, 4).join(":") : rawIp
|
const ip = rawIp.includes(":") ? rawIp.split(":").slice(0, 4).join(":") : rawIp
|
||||||
@@ -108,6 +111,7 @@ export async function handler(
|
|||||||
request: requestId,
|
request: requestId,
|
||||||
client: ocClient,
|
client: ocClient,
|
||||||
user_agent: userAgent,
|
user_agent: userAgent,
|
||||||
|
"model.variant": variant,
|
||||||
})
|
})
|
||||||
const zenData = ZenData.list(opts.modelList)
|
const zenData = ZenData.list(opts.modelList)
|
||||||
const modelInfo = validateModel(zenData, model)
|
const modelInfo = validateModel(zenData, model)
|
||||||
@@ -116,7 +120,7 @@ export async function handler(
|
|||||||
const trialProviders = await trialLimiter?.check()
|
const trialProviders = await trialLimiter?.check()
|
||||||
const rateLimiter = modelInfo.allowAnonymous
|
const rateLimiter = modelInfo.allowAnonymous
|
||||||
? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)
|
? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)
|
||||||
: createKeyRateLimiter(modelInfo.id, zenApiKey, input.request)
|
: createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)
|
||||||
await rateLimiter?.check()
|
await rateLimiter?.check()
|
||||||
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
|
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
|
||||||
const stickyProvider = await stickyTracker?.get()
|
const stickyProvider = await stickyTracker?.get()
|
||||||
@@ -161,6 +165,7 @@ export async function handler(
|
|||||||
if (typeof v === "string") {
|
if (typeof v === "string") {
|
||||||
if (v === "$ip") return [[k, ip]]
|
if (v === "$ip") return [[k, ip]]
|
||||||
if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : []
|
if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : []
|
||||||
|
if (v === "$session") return sessionId ? [[k, sessionId]] : []
|
||||||
if (v.startsWith("$header.")) {
|
if (v.startsWith("$header.")) {
|
||||||
const headerValue = input.request.headers.get(v.slice(8))
|
const headerValue = input.request.headers.get(v.slice(8))
|
||||||
return headerValue ? [[k, headerValue]] : []
|
return headerValue ? [[k, headerValue]] : []
|
||||||
@@ -395,7 +400,8 @@ export async function handler(
|
|||||||
if (
|
if (
|
||||||
error instanceof RateLimitError ||
|
error instanceof RateLimitError ||
|
||||||
error instanceof FreeUsageLimitError ||
|
error instanceof FreeUsageLimitError ||
|
||||||
error instanceof SubscriptionUsageLimitError
|
error instanceof GoUsageLimitError ||
|
||||||
|
error instanceof BlackUsageLimitError
|
||||||
) {
|
) {
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
if (error.retryAfter) {
|
if (error.retryAfter) {
|
||||||
@@ -404,7 +410,17 @@ export async function handler(
|
|||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: { type: error.constructor.name, message: error.message },
|
error: {
|
||||||
|
type: error.constructor.name,
|
||||||
|
message: error.message,
|
||||||
|
},
|
||||||
|
metadata:
|
||||||
|
error instanceof GoUsageLimitError
|
||||||
|
? {
|
||||||
|
workspace: error.workspace,
|
||||||
|
limitName: error.limitName,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
}),
|
}),
|
||||||
{ status: 429, headers },
|
{ status: 429, headers },
|
||||||
)
|
)
|
||||||
@@ -693,7 +709,7 @@ export async function handler(
|
|||||||
timeUpdated: sub.timeFixedUpdated,
|
timeUpdated: sub.timeFixedUpdated,
|
||||||
})
|
})
|
||||||
if (result.status === "rate-limited")
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionUsageLimitError(
|
throw new BlackUsageLimitError(
|
||||||
t("zen.api.error.subscriptionQuotaExceeded", {
|
t("zen.api.error.subscriptionQuotaExceeded", {
|
||||||
retryIn: formatRetryTime(result.resetInSec),
|
retryIn: formatRetryTime(result.resetInSec),
|
||||||
}),
|
}),
|
||||||
@@ -711,7 +727,7 @@ export async function handler(
|
|||||||
timeUpdated: sub.timeRollingUpdated,
|
timeUpdated: sub.timeRollingUpdated,
|
||||||
})
|
})
|
||||||
if (result.status === "rate-limited")
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionUsageLimitError(
|
throw new BlackUsageLimitError(
|
||||||
t("zen.api.error.subscriptionQuotaExceeded", {
|
t("zen.api.error.subscriptionQuotaExceeded", {
|
||||||
retryIn: formatRetryTime(result.resetInSec),
|
retryIn: formatRetryTime(result.resetInSec),
|
||||||
}),
|
}),
|
||||||
@@ -728,6 +744,7 @@ export async function handler(
|
|||||||
// Validate lite subscription billing
|
// Validate lite subscription billing
|
||||||
if (opts.modelList === "lite" && authInfo.billing.lite && authInfo.lite) {
|
if (opts.modelList === "lite" && authInfo.billing.lite && authInfo.lite) {
|
||||||
try {
|
try {
|
||||||
|
const consoleGoUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/go`
|
||||||
const sub = authInfo.lite
|
const sub = authInfo.lite
|
||||||
const liteData = LiteData.getLimits()
|
const liteData = LiteData.getLimits()
|
||||||
|
|
||||||
@@ -739,8 +756,13 @@ export async function handler(
|
|||||||
timeUpdated: sub.timeWeeklyUpdated,
|
timeUpdated: sub.timeWeeklyUpdated,
|
||||||
})
|
})
|
||||||
if (result.status === "rate-limited")
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionUsageLimitError(
|
throw new GoUsageLimitError(
|
||||||
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
|
t("zen.api.error.goSubscriptionWeeklyLimitExceeded", {
|
||||||
|
retryIn: formatRetryTime(result.resetInSec),
|
||||||
|
consoleGoUrl,
|
||||||
|
}),
|
||||||
|
authInfo.workspaceID,
|
||||||
|
"weekly",
|
||||||
result.resetInSec,
|
result.resetInSec,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -754,8 +776,13 @@ export async function handler(
|
|||||||
timeSubscribed: sub.timeCreated,
|
timeSubscribed: sub.timeCreated,
|
||||||
})
|
})
|
||||||
if (result.status === "rate-limited")
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionUsageLimitError(
|
throw new GoUsageLimitError(
|
||||||
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
|
t("zen.api.error.goSubscriptionMonthlyLimitExceeded", {
|
||||||
|
retryIn: formatRetryTime(result.resetInSec),
|
||||||
|
consoleGoUrl,
|
||||||
|
}),
|
||||||
|
authInfo.workspaceID,
|
||||||
|
"monthly",
|
||||||
result.resetInSec,
|
result.resetInSec,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -769,8 +796,13 @@ export async function handler(
|
|||||||
timeUpdated: sub.timeRollingUpdated,
|
timeUpdated: sub.timeRollingUpdated,
|
||||||
})
|
})
|
||||||
if (result.status === "rate-limited")
|
if (result.status === "rate-limited")
|
||||||
throw new SubscriptionUsageLimitError(
|
throw new GoUsageLimitError(
|
||||||
t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
|
t("zen.api.error.goSubscriptionRollingLimitExceeded", {
|
||||||
|
retryIn: formatRetryTime(result.resetInSec),
|
||||||
|
consoleGoUrl,
|
||||||
|
}),
|
||||||
|
authInfo.workspaceID,
|
||||||
|
"5 hour",
|
||||||
result.resetInSec,
|
result.resetInSec,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import { RateLimitError } from "./error"
|
|||||||
import { i18n } from "~/i18n"
|
import { i18n } from "~/i18n"
|
||||||
import { localeFromRequest } from "~/lib/language"
|
import { localeFromRequest } from "~/lib/language"
|
||||||
|
|
||||||
export function createRateLimiter(modelId: string, zenApiKey: string | undefined, request: Request) {
|
export function createRateLimiter(
|
||||||
|
modelId: string,
|
||||||
|
rateLimit: number | undefined,
|
||||||
|
zenApiKey: string | undefined,
|
||||||
|
request: Request,
|
||||||
|
) {
|
||||||
if (!zenApiKey) return
|
if (!zenApiKey) return
|
||||||
const dict = i18n(localeFromRequest(request))
|
const dict = i18n(localeFromRequest(request))
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = rateLimit ?? 500
|
||||||
const yyyyMMddHHmm = new Date(Date.now())
|
const yyyyMMddHHmm = new Date(Date.now())
|
||||||
.toISOString()
|
.toISOString()
|
||||||
.replace(/[^0-9]/g, "")
|
.replace(/[^0-9]/g, "")
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export function parseAnthropicVariant(body: any) {
|
||||||
|
const effort = body.effort ?? body.output_config?.effort ?? body.outputConfig?.effort ?? body.thinking?.effort
|
||||||
|
if (effort) return effort
|
||||||
|
|
||||||
|
const budget = body.thinking?.budget_tokens ?? body.thinking?.budgetTokens
|
||||||
|
if (body.thinking?.type !== "enabled" || typeof budget !== "number") return undefined
|
||||||
|
return budget > 16_000 ? "max" : "high"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseGoogleVariant(body: any) {
|
||||||
|
const thinkingConfig = body.generationConfig?.thinkingConfig ?? body.thinkingConfig
|
||||||
|
if (thinkingConfig?.thinkingLevel) return thinkingConfig.thinkingLevel
|
||||||
|
|
||||||
|
const budget = thinkingConfig?.thinkingBudget ?? thinkingConfig?.thinking_budget
|
||||||
|
if (typeof budget !== "number" || budget <= 0) return undefined
|
||||||
|
return budget > 16_000 ? "max" : "high"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseOpenAiVariant(body: any) {
|
||||||
|
return body.reasoningEffort ?? body.reasoning_effort ?? body.reasoning?.effort
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseOpenAiVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "full",
|
modelList: "full",
|
||||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||||
parseModel: (url: string, body: any) => body.model,
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseOpenAiVariant(body),
|
||||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseAnthropicVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "full",
|
modelList: "full",
|
||||||
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
|
||||||
parseModel: (url: string, body: any) => body.model,
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseAnthropicVariant(body),
|
||||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ export async function GET(input: APIEvent) {
|
|||||||
)
|
)
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const models = Object.keys(ZenData.list("full").models).filter((id) => !disabledModels.includes(id))
|
const models = Object.keys(ZenData.list("full").models)
|
||||||
|
.filter((id) => !id.endsWith(":global"))
|
||||||
|
.filter((id) => !disabledModels.includes(id))
|
||||||
|
|
||||||
return buildModelsResponse(models)
|
return buildModelsResponse(models)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseGoogleVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "full",
|
modelList: "full",
|
||||||
parseApiKey: (headers: Headers) => headers.get("x-goog-api-key") ?? undefined,
|
parseApiKey: (headers: Headers) => headers.get("x-goog-api-key") ?? undefined,
|
||||||
parseModel: (url: string, _body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
|
parseModel: (url: string, _body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
|
||||||
|
parseVariant: (url: string, body: any) => parseGoogleVariant(body),
|
||||||
parseIsStream: (url: string, _body: any) =>
|
parseIsStream: (url: string, _body: any) =>
|
||||||
// ie. url: https://opencode.ai/zen/v1/models/gemini-3-pro:streamGenerateContent?alt=sse'
|
// ie. url: https://opencode.ai/zen/v1/models/gemini-3-pro:streamGenerateContent?alt=sse'
|
||||||
url.split("/").pop()?.split(":")?.[1]?.startsWith("streamGenerateContent") ?? false,
|
url.split("/").pop()?.split(":")?.[1]?.startsWith("streamGenerateContent") ?? false,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server"
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
import { handler } from "~/routes/zen/util/handler"
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseOpenAiVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
export function POST(input: APIEvent) {
|
export function POST(input: APIEvent) {
|
||||||
return handler(input, {
|
return handler(input, {
|
||||||
@@ -7,6 +8,7 @@ export function POST(input: APIEvent) {
|
|||||||
modelList: "full",
|
modelList: "full",
|
||||||
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||||
parseModel: (url: string, body: any) => body.model,
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseOpenAiVariant(body),
|
||||||
parseIsStream: (url: string, body: any) => !!body.stream,
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { timingSafeEqual } from "node:crypto"
|
||||||
|
|
||||||
|
export function safeEqual(a: string, b: string): boolean {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const aBytes = encoder.encode(a)
|
||||||
|
const bBytes = encoder.encode(b)
|
||||||
|
return aBytes.length === bBytes.length && timingSafeEqual(aBytes, bBytes)
|
||||||
|
}
|
||||||
Vendored
+8
@@ -35,6 +35,10 @@ declare module "sst" {
|
|||||||
"type": "sst.cloudflare.SolidStart"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
|
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"HoneycombWebhookSecret": {
|
||||||
|
"type": "random.index/randomPassword.RandomPassword"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"R2AccessKey": {
|
"R2AccessKey": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default {
|
|||||||
url.pathname !== "/zen/go/v1/responses" &&
|
url.pathname !== "/zen/go/v1/responses" &&
|
||||||
!url.pathname.startsWith("/zen/go/v1/models/")
|
!url.pathname.startsWith("/zen/go/v1/models/")
|
||||||
)
|
)
|
||||||
return
|
continue
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
"cf.continent": event.event.request.cf?.continent,
|
"cf.continent": event.event.request.cf?.continent,
|
||||||
|
|||||||
+8
@@ -35,6 +35,10 @@ declare module "sst" {
|
|||||||
"type": "sst.cloudflare.SolidStart"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
|
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"HoneycombWebhookSecret": {
|
||||||
|
"type": "random.index/randomPassword.RandomPassword"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"R2AccessKey": {
|
"R2AccessKey": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"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",
|
||||||
|
|||||||
+8
@@ -35,6 +35,10 @@ declare module "sst" {
|
|||||||
"type": "sst.cloudflare.SolidStart"
|
"type": "sst.cloudflare.SolidStart"
|
||||||
"url": string
|
"url": string
|
||||||
}
|
}
|
||||||
|
"DISCORD_INCIDENT_WEBHOOK_URL": {
|
||||||
|
"type": "sst.sst.Secret"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"DISCORD_SUPPORT_BOT_TOKEN": {
|
"DISCORD_SUPPORT_BOT_TOKEN": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
@@ -87,6 +91,10 @@ declare module "sst" {
|
|||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
}
|
}
|
||||||
|
"HoneycombWebhookSecret": {
|
||||||
|
"type": "random.index/randomPassword.RandomPassword"
|
||||||
|
"value": string
|
||||||
|
}
|
||||||
"R2AccessKey": {
|
"R2AccessKey": {
|
||||||
"type": "sst.sst.Secret"
|
"type": "sst.sst.Secret"
|
||||||
"value": string
|
"value": string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export const Flag = {
|
|||||||
OPENCODE_EXPERIMENTAL_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
OPENCODE_EXPERIMENTAL_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
||||||
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
||||||
OPENCODE_EXPERIMENTAL_MARKDOWN: !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN"),
|
OPENCODE_EXPERIMENTAL_MARKDOWN: !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN"),
|
||||||
|
OPENCODE_ENABLE_PARALLEL: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
|
||||||
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
||||||
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
||||||
OPENCODE_DISABLE_EMBEDDED_WEB_UI: truthy("OPENCODE_DISABLE_EMBEDDED_WEB_UI"),
|
OPENCODE_DISABLE_EMBEDDED_WEB_UI: truthy("OPENCODE_DISABLE_EMBEDDED_WEB_UI"),
|
||||||
|
|||||||
@@ -1,32 +1,19 @@
|
|||||||
# OpenCode Desktop
|
# OpenCode Desktop
|
||||||
|
|
||||||
Native OpenCode desktop app, built with Tauri v2.
|
The OpenCode Desktop app, built with Electron.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
From the repo root:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
bun run --cwd packages/desktop tauri dev
|
bun dev
|
||||||
```
|
|
||||||
|
|
||||||
This starts the Vite dev server on http://localhost:1420 and opens the native window.
|
|
||||||
|
|
||||||
If you only want the web dev server (no native shell):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun run --cwd packages/desktop dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
To create a production `dist/` and build the native app bundle:
|
Run the `build` script to build the app's JS assets, then `package` to
|
||||||
|
bundle the assets as an application. The resulting app will be in `dist/`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run --cwd packages/desktop tauri build
|
bun run build && bun run package
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ const getBase = (): Configuration => ({
|
|||||||
verifyUpdateCodeSignature: false,
|
verifyUpdateCodeSignature: false,
|
||||||
},
|
},
|
||||||
nsis: {
|
nsis: {
|
||||||
oneClick: false,
|
oneClick: true,
|
||||||
allowToChangeInstallationDirectory: true,
|
perMachine: false,
|
||||||
installerIcon: `resources/icons/icon.ico`,
|
installerIcon: `resources/icons/icon.ico`,
|
||||||
installerHeaderIcon: `resources/icons/icon.ico`,
|
installerHeaderIcon: `resources/icons/icon.ico`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: { index: "src/main/index.ts" },
|
input: { index: "src/main/index.ts", sidecar: "src/main/sidecar.ts" },
|
||||||
},
|
},
|
||||||
externalizeDeps: { include: [nodePtyPkg] },
|
externalizeDeps: { include: [nodePtyPkg] },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.14.38",
|
"version": "1.14.41",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://opencode.ai",
|
"homepage": "https://opencode.ai",
|
||||||
@@ -63,6 +63,14 @@
|
|||||||
"@lydell/node-pty-linux-arm64": "1.2.0-beta.10",
|
"@lydell/node-pty-linux-arm64": "1.2.0-beta.10",
|
||||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
||||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
||||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.10"
|
"@lydell/node-pty-win32-x64": "1.2.0-beta.10",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-x64": "2.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import { execFileSync } from "node:child_process"
|
import { execFile, execFileSync } from "node:child_process"
|
||||||
import { existsSync, readFileSync, readdirSync } from "node:fs"
|
import { access, readFile, readdir } from "node:fs/promises"
|
||||||
import { dirname, extname, join } from "node:path"
|
import { dirname, extname, join } from "node:path"
|
||||||
|
import util from "node:util"
|
||||||
|
|
||||||
export function checkAppExists(appName: string): boolean {
|
const execFilePromise = util.promisify(execFile)
|
||||||
|
|
||||||
|
const exists = (path: string) =>
|
||||||
|
access(path)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false)
|
||||||
|
|
||||||
|
export function checkAppExists(appName: string) {
|
||||||
if (process.platform === "win32") return true
|
if (process.platform === "win32") return true
|
||||||
if (process.platform === "linux") return true
|
if (process.platform === "linux") return true
|
||||||
return checkMacosApp(appName)
|
return checkMacosApp(appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveAppPath(appName: string): string | null {
|
export function resolveAppPath(appName: string) {
|
||||||
if (process.platform !== "win32") return appName
|
if (process.platform !== "win32") return appName
|
||||||
return resolveWindowsAppPath(appName)
|
return resolveWindowsAppPath(appName)
|
||||||
}
|
}
|
||||||
@@ -32,26 +40,25 @@ export function wslPath(path: string, mode: "windows" | "linux" | null): string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMacosApp(appName: string) {
|
async function checkMacosApp(appName: string) {
|
||||||
const locations = [`/Applications/${appName}.app`, `/System/Applications/${appName}.app`]
|
const locations = [`/Applications/${appName}.app`, `/System/Applications/${appName}.app`]
|
||||||
|
|
||||||
const home = process.env.HOME
|
const home = process.env.HOME
|
||||||
if (home) locations.push(`${home}/Applications/${appName}.app`)
|
if (home) locations.push(`${home}/Applications/${appName}.app`)
|
||||||
|
|
||||||
if (locations.some((location) => existsSync(location))) return true
|
for (const location of locations) {
|
||||||
|
if (await exists(location)) return true
|
||||||
try {
|
|
||||||
execFileSync("which", [appName])
|
|
||||||
return true
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWindowsAppPath(appName: string): string | null {
|
return execFilePromise("which", [appName])
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveWindowsAppPath(appName: string): Promise<string | null> {
|
||||||
let output: string
|
let output: string
|
||||||
try {
|
try {
|
||||||
output = execFileSync("where", [appName]).toString()
|
output = execFilePromise("where", [appName]).toString()
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -66,8 +73,8 @@ function resolveWindowsAppPath(appName: string): string | null {
|
|||||||
const exe = paths.find((path) => hasExt(path, "exe"))
|
const exe = paths.find((path) => hasExt(path, "exe"))
|
||||||
if (exe) return exe
|
if (exe) return exe
|
||||||
|
|
||||||
const resolveCmd = (path: string) => {
|
const resolveCmd = async (path: string) => {
|
||||||
const content = readFileSync(path, "utf8")
|
const content = await readFile(path, "utf8")
|
||||||
for (const token of content.split('"').map((value: string) => value.trim())) {
|
for (const token of content.split('"').map((value: string) => value.trim())) {
|
||||||
const lower = token.toLowerCase()
|
const lower = token.toLowerCase()
|
||||||
if (!lower.includes(".exe")) continue
|
if (!lower.includes(".exe")) continue
|
||||||
@@ -85,10 +92,10 @@ function resolveWindowsAppPath(appName: string): string | null {
|
|||||||
return join(current, part)
|
return join(current, part)
|
||||||
}, base)
|
}, base)
|
||||||
|
|
||||||
if (existsSync(resolved)) return resolved
|
if (await exists(resolved)) return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existsSync(token)) return token
|
if (await exists(token)) return token
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -96,20 +103,20 @@ function resolveWindowsAppPath(appName: string): string | null {
|
|||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (hasExt(path, "cmd") || hasExt(path, "bat")) {
|
if (hasExt(path, "cmd") || hasExt(path, "bat")) {
|
||||||
const resolved = resolveCmd(path)
|
const resolved = await resolveCmd(path)
|
||||||
if (resolved) return resolved
|
if (resolved) return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extname(path)) {
|
if (!extname(path)) {
|
||||||
const cmd = `${path}.cmd`
|
const cmd = `${path}.cmd`
|
||||||
if (existsSync(cmd)) {
|
if (await exists(cmd)) {
|
||||||
const resolved = resolveCmd(cmd)
|
const resolved = await resolveCmd(cmd)
|
||||||
if (resolved) return resolved
|
if (resolved) return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
const bat = `${path}.bat`
|
const bat = `${path}.bat`
|
||||||
if (existsSync(bat)) {
|
if (await exists(bat)) {
|
||||||
const resolved = resolveCmd(bat)
|
const resolved = await resolveCmd(bat)
|
||||||
if (resolved) return resolved
|
if (resolved) return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +133,7 @@ function resolveWindowsAppPath(appName: string): string | null {
|
|||||||
const dirs = [dirname(path), dirname(dirname(path)), dirname(dirname(dirname(path)))]
|
const dirs = [dirname(path), dirname(dirname(path)), dirname(dirname(dirname(path)))]
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
try {
|
try {
|
||||||
for (const entry of readdirSync(dir)) {
|
for (const entry of await readdir(dir)) {
|
||||||
const candidate = join(dir, entry)
|
const candidate = join(dir, entry)
|
||||||
if (!hasExt(candidate, "exe")) continue
|
if (!hasExt(candidate, "exe")) continue
|
||||||
const stem = entry.replace(/\.exe$/i, "")
|
const stem = entry.replace(/\.exe$/i, "")
|
||||||
|
|||||||
Vendored
+1
@@ -5,6 +5,7 @@ interface ImportMetaEnv {
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "virtual:opencode-server" {
|
declare module "virtual:opencode-server" {
|
||||||
export namespace Server {
|
export namespace Server {
|
||||||
export const listen: typeof import("../../../opencode/dist/types/src/node").Server.listen
|
export const listen: typeof import("../../../opencode/dist/types/src/node").Server.listen
|
||||||
|
|||||||
+274
-369
@@ -1,23 +1,42 @@
|
|||||||
import { randomUUID } from "node:crypto"
|
import { randomUUID } from "node:crypto"
|
||||||
import { EventEmitter } from "node:events"
|
import { EventEmitter } from "node:events"
|
||||||
import { existsSync } from "node:fs"
|
import { existsSync, mkdirSync, rmSync } from "node:fs"
|
||||||
|
import * as http from "node:http"
|
||||||
import { createServer } from "node:net"
|
import { createServer } from "node:net"
|
||||||
import { homedir } from "node:os"
|
import { homedir, tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { getCACertificates, setDefaultCACertificates } from "node:tls"
|
import { getCACertificates, setDefaultCACertificates } from "node:tls"
|
||||||
import type { Event } from "electron"
|
import type { Event } from "electron"
|
||||||
import { app, BrowserWindow, dialog } from "electron"
|
import { app, BrowserWindow } from "electron"
|
||||||
import pkg from "electron-updater"
|
|
||||||
|
|
||||||
import contextMenu from "electron-context-menu"
|
import contextMenu from "electron-context-menu"
|
||||||
contextMenu({ showSaveImageAs: true, showLookUpSelection: false, showSearchWithGoogle: false })
|
|
||||||
|
|
||||||
// on macOS apps run in `/` which can cause issues with ripgrep
|
import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types"
|
||||||
try {
|
import { checkAppExists, resolveAppPath, wslPath } from "./apps"
|
||||||
process.chdir(homedir())
|
import { CHANNEL, UPDATER_ENABLED } from "./constants"
|
||||||
} catch {}
|
import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigrationProgress } from "./ipc"
|
||||||
|
import { initLogging } from "./logging"
|
||||||
process.env.OPENCODE_DISABLE_EMBEDDED_WEB_UI = "true"
|
import { parseMarkdown } from "./markdown"
|
||||||
|
import { createMenu } from "./menu"
|
||||||
|
import {
|
||||||
|
getDefaultServerUrl,
|
||||||
|
getWslConfig,
|
||||||
|
preferAppEnv,
|
||||||
|
setDefaultServerUrl,
|
||||||
|
setWslConfig,
|
||||||
|
spawnLocalServer,
|
||||||
|
type SidecarListener,
|
||||||
|
} from "./server"
|
||||||
|
import {
|
||||||
|
createLoadingWindow,
|
||||||
|
createMainWindow,
|
||||||
|
registerRendererProtocol,
|
||||||
|
setBackgroundColor,
|
||||||
|
setDockIcon,
|
||||||
|
} from "./windows"
|
||||||
|
import { migrate } from "./migrate"
|
||||||
|
import { checkUpdate, checkForUpdates, installUpdate, setupAutoUpdater } from "./updater"
|
||||||
|
import { Deferred, Effect, Fiber } from "effect"
|
||||||
|
|
||||||
const APP_NAMES: Record<string, string> = {
|
const APP_NAMES: Record<string, string> = {
|
||||||
dev: "OpenCode Dev",
|
dev: "OpenCode Dev",
|
||||||
@@ -29,107 +48,23 @@ const APP_IDS: Record<string, string> = {
|
|||||||
beta: "ai.opencode.desktop.beta",
|
beta: "ai.opencode.desktop.beta",
|
||||||
prod: "ai.opencode.desktop",
|
prod: "ai.opencode.desktop",
|
||||||
}
|
}
|
||||||
const appId = app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev"
|
const TEST_ONBOARDING = process.env.OPENCODE_TEST_ONBOARDING === "1"
|
||||||
app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "OpenCode Dev")
|
|
||||||
app.setAppUserModelId(appId)
|
|
||||||
app.setPath("userData", join(app.getPath("appData"), appId))
|
|
||||||
const { autoUpdater } = pkg
|
|
||||||
|
|
||||||
import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types"
|
let logger: ReturnType<typeof initLogging>
|
||||||
import { checkAppExists, resolveAppPath, wslPath } from "./apps"
|
let mainWindow: BrowserWindow | null = null
|
||||||
import { CHANNEL, UPDATER_ENABLED } from "./constants"
|
let server: SidecarListener | null = null
|
||||||
import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigrationProgress } from "./ipc"
|
|
||||||
import { initLogging } from "./logging"
|
|
||||||
import { parseMarkdown } from "./markdown"
|
|
||||||
import { createMenu } from "./menu"
|
|
||||||
import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server"
|
|
||||||
import {
|
|
||||||
createLoadingWindow,
|
|
||||||
createMainWindow,
|
|
||||||
registerRendererProtocol,
|
|
||||||
setBackgroundColor,
|
|
||||||
setDockIcon,
|
|
||||||
} from "./windows"
|
|
||||||
import { drizzle } from "drizzle-orm/node-sqlite/driver"
|
|
||||||
import type { Server } from "virtual:opencode-server"
|
|
||||||
import { migrate } from "./migrate"
|
|
||||||
|
|
||||||
const initEmitter = new EventEmitter()
|
const initEmitter = new EventEmitter()
|
||||||
let initStep: InitStep = { phase: "server_waiting" }
|
let initStep: InitStep = { phase: "server_waiting" }
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
|
||||||
let server: Server.Listener | null = null
|
|
||||||
const loadingComplete = defer<void>()
|
|
||||||
|
|
||||||
const pendingDeepLinks: string[] = []
|
const pendingDeepLinks: string[] = []
|
||||||
|
|
||||||
const serverReady = defer<ServerReadyData>()
|
function useEnvProxy() {
|
||||||
const logger = initLogging()
|
|
||||||
|
|
||||||
useSystemCertificates()
|
|
||||||
|
|
||||||
logger.log("app starting", {
|
|
||||||
version: app.getVersion(),
|
|
||||||
packaged: app.isPackaged,
|
|
||||||
})
|
|
||||||
|
|
||||||
setupApp()
|
|
||||||
|
|
||||||
function setupApp() {
|
|
||||||
ensureLoopbackNoProxy()
|
|
||||||
app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>")
|
|
||||||
if (!app.isPackaged) app.commandLine.appendSwitch("remote-debugging-port", "9222")
|
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock()) {
|
|
||||||
app.quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on("second-instance", (_event: Event, argv: string[]) => {
|
|
||||||
const urls = argv.filter((arg: string) => arg.startsWith("opencode://"))
|
|
||||||
if (urls.length) {
|
|
||||||
logger.log("deep link received via second-instance", { urls })
|
|
||||||
emitDeepLinks(urls)
|
|
||||||
}
|
|
||||||
focusMainWindow()
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("open-url", (event: Event, url: string) => {
|
|
||||||
event.preventDefault()
|
|
||||||
logger.log("deep link received via open-url", { url })
|
|
||||||
emitDeepLinks([url])
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("before-quit", () => {
|
|
||||||
killSidecar()
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on("will-quit", () => {
|
|
||||||
killSidecar()
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
||||||
process.on(signal, () => {
|
|
||||||
killSidecar()
|
|
||||||
app.exit(0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
void app.whenReady().then(async () => {
|
|
||||||
migrate()
|
|
||||||
app.setAsDefaultProtocolClient("opencode")
|
|
||||||
registerRendererProtocol()
|
|
||||||
setDockIcon()
|
|
||||||
setupAutoUpdater()
|
|
||||||
await initialize()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function useSystemCertificates() {
|
|
||||||
try {
|
try {
|
||||||
setDefaultCACertificates([...new Set([...getCACertificates("default"), ...getCACertificates("system")])])
|
// Electron 41.2 runs Node 24.14.1; latest @types/node@24 is 24.12.2.
|
||||||
|
;(http as any).setGlobalProxyFromEnv()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("failed to load system certificates", error)
|
logger.warn("failed to load proxy environment", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,151 +74,17 @@ function emitDeepLinks(urls: string[]) {
|
|||||||
if (mainWindow) sendDeepLinks(mainWindow, urls)
|
if (mainWindow) sendDeepLinks(mainWindow, urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusMainWindow() {
|
|
||||||
if (!mainWindow) return
|
|
||||||
mainWindow.show()
|
|
||||||
mainWindow.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function setInitStep(step: InitStep) {
|
function setInitStep(step: InitStep) {
|
||||||
initStep = step
|
initStep = step
|
||||||
logger.log("init step", { step })
|
logger.log("init step", { step })
|
||||||
initEmitter.emit("step", step)
|
initEmitter.emit("step", step)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize() {
|
async function killSidecar() {
|
||||||
const needsMigration = !sqliteFileExists()
|
|
||||||
const sqliteDone = needsMigration ? defer<void>() : undefined
|
|
||||||
let overlay: BrowserWindow | null = null
|
|
||||||
|
|
||||||
const port = await getSidecarPort()
|
|
||||||
const hostname = "127.0.0.1"
|
|
||||||
const url = `http://${hostname}:${port}`
|
|
||||||
const password = randomUUID()
|
|
||||||
|
|
||||||
const loadingTask = (async () => {
|
|
||||||
logger.log("sidecar connection started", { url })
|
|
||||||
|
|
||||||
initEmitter.on("sqlite", (progress: SqliteMigrationProgress) => {
|
|
||||||
setInitStep({ phase: "sqlite_waiting" })
|
|
||||||
if (overlay) sendSqliteMigrationProgress(overlay, progress)
|
|
||||||
if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress)
|
|
||||||
if (progress.type === "Done") sqliteDone?.resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (needsMigration) {
|
|
||||||
const { Database, JsonMigration } = await import("virtual:opencode-server")
|
|
||||||
await JsonMigration.run(drizzle({ client: Database.Client().$client }), {
|
|
||||||
progress: (event: { current: number; total: number }) => {
|
|
||||||
const percent = Math.round(event.current / event.total) * 100
|
|
||||||
initEmitter.emit("sqlite", { type: "InProgress", value: percent })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
initEmitter.emit("sqlite", { type: "Done" })
|
|
||||||
|
|
||||||
sqliteDone?.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsMigration) {
|
|
||||||
await sqliteDone?.promise
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log("spawning sidecar", { url })
|
|
||||||
const { listener, health } = await spawnLocalServer(hostname, port, password)
|
|
||||||
server = listener
|
|
||||||
serverReady.resolve({
|
|
||||||
url,
|
|
||||||
username: "opencode",
|
|
||||||
password,
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.race([
|
|
||||||
health.wait,
|
|
||||||
delay(30_000).then(() => {
|
|
||||||
throw new Error("Sidecar health check timed out")
|
|
||||||
}),
|
|
||||||
]).catch((error) => {
|
|
||||||
logger.error("sidecar health check failed", error)
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.log("loading task finished")
|
|
||||||
})()
|
|
||||||
|
|
||||||
if (needsMigration) {
|
|
||||||
const show = await Promise.race([loadingTask.then(() => false), delay(1_000).then(() => true)])
|
|
||||||
if (show) {
|
|
||||||
overlay = createLoadingWindow()
|
|
||||||
await delay(1_000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadingTask
|
|
||||||
setInitStep({ phase: "done" })
|
|
||||||
|
|
||||||
if (overlay) {
|
|
||||||
await loadingComplete.promise
|
|
||||||
}
|
|
||||||
|
|
||||||
mainWindow = createMainWindow()
|
|
||||||
wireMenu()
|
|
||||||
|
|
||||||
overlay?.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
function wireMenu() {
|
|
||||||
if (!mainWindow) return
|
|
||||||
createMenu({
|
|
||||||
trigger: (id) => mainWindow && sendMenuCommand(mainWindow, id),
|
|
||||||
checkForUpdates: () => {
|
|
||||||
void checkForUpdates(true)
|
|
||||||
},
|
|
||||||
reload: () => mainWindow?.reload(),
|
|
||||||
relaunch: () => {
|
|
||||||
killSidecar()
|
|
||||||
app.relaunch()
|
|
||||||
app.exit(0)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
registerIpcHandlers({
|
|
||||||
killSidecar: () => killSidecar(),
|
|
||||||
awaitInitialization: async (sendStep) => {
|
|
||||||
sendStep(initStep)
|
|
||||||
const listener = (step: InitStep) => sendStep(step)
|
|
||||||
initEmitter.on("step", listener)
|
|
||||||
try {
|
|
||||||
logger.log("awaiting server ready")
|
|
||||||
const res = await serverReady.promise
|
|
||||||
logger.log("server ready", { url: res.url })
|
|
||||||
return res
|
|
||||||
} finally {
|
|
||||||
initEmitter.off("step", listener)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getWindowConfig: () => ({ updaterEnabled: UPDATER_ENABLED }),
|
|
||||||
consumeInitialDeepLinks: () => pendingDeepLinks.splice(0),
|
|
||||||
getDefaultServerUrl: () => getDefaultServerUrl(),
|
|
||||||
setDefaultServerUrl: (url) => setDefaultServerUrl(url),
|
|
||||||
getWslConfig: () => Promise.resolve(getWslConfig()),
|
|
||||||
setWslConfig: (config: WslConfig) => setWslConfig(config),
|
|
||||||
getDisplayBackend: async () => null,
|
|
||||||
setDisplayBackend: async () => undefined,
|
|
||||||
parseMarkdown: async (markdown) => parseMarkdown(markdown),
|
|
||||||
checkAppExists: async (appName) => checkAppExists(appName),
|
|
||||||
wslPath: async (path, mode) => wslPath(path, mode),
|
|
||||||
resolveAppPath: async (appName) => resolveAppPath(appName),
|
|
||||||
loadingWindowComplete: () => loadingComplete.resolve(),
|
|
||||||
runUpdater: async (alertOnFail) => checkForUpdates(alertOnFail),
|
|
||||||
checkUpdate: async () => checkUpdate(),
|
|
||||||
installUpdate: async () => installUpdate(),
|
|
||||||
setBackgroundColor: (color) => setBackgroundColor(color),
|
|
||||||
})
|
|
||||||
|
|
||||||
function killSidecar() {
|
|
||||||
if (!server) return
|
if (!server) return
|
||||||
server.stop()
|
const current = server
|
||||||
server = null
|
server = null
|
||||||
|
await current.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureLoopbackNoProxy() {
|
function ensureLoopbackNoProxy() {
|
||||||
@@ -306,161 +107,265 @@ function ensureLoopbackNoProxy() {
|
|||||||
upsert("no_proxy")
|
upsert("no_proxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSidecarPort() {
|
const main = Effect.gen(function* () {
|
||||||
|
contextMenu({ showSaveImageAs: true, showLookUpSelection: false, showSearchWithGoogle: false })
|
||||||
|
|
||||||
|
// on macOS apps run in `/` which can cause issues with ripgrep
|
||||||
|
try {
|
||||||
|
process.chdir(homedir())
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
process.env.OPENCODE_DISABLE_EMBEDDED_WEB_UI = "true"
|
||||||
|
|
||||||
|
const appId = app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev"
|
||||||
|
const onboardingTestRoot = ((): string | undefined => {
|
||||||
|
if (!TEST_ONBOARDING) return
|
||||||
|
|
||||||
|
const root = join(tmpdir(), `opencode-onboarding-${randomUUID()}`)
|
||||||
|
rmSync(root, { recursive: true, force: true })
|
||||||
|
;["data", "config", "cache", "state", "desktop", "session"].forEach((dir) =>
|
||||||
|
mkdirSync(join(root, dir), { recursive: true }),
|
||||||
|
)
|
||||||
|
process.env.OPENCODE_DB = ":memory:"
|
||||||
|
process.env.XDG_DATA_HOME = join(root, "data")
|
||||||
|
process.env.XDG_CONFIG_HOME = join(root, "config")
|
||||||
|
process.env.XDG_CACHE_HOME = join(root, "cache")
|
||||||
|
process.env.XDG_STATE_HOME = join(root, "state")
|
||||||
|
return root
|
||||||
|
})()
|
||||||
|
app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "OpenCode Dev")
|
||||||
|
app.setAppUserModelId(appId)
|
||||||
|
app.setPath(
|
||||||
|
"userData",
|
||||||
|
onboardingTestRoot ? join(onboardingTestRoot, "desktop") : join(app.getPath("appData"), appId),
|
||||||
|
)
|
||||||
|
if (onboardingTestRoot) app.setPath("sessionData", join(onboardingTestRoot, "session"))
|
||||||
|
logger = initLogging()
|
||||||
|
|
||||||
|
try {
|
||||||
|
setDefaultCACertificates([...new Set([...getCACertificates("default"), ...getCACertificates("system")])])
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn("failed to load system certificates", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("app starting", {
|
||||||
|
version: app.getVersion(),
|
||||||
|
packaged: app.isPackaged,
|
||||||
|
onboardingTest: Boolean(onboardingTestRoot),
|
||||||
|
})
|
||||||
|
|
||||||
|
ensureLoopbackNoProxy()
|
||||||
|
useEnvProxy()
|
||||||
|
app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>")
|
||||||
|
if (!app.isPackaged) app.commandLine.appendSwitch("remote-debugging-port", "9222")
|
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock()) {
|
||||||
|
app.quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preferAppEnv(app.getPath("userData"))
|
||||||
|
|
||||||
|
app.on("second-instance", (_event: Event, argv: string[]) => {
|
||||||
|
const urls = argv.filter((arg: string) => arg.startsWith("opencode://"))
|
||||||
|
if (urls.length) {
|
||||||
|
logger.log("deep link received via second-instance", { urls })
|
||||||
|
emitDeepLinks(urls)
|
||||||
|
}
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.show()
|
||||||
|
mainWindow.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("open-url", (event: Event, url: string) => {
|
||||||
|
event.preventDefault()
|
||||||
|
logger.log("deep link received via open-url", { url })
|
||||||
|
emitDeepLinks([url])
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("before-quit", () => {
|
||||||
|
void killSidecar()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("will-quit", () => {
|
||||||
|
void killSidecar()
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
||||||
|
process.on(signal, () => {
|
||||||
|
void killSidecar().finally(() => app.exit(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverReady = Deferred.makeUnsafe<ServerReadyData>()
|
||||||
|
const loadingComplete = Deferred.makeUnsafe<void>()
|
||||||
|
|
||||||
|
registerIpcHandlers({
|
||||||
|
killSidecar: () => killSidecar(),
|
||||||
|
awaitInitialization: Effect.fnUntraced(
|
||||||
|
function* (sendStep) {
|
||||||
|
sendStep(initStep)
|
||||||
|
const listener = (step: InitStep) => sendStep(step)
|
||||||
|
initEmitter.on("step", listener)
|
||||||
|
try {
|
||||||
|
logger.log("awaiting server ready")
|
||||||
|
const res = yield* Deferred.await(serverReady)
|
||||||
|
logger.log("server ready", { url: res.url })
|
||||||
|
return res
|
||||||
|
} finally {
|
||||||
|
initEmitter.off("step", listener)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(e) => Effect.runPromise(e),
|
||||||
|
),
|
||||||
|
getWindowConfig: () => ({ updaterEnabled: UPDATER_ENABLED }),
|
||||||
|
consumeInitialDeepLinks: () => pendingDeepLinks.splice(0),
|
||||||
|
getDefaultServerUrl: () => getDefaultServerUrl(),
|
||||||
|
setDefaultServerUrl: (url) => setDefaultServerUrl(url),
|
||||||
|
getWslConfig: () => Promise.resolve(getWslConfig()),
|
||||||
|
setWslConfig: (config: WslConfig) => setWslConfig(config),
|
||||||
|
getDisplayBackend: async () => null,
|
||||||
|
setDisplayBackend: async () => undefined,
|
||||||
|
parseMarkdown: async (markdown) => parseMarkdown(markdown),
|
||||||
|
checkAppExists: (appName) => checkAppExists(appName),
|
||||||
|
wslPath: async (path, mode) => wslPath(path, mode),
|
||||||
|
resolveAppPath: async (appName) => resolveAppPath(appName),
|
||||||
|
loadingWindowComplete: () => Deferred.doneUnsafe(loadingComplete, Effect.void),
|
||||||
|
runUpdater: async (alertOnFail) => checkForUpdates(alertOnFail, killSidecar),
|
||||||
|
checkUpdate: async () => checkUpdate(),
|
||||||
|
installUpdate: async () => installUpdate(killSidecar),
|
||||||
|
setBackgroundColor: (color) => setBackgroundColor(color),
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* Effect.promise(() => app.whenReady())
|
||||||
|
|
||||||
|
if (!TEST_ONBOARDING) migrate()
|
||||||
|
app.setAsDefaultProtocolClient("opencode")
|
||||||
|
registerRendererProtocol()
|
||||||
|
setDockIcon()
|
||||||
|
setupAutoUpdater()
|
||||||
|
|
||||||
|
const needsMigration = ((): boolean => {
|
||||||
|
if (process.env.OPENCODE_DB === ":memory:") return false
|
||||||
|
|
||||||
|
const xdg = process.env.XDG_DATA_HOME
|
||||||
|
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".local", "share")
|
||||||
|
return !existsSync(join(base, "opencode", "opencode.db"))
|
||||||
|
})()
|
||||||
|
let overlay: BrowserWindow | null = null
|
||||||
|
|
||||||
|
const port = yield* Effect.gen(function* () {
|
||||||
const fromEnv = process.env.OPENCODE_PORT
|
const fromEnv = process.env.OPENCODE_PORT
|
||||||
if (fromEnv) {
|
if (fromEnv) {
|
||||||
const parsed = Number.parseInt(fromEnv, 10)
|
const parsed = Number.parseInt(fromEnv, 10)
|
||||||
if (!Number.isNaN(parsed)) return parsed
|
if (!Number.isNaN(parsed)) return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise<number>((resolve, reject) => {
|
const res = yield* Deferred.make<number, unknown>()
|
||||||
const server = createServer()
|
const server = createServer()
|
||||||
server.on("error", reject)
|
server.on("error", (e) => Deferred.failSync(res, () => e))
|
||||||
server.listen(0, "127.0.0.1", () => {
|
server.listen(0, "127.0.0.1", () => {
|
||||||
const address = server.address()
|
const address = server.address()
|
||||||
if (typeof address !== "object" || !address) {
|
if (typeof address !== "object" || !address) {
|
||||||
server.close()
|
server.close()
|
||||||
reject(new Error("Failed to get port"))
|
Deferred.failSync(res, () => new Error("Failed to get port"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const port = address.port
|
const port = address.port
|
||||||
server.close(() => resolve(port))
|
server.close(() => Effect.runSync(Deferred.succeed(res, port)))
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function sqliteFileExists() {
|
return yield* Deferred.await(res)
|
||||||
const xdg = process.env.XDG_DATA_HOME
|
})
|
||||||
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".local", "share")
|
const hostname = "127.0.0.1"
|
||||||
return existsSync(join(base, "opencode", "opencode.db"))
|
const url = `http://${hostname}:${port}`
|
||||||
}
|
const password = randomUUID()
|
||||||
|
|
||||||
function setupAutoUpdater() {
|
const loadingTask = yield* Effect.gen(function* () {
|
||||||
if (!UPDATER_ENABLED) return
|
logger.log("sidecar connection started", { url })
|
||||||
autoUpdater.logger = logger
|
|
||||||
autoUpdater.channel = "latest"
|
|
||||||
autoUpdater.allowPrerelease = false
|
|
||||||
autoUpdater.allowDowngrade = true
|
|
||||||
autoUpdater.autoDownload = false
|
|
||||||
autoUpdater.autoInstallOnAppQuit = true
|
|
||||||
logger.log("auto updater configured", {
|
|
||||||
channel: autoUpdater.channel,
|
|
||||||
allowPrerelease: autoUpdater.allowPrerelease,
|
|
||||||
allowDowngrade: autoUpdater.allowDowngrade,
|
|
||||||
currentVersion: app.getVersion(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let downloadedUpdateVersion: string | undefined
|
initEmitter.on("sqlite", (progress: SqliteMigrationProgress) => {
|
||||||
|
setInitStep({ phase: "sqlite_waiting" })
|
||||||
|
if (overlay) sendSqliteMigrationProgress(overlay, progress)
|
||||||
|
if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress)
|
||||||
|
})
|
||||||
|
|
||||||
async function checkUpdate() {
|
logger.log("spawning sidecar", { url })
|
||||||
if (!UPDATER_ENABLED) return { updateAvailable: false }
|
const { listener, health } = yield* Effect.promise(() =>
|
||||||
if (downloadedUpdateVersion) {
|
spawnLocalServer(
|
||||||
logger.log("returning cached downloaded update", {
|
hostname,
|
||||||
version: downloadedUpdateVersion,
|
port,
|
||||||
|
password,
|
||||||
|
() => {
|
||||||
|
ensureLoopbackNoProxy()
|
||||||
|
useEnvProxy()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
needsMigration,
|
||||||
|
userDataPath: app.getPath("userData"),
|
||||||
|
onSqliteProgress: (progress) => initEmitter.emit("sqlite", progress),
|
||||||
|
onStdout: (message) => logger.log("sidecar stdout", { message }),
|
||||||
|
onStderr: (message) => logger.warn("sidecar stderr", { message }),
|
||||||
|
onExit: (code) => logger.warn("sidecar exited", { code }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
server = listener
|
||||||
|
yield* Deferred.succeed(serverReady, {
|
||||||
|
url,
|
||||||
|
username: "opencode",
|
||||||
|
password,
|
||||||
})
|
})
|
||||||
return { updateAvailable: true, version: downloadedUpdateVersion }
|
|
||||||
}
|
yield* Effect.promise(() => health.wait).pipe(
|
||||||
logger.log("checking for updates", {
|
Effect.timeout("30 seconds"),
|
||||||
currentVersion: app.getVersion(),
|
Effect.catch((e) =>
|
||||||
channel: autoUpdater.channel,
|
Effect.sync(() => {
|
||||||
allowPrerelease: autoUpdater.allowPrerelease,
|
logger.error("sidecar health check failed", e.toString())
|
||||||
allowDowngrade: autoUpdater.allowDowngrade,
|
}),
|
||||||
})
|
),
|
||||||
try {
|
)
|
||||||
const result = await autoUpdater.checkForUpdates()
|
|
||||||
const updateInfo = result?.updateInfo
|
logger.log("loading task finished")
|
||||||
logger.log("update metadata fetched", {
|
}).pipe(Effect.forkChild)
|
||||||
releaseVersion: updateInfo?.version ?? null,
|
|
||||||
releaseDate: updateInfo?.releaseDate ?? null,
|
if (needsMigration) {
|
||||||
releaseName: updateInfo?.releaseName ?? null,
|
const show = yield* loadingTask.pipe(
|
||||||
files: updateInfo?.files?.map((file) => file.url) ?? [],
|
Fiber.await,
|
||||||
})
|
Effect.timeout("1 second"),
|
||||||
const version = result?.updateInfo?.version
|
Effect.as(false),
|
||||||
if (result?.isUpdateAvailable === false || !version) {
|
Effect.catch(() => Effect.succeed(true)),
|
||||||
logger.log("no update available", {
|
)
|
||||||
reason: "provider returned no newer version",
|
if (show) {
|
||||||
})
|
overlay = createLoadingWindow()
|
||||||
return { updateAvailable: false }
|
yield* Effect.sleep("1 second")
|
||||||
}
|
|
||||||
logger.log("update available", { version })
|
|
||||||
await autoUpdater.downloadUpdate()
|
|
||||||
logger.log("update download completed", { version })
|
|
||||||
downloadedUpdateVersion = version
|
|
||||||
return { updateAvailable: true, version }
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("update check failed", error)
|
|
||||||
return { updateAvailable: false, failed: true }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installUpdate() {
|
yield* Fiber.await(loadingTask)
|
||||||
if (!downloadedUpdateVersion) {
|
setInitStep({ phase: "done" })
|
||||||
logger.log("install update skipped", {
|
|
||||||
reason: "no downloaded update ready",
|
if (overlay) yield* Deferred.await(loadingComplete)
|
||||||
|
|
||||||
|
mainWindow = createMainWindow()
|
||||||
|
if (mainWindow) {
|
||||||
|
createMenu({
|
||||||
|
trigger: (id) => mainWindow && sendMenuCommand(mainWindow, id),
|
||||||
|
checkForUpdates: () => {
|
||||||
|
void checkForUpdates(true, killSidecar)
|
||||||
|
},
|
||||||
|
reload: () => mainWindow?.reload(),
|
||||||
|
relaunch: () => {
|
||||||
|
void killSidecar().finally(() => {
|
||||||
|
app.relaunch()
|
||||||
|
app.exit(0)
|
||||||
})
|
})
|
||||||
return
|
},
|
||||||
}
|
|
||||||
logger.log("installing downloaded update", {
|
|
||||||
version: downloadedUpdateVersion,
|
|
||||||
})
|
})
|
||||||
killSidecar()
|
|
||||||
autoUpdater.quitAndInstall()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkForUpdates(alertOnFail: boolean) {
|
overlay?.close()
|
||||||
if (!UPDATER_ENABLED) return
|
|
||||||
logger.log("checkForUpdates invoked", { alertOnFail })
|
|
||||||
const result = await checkUpdate()
|
|
||||||
if (!result.updateAvailable) {
|
|
||||||
if (result.failed) {
|
|
||||||
logger.log("no update decision", { reason: "update check failed" })
|
|
||||||
if (!alertOnFail) return
|
|
||||||
await dialog.showMessageBox({
|
|
||||||
type: "error",
|
|
||||||
message: "Update check failed.",
|
|
||||||
title: "Update Error",
|
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log("no update decision", { reason: "already up to date" })
|
Effect.runFork(main)
|
||||||
if (!alertOnFail) return
|
|
||||||
await dialog.showMessageBox({
|
|
||||||
type: "info",
|
|
||||||
message: "You're up to date.",
|
|
||||||
title: "No Updates",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await dialog.showMessageBox({
|
|
||||||
type: "info",
|
|
||||||
message: `Update ${result.version ?? ""} downloaded. Restart now?`,
|
|
||||||
title: "Update Ready",
|
|
||||||
buttons: ["Restart", "Later"],
|
|
||||||
defaultId: 0,
|
|
||||||
cancelId: 1,
|
|
||||||
})
|
|
||||||
logger.log("update prompt response", {
|
|
||||||
version: result.version ?? null,
|
|
||||||
restartNow: response.response === 0,
|
|
||||||
})
|
|
||||||
if (response.response === 0) {
|
|
||||||
await installUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delay(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
function defer<T>() {
|
|
||||||
let resolve!: (value: T) => void
|
|
||||||
let reject!: (error: Error) => void
|
|
||||||
const promise = new Promise<T>((res, rej) => {
|
|
||||||
resolve = res
|
|
||||||
reject = rej
|
|
||||||
})
|
|
||||||
return { promise, resolve, reject }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const pickerFilters = (ext?: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Deps = {
|
type Deps = {
|
||||||
killSidecar: () => void
|
killSidecar: () => Promise<void> | void
|
||||||
awaitInitialization: (sendStep: (step: InitStep) => void) => Promise<ServerReadyData>
|
awaitInitialization: (sendStep: (step: InitStep) => void) => Promise<ServerReadyData>
|
||||||
getWindowConfig: () => Promise<WindowConfig> | WindowConfig
|
getWindowConfig: () => Promise<WindowConfig> | WindowConfig
|
||||||
consumeInitialDeepLinks: () => Promise<string[]> | string[]
|
consumeInitialDeepLinks: () => Promise<string[]> | string[]
|
||||||
@@ -70,10 +70,14 @@ export function registerIpcHandlers(deps: Deps) {
|
|||||||
ipcMain.handle("install-update", () => deps.installUpdate())
|
ipcMain.handle("install-update", () => deps.installUpdate())
|
||||||
ipcMain.handle("set-background-color", (_event: IpcMainInvokeEvent, color: string) => deps.setBackgroundColor(color))
|
ipcMain.handle("set-background-color", (_event: IpcMainInvokeEvent, color: string) => deps.setBackgroundColor(color))
|
||||||
ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => {
|
ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => {
|
||||||
|
try {
|
||||||
const store = getStore(name)
|
const store = getStore(name)
|
||||||
const value = store.get(key)
|
const value = store.get(key)
|
||||||
if (value === undefined || value === null) return null
|
if (value === undefined || value === null) return null
|
||||||
return typeof value === "string" ? value : JSON.stringify(value)
|
return typeof value === "string" ? value : JSON.stringify(value)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
ipcMain.handle("store-set", (_event: IpcMainInvokeEvent, name: string, key: string, value: string) => {
|
ipcMain.handle("store-set", (_event: IpcMainInvokeEvent, name: string, key: string, value: string) => {
|
||||||
getStore(name).set(key, value)
|
getStore(name).set(key, value)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MainLogger } from "electron-log"
|
||||||
import log from "electron-log/main.js"
|
import log from "electron-log/main.js"
|
||||||
import { readFileSync, readdirSync, statSync, unlinkSync } from "node:fs"
|
import { readFileSync, readdirSync, statSync, unlinkSync } from "node:fs"
|
||||||
import { dirname, join } from "node:path"
|
import { dirname, join } from "node:path"
|
||||||
@@ -5,10 +6,14 @@ import { dirname, join } from "node:path"
|
|||||||
const MAX_LOG_AGE_DAYS = 7
|
const MAX_LOG_AGE_DAYS = 7
|
||||||
const TAIL_LINES = 1000
|
const TAIL_LINES = 1000
|
||||||
|
|
||||||
|
let logger: MainLogger
|
||||||
|
export const getLogger = () => logger
|
||||||
|
|
||||||
export function initLogging() {
|
export function initLogging() {
|
||||||
log.transports.file.maxSize = 5 * 1024 * 1024
|
log.transports.file.maxSize = 5 * 1024 * 1024
|
||||||
|
initConsoleTransport()
|
||||||
cleanup()
|
cleanup()
|
||||||
return log
|
return (logger = log)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tail(): string {
|
export function tail(): string {
|
||||||
@@ -38,3 +43,19 @@ function cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initConsoleTransport() {
|
||||||
|
const write = log.transports.console.writeFn.bind(log.transports.console)
|
||||||
|
log.transports.console.writeFn = (options) => {
|
||||||
|
try {
|
||||||
|
write(options)
|
||||||
|
} catch (err) {
|
||||||
|
if (!isBrokenPipe(err)) throw err
|
||||||
|
log.transports.console.level = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBrokenPipe(err: unknown) {
|
||||||
|
return typeof err === "object" && err !== null && "code" in err && err.code === "EPIPE"
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ export function createMenu(deps: Deps) {
|
|||||||
enabled: UPDATER_ENABLED,
|
enabled: UPDATER_ENABLED,
|
||||||
click: () => deps.checkForUpdates(),
|
click: () => deps.checkForUpdates(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Settings",
|
||||||
|
accelerator: "Cmd+,",
|
||||||
|
click: () => deps.trigger("settings.open"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Reload Webview",
|
label: "Reload Webview",
|
||||||
click: () => deps.reload(),
|
click: () => deps.reload(),
|
||||||
|
|||||||
@@ -1,12 +1,37 @@
|
|||||||
import { app } from "electron"
|
import { dirname, join } from "node:path"
|
||||||
|
import { fileURLToPath } from "node:url"
|
||||||
|
import { app, utilityProcess } from "electron"
|
||||||
|
import type { Details } from "electron"
|
||||||
import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
|
import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
|
||||||
import { getUserShell, loadShellEnv } from "./shell-env"
|
import { getUserShell, loadShellEnv } from "./shell-env"
|
||||||
import { getStore } from "./store"
|
import { getStore } from "./store"
|
||||||
|
import type { SqliteMigrationProgress } from "../preload/types"
|
||||||
|
|
||||||
export type WslConfig = { enabled: boolean }
|
export type WslConfig = { enabled: boolean }
|
||||||
|
|
||||||
export type HealthCheck = { wait: Promise<void> }
|
export type HealthCheck = { wait: Promise<void> }
|
||||||
|
|
||||||
|
type SidecarMessage =
|
||||||
|
| { type: "sqlite"; progress: SqliteMigrationProgress }
|
||||||
|
| { type: "ready" }
|
||||||
|
| { type: "stopped" }
|
||||||
|
| { type: "error"; error: { message: string; stack?: string } }
|
||||||
|
|
||||||
|
export type SidecarListener = { stop: () => Promise<void> }
|
||||||
|
|
||||||
|
const SIDECAR_SERVICE_NAME = "opencode server"
|
||||||
|
const SIDECAR_START_STALL_TIMEOUT = 60_000
|
||||||
|
const SIDECAR_STOP_TIMEOUT = 6_000
|
||||||
|
|
||||||
|
type SpawnLocalServerOptions = {
|
||||||
|
needsMigration: boolean
|
||||||
|
userDataPath: string
|
||||||
|
onSqliteProgress?: (progress: SqliteMigrationProgress) => void
|
||||||
|
onStdout?: (message: string) => void
|
||||||
|
onStderr?: (message: string) => void
|
||||||
|
onExit?: (code: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
export function getDefaultServerUrl(): string | null {
|
export function getDefaultServerUrl(): string | null {
|
||||||
const value = getStore().get(DEFAULT_SERVER_URL_KEY)
|
const value = getStore().get(DEFAULT_SERVER_URL_KEY)
|
||||||
return typeof value === "string" ? value : null
|
return typeof value === "string" ? value : null
|
||||||
@@ -30,48 +55,152 @@ export function setWslConfig(config: WslConfig) {
|
|||||||
getStore().set(WSL_ENABLED_KEY, config.enabled)
|
getStore().set(WSL_ENABLED_KEY, config.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function spawnLocalServer(hostname: string, port: number, password: string) {
|
export function preferAppEnv(userDataPath: string) {
|
||||||
prepareServerEnv(password)
|
const shell = process.platform === "win32" ? null : getUserShell()
|
||||||
const { Log, Server } = await import("virtual:opencode-server")
|
Object.assign(process.env, {
|
||||||
await Log.init({ level: "WARN" })
|
...(shell ? loadShellEnv(shell) : null),
|
||||||
const listener = await Server.listen({
|
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
|
||||||
port,
|
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
|
||||||
|
OPENCODE_CLIENT: "desktop",
|
||||||
|
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function spawnLocalServer(
|
||||||
|
hostname: string,
|
||||||
|
port: number,
|
||||||
|
password: string,
|
||||||
|
configureEnv: () => void,
|
||||||
|
options: SpawnLocalServerOptions,
|
||||||
|
) {
|
||||||
|
configureEnv?.()
|
||||||
|
const sidecar = join(dirname(fileURLToPath(import.meta.url)), "sidecar.js")
|
||||||
|
const child = utilityProcess.fork(sidecar, [], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: createSidecarEnv(),
|
||||||
|
serviceName: SIDECAR_SERVICE_NAME,
|
||||||
|
stdio: "pipe",
|
||||||
|
})
|
||||||
|
let exited = false
|
||||||
|
const exit = defer<number>()
|
||||||
|
|
||||||
|
const onProcessGone = (_event: unknown, details: Details) => {
|
||||||
|
if (details.type !== "Utility" || details.name !== SIDECAR_SERVICE_NAME) return
|
||||||
|
options.onStderr?.(`utility process gone reason=${details.reason} exitCode=${details.exitCode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on("child-process-gone", onProcessGone)
|
||||||
|
child.once("exit", (code) => {
|
||||||
|
exited = true
|
||||||
|
app.off("child-process-gone", onProcessGone)
|
||||||
|
options.onExit?.(code)
|
||||||
|
exit.resolve(code)
|
||||||
|
})
|
||||||
|
child.on("error", (error) => options.onStderr?.(`utility process error: ${serializeError(error).message}`))
|
||||||
|
|
||||||
|
child.stdout?.on("data", (chunk: Buffer) => options.onStdout?.(chunk.toString("utf8").trimEnd()))
|
||||||
|
child.stderr?.on("data", (chunk: Buffer) => options.onStderr?.(chunk.toString("utf8").trimEnd()))
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
let done = false
|
||||||
|
let timeout: NodeJS.Timeout
|
||||||
|
|
||||||
|
const fail = (error: Error) => {
|
||||||
|
if (done) return
|
||||||
|
done = true
|
||||||
|
cleanup()
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshTimeout = () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
fail(new Error(`Sidecar did not become ready within ${SIDECAR_START_STALL_TIMEOUT}ms: ${sidecar}`))
|
||||||
|
}, SIDECAR_START_STALL_TIMEOUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMessage = (message: SidecarMessage) => {
|
||||||
|
if (message.type === "sqlite") {
|
||||||
|
refreshTimeout()
|
||||||
|
options.onSqliteProgress?.(message.progress)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (message.type === "ready") {
|
||||||
|
if (done) return
|
||||||
|
done = true
|
||||||
|
cleanup()
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (message.type === "error") {
|
||||||
|
fail(Object.assign(new Error(message.error.message), { stack: message.error.stack }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onExit = (code: number) => {
|
||||||
|
fail(new Error(`Sidecar exited before ready with code ${code}`))
|
||||||
|
}
|
||||||
|
const cleanup = () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
child.off("message", onMessage)
|
||||||
|
child.off("exit", onExit)
|
||||||
|
}
|
||||||
|
|
||||||
|
child.on("message", onMessage)
|
||||||
|
child.on("exit", onExit)
|
||||||
|
refreshTimeout()
|
||||||
|
child.postMessage({
|
||||||
|
type: "start",
|
||||||
hostname,
|
hostname,
|
||||||
username: "opencode",
|
port,
|
||||||
password,
|
password,
|
||||||
cors: ["oc://renderer"],
|
userDataPath: options.userDataPath,
|
||||||
|
needsMigration: options.needsMigration,
|
||||||
|
})
|
||||||
|
}).catch((error) => {
|
||||||
|
if (!exited) child.kill()
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
const wait = (async () => {
|
const wait = (async () => {
|
||||||
const url = `http://${hostname}:${port}`
|
const url = `http://${hostname}:${port}`
|
||||||
|
let healthy = false
|
||||||
|
const gone = exit.promise.then((code) => {
|
||||||
|
if (healthy) return
|
||||||
|
throw new Error(`Sidecar exited before health check passed with code ${code}`)
|
||||||
|
})
|
||||||
|
|
||||||
const ready = async () => {
|
const ready = async () => {
|
||||||
while (true) {
|
while (true) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
if (await checkHealth(url, password)) return
|
if (await checkHealth(url, password)) {
|
||||||
|
healthy = true
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await ready()
|
await Promise.race([ready(), gone])
|
||||||
})()
|
})()
|
||||||
|
|
||||||
return { listener, health: { wait } }
|
let stopping: Promise<void> | undefined
|
||||||
}
|
|
||||||
|
|
||||||
function prepareServerEnv(password: string) {
|
return {
|
||||||
const shell = process.platform === "win32" ? null : getUserShell()
|
listener: {
|
||||||
const shellEnv = shell ? (loadShellEnv(shell) ?? {}) : {}
|
stop: () => {
|
||||||
const env = {
|
if (stopping) return stopping
|
||||||
...process.env,
|
if (exited) return Promise.resolve()
|
||||||
...shellEnv,
|
child.postMessage({ type: "stop" })
|
||||||
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
|
stopping = Promise.race([
|
||||||
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
|
exit.promise.then(() => undefined),
|
||||||
OPENCODE_CLIENT: "desktop",
|
delay(SIDECAR_STOP_TIMEOUT).then(() => {
|
||||||
OPENCODE_SERVER_USERNAME: "opencode",
|
if (!exited) child.kill()
|
||||||
OPENCODE_SERVER_PASSWORD: password,
|
}),
|
||||||
XDG_STATE_HOME: app.getPath("userData"),
|
])
|
||||||
|
return stopping
|
||||||
|
},
|
||||||
|
},
|
||||||
|
health: { wait },
|
||||||
}
|
}
|
||||||
Object.assign(process.env, env)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
|
export async function checkHealth(url: string, password?: string | null): Promise<boolean> {
|
||||||
@@ -99,3 +228,31 @@ export async function checkHealth(url: string, password?: string | null): Promis
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSidecarEnv(): Record<string, string> {
|
||||||
|
const env = Object.fromEntries(
|
||||||
|
Object.entries(process.env).flatMap(([key, value]) => (value === undefined ? [] : [[key, String(value)]])),
|
||||||
|
)
|
||||||
|
delete env.DEBUG
|
||||||
|
if (process.platform === "linux") delete env.LD_PRELOAD
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
function delay(ms: number) {
|
||||||
|
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeError(error: unknown) {
|
||||||
|
if (error instanceof Error) return { message: error.message, stack: error.stack }
|
||||||
|
return { message: String(error) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function defer<T>() {
|
||||||
|
let resolve!: (value: T) => void
|
||||||
|
let reject!: (error: Error) => void
|
||||||
|
const promise = new Promise<T>((res, rej) => {
|
||||||
|
resolve = res
|
||||||
|
reject = rej
|
||||||
|
})
|
||||||
|
return { promise, resolve, reject }
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user