diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 66d6526c75..34ae0fc311 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -22,16 +22,20 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Bun uses: ./.github/actions/setup-bun - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Setup Git Committer + id: setup-git-committer + uses: ./.github/actions/setup-git-committer + with: + opencode-app-id: ${{ vars.OPENCODE_APP_ID }} + opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - name: Sync beta branch env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }} run: bun script/beta.ts diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 31b3962d9d..4679b59c90 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -4,8 +4,6 @@ on: push: branches: - dev - pull_request: - workflow_dispatch: jobs: generate: diff --git a/infra/console.ts b/infra/console.ts index 5b08e9ceaa..ba1ff15bf2 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -133,6 +133,8 @@ const ZEN_MODELS = [ new sst.Secret("ZEN_MODELS6"), new sst.Secret("ZEN_MODELS7"), new sst.Secret("ZEN_MODELS8"), + new sst.Secret("ZEN_MODELS9"), + new sst.Secret("ZEN_MODELS10"), ] const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY") const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY") diff --git a/packages/app/e2e/home.spec.ts b/packages/app/e2e/app/home.spec.ts similarity index 88% rename from packages/app/e2e/home.spec.ts rename to packages/app/e2e/app/home.spec.ts index c6fb0e3b07..f21dc40ec2 100644 --- a/packages/app/e2e/home.spec.ts +++ b/packages/app/e2e/app/home.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { serverName } from "./utils" +import { test, expect } from "../fixtures" +import { serverName } from "../utils" test("home renders and shows core entrypoints", async ({ page }) => { await page.goto("/") diff --git a/packages/app/e2e/navigation.spec.ts b/packages/app/e2e/app/navigation.spec.ts similarity index 72% rename from packages/app/e2e/navigation.spec.ts rename to packages/app/e2e/app/navigation.spec.ts index 76923af6ed..0812ea0187 100644 --- a/packages/app/e2e/navigation.spec.ts +++ b/packages/app/e2e/app/navigation.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { dirPath, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { dirPath, promptSelector } from "../utils" test("project route redirects to /session", async ({ page, directory, slug }) => { await page.goto(dirPath(directory)) diff --git a/packages/app/e2e/palette.spec.ts b/packages/app/e2e/app/palette.spec.ts similarity index 82% rename from packages/app/e2e/palette.spec.ts rename to packages/app/e2e/app/palette.spec.ts index 617c55ac16..264b463bb4 100644 --- a/packages/app/e2e/palette.spec.ts +++ b/packages/app/e2e/app/palette.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("search palette opens and closes", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/server-default.spec.ts b/packages/app/e2e/app/server-default.spec.ts similarity index 95% rename from packages/app/e2e/server-default.spec.ts rename to packages/app/e2e/app/server-default.spec.ts index b6b16f0bcc..6f44ded1a2 100644 --- a/packages/app/e2e/server-default.spec.ts +++ b/packages/app/e2e/app/server-default.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { serverName, serverUrl } from "./utils" +import { test, expect } from "../fixtures" +import { serverName, serverUrl } from "../utils" const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl" diff --git a/packages/app/e2e/session.spec.ts b/packages/app/e2e/app/session.spec.ts similarity index 88% rename from packages/app/e2e/session.spec.ts rename to packages/app/e2e/app/session.spec.ts index 19e25a4213..8d605f0c3a 100644 --- a/packages/app/e2e/session.spec.ts +++ b/packages/app/e2e/app/session.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("can open an existing session and type into the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke ${Date.now()}` diff --git a/packages/app/e2e/titlebar-history.spec.ts b/packages/app/e2e/app/titlebar-history.spec.ts similarity index 95% rename from packages/app/e2e/titlebar-history.spec.ts rename to packages/app/e2e/app/titlebar-history.spec.ts index d4aa605e6d..649e5e0dc1 100644 --- a/packages/app/e2e/titlebar-history.spec.ts +++ b/packages/app/e2e/app/titlebar-history.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => { await page.setViewportSize({ width: 1400, height: 800 }) diff --git a/packages/app/e2e/file-open.spec.ts b/packages/app/e2e/files/file-open.spec.ts similarity index 89% rename from packages/app/e2e/file-open.spec.ts rename to packages/app/e2e/files/file-open.spec.ts index fb7104b6b0..e384f0b0da 100644 --- a/packages/app/e2e/file-open.spec.ts +++ b/packages/app/e2e/files/file-open.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("can open a file tab from the search palette", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/file-tree.spec.ts b/packages/app/e2e/files/file-tree.spec.ts similarity index 96% rename from packages/app/e2e/file-tree.spec.ts rename to packages/app/e2e/files/file-tree.spec.ts index 0b04eb2468..844da1b329 100644 --- a/packages/app/e2e/file-tree.spec.ts +++ b/packages/app/e2e/files/file-tree.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "./fixtures" +import { test, expect } from "../fixtures" test.skip("file tree can expand folders and open a file", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/file-viewer.spec.ts b/packages/app/e2e/files/file-viewer.spec.ts similarity index 92% rename from packages/app/e2e/file-viewer.spec.ts rename to packages/app/e2e/files/file-viewer.spec.ts index 1e0f8a6f23..bed6d1d369 100644 --- a/packages/app/e2e/file-viewer.spec.ts +++ b/packages/app/e2e/files/file-viewer.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("smoke file viewer renders real file content", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/model-picker.spec.ts b/packages/app/e2e/models/model-picker.spec.ts similarity index 94% rename from packages/app/e2e/model-picker.spec.ts rename to packages/app/e2e/models/model-picker.spec.ts index 9e64b3dfb0..a0c70aabef 100644 --- a/packages/app/e2e/model-picker.spec.ts +++ b/packages/app/e2e/models/model-picker.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke model selection updates prompt footer", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/models-visibility.spec.ts b/packages/app/e2e/models/models-visibility.spec.ts similarity index 96% rename from packages/app/e2e/models-visibility.spec.ts rename to packages/app/e2e/models/models-visibility.spec.ts index 680ba96a31..0db7580c20 100644 --- a/packages/app/e2e/models-visibility.spec.ts +++ b/packages/app/e2e/models/models-visibility.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/context.spec.ts b/packages/app/e2e/prompt/context.spec.ts similarity index 93% rename from packages/app/e2e/context.spec.ts rename to packages/app/e2e/prompt/context.spec.ts index beabd2eb7d..f0f3f073ae 100644 --- a/packages/app/e2e/context.spec.ts +++ b/packages/app/e2e/prompt/context.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke context ${Date.now()}` diff --git a/packages/app/e2e/prompt-mention.spec.ts b/packages/app/e2e/prompt/prompt-mention.spec.ts similarity index 90% rename from packages/app/e2e/prompt-mention.spec.ts rename to packages/app/e2e/prompt/prompt-mention.spec.ts index 113b8465f7..85acb4c28f 100644 --- a/packages/app/e2e/prompt-mention.spec.ts +++ b/packages/app/e2e/prompt/prompt-mention.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke @mention inserts file pill token", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/prompt-slash-open.spec.ts b/packages/app/e2e/prompt/prompt-slash-open.spec.ts similarity index 87% rename from packages/app/e2e/prompt-slash-open.spec.ts rename to packages/app/e2e/prompt/prompt-slash-open.spec.ts index 3c29d405c1..3e769e3305 100644 --- a/packages/app/e2e/prompt-slash-open.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-open.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" test("smoke /open opens file picker dialog", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/prompt.spec.ts b/packages/app/e2e/prompt/prompt.spec.ts similarity index 95% rename from packages/app/e2e/prompt.spec.ts rename to packages/app/e2e/prompt/prompt.spec.ts index 3e5892ce8d..b58e5e296c 100644 --- a/packages/app/e2e/prompt.spec.ts +++ b/packages/app/e2e/prompt/prompt.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector } from "../utils" function sessionIDFromUrl(url: string) { const match = /\/session\/([^/?#]+)/.exec(url) diff --git a/packages/app/e2e/settings/settings-language.spec.ts b/packages/app/e2e/settings/settings-language.spec.ts new file mode 100644 index 0000000000..b2ef70bf88 --- /dev/null +++ b/packages/app/e2e/settings/settings-language.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from "../fixtures" +import { modKey, settingsLanguageSelectSelector } from "../utils" + +test("smoke changing language updates settings labels", async ({ page, gotoSession }) => { + await page.addInitScript(() => { + localStorage.setItem("opencode.global.dat:language", JSON.stringify({ locale: "en" })) + }) + + await gotoSession() + + const dialog = page.getByRole("dialog") + + await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined) + + const opened = await dialog + .waitFor({ state: "visible", timeout: 3000 }) + .then(() => true) + .catch(() => false) + + if (!opened) { + await page.getByRole("button", { name: "Settings" }).first().click() + await expect(dialog).toBeVisible() + } + + const heading = dialog.getByRole("heading", { level: 2 }) + await expect(heading).toHaveText("General") + + const select = dialog.locator(settingsLanguageSelectSelector) + await expect(select).toBeVisible() + await select.locator('[data-slot="select-select-trigger"]').click() + + await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Deutsch" }).click() + + await expect(heading).toHaveText("Allgemein") + + await select.locator('[data-slot="select-select-trigger"]').click() + await page.locator('[data-slot="select-select-item"]').filter({ hasText: "English" }).click() + await expect(heading).toHaveText("General") +}) diff --git a/packages/app/e2e/settings-providers.spec.ts b/packages/app/e2e/settings/settings-providers.spec.ts similarity index 95% rename from packages/app/e2e/settings-providers.spec.ts rename to packages/app/e2e/settings/settings-providers.spec.ts index 326a9fad1d..5b9325c2ab 100644 --- a/packages/app/e2e/settings-providers.spec.ts +++ b/packages/app/e2e/settings/settings-providers.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" test("smoke providers settings opens provider selector", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts similarity index 94% rename from packages/app/e2e/settings.spec.ts rename to packages/app/e2e/settings/settings.spec.ts index 09dc942cc9..293a4ba9aa 100644 --- a/packages/app/e2e/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("smoke settings dialog opens, switches tabs, closes", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/sidebar-session-links.spec.ts b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts similarity index 64% rename from packages/app/e2e/sidebar-session-links.spec.ts rename to packages/app/e2e/sidebar/sidebar-session-links.spec.ts index fab64736e2..8c3f695475 100644 --- a/packages/app/e2e/sidebar-session-links.spec.ts +++ b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts @@ -1,33 +1,7 @@ -import { test, expect } from "./fixtures" -import { modKey, promptSelector } from "./utils" +import { test, expect } from "../fixtures" +import { modKey, promptSelector } from "../utils" -type Locator = { - first: () => Locator - getAttribute: (name: string) => Promise - scrollIntoViewIfNeeded: () => Promise - click: () => Promise -} - -type Page = { - locator: (selector: string) => Locator - keyboard: { - press: (key: string) => Promise - } -} - -type Fixtures = { - page: Page - slug: string - sdk: { - session: { - create: (input: { title: string }) => Promise<{ data?: { id?: string } }> - delete: (input: { sessionID: string }) => Promise - } - } - gotoSession: (sessionID?: string) => Promise -} - -test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }: Fixtures) => { +test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => { const stamp = Date.now() const one = await sdk.session.create({ title: `e2e sidebar nav 1 ${stamp}` }).then((r) => r.data) diff --git a/packages/app/e2e/sidebar.spec.ts b/packages/app/e2e/sidebar/sidebar.spec.ts similarity index 88% rename from packages/app/e2e/sidebar.spec.ts rename to packages/app/e2e/sidebar/sidebar.spec.ts index 925590f510..ba58b1008f 100644 --- a/packages/app/e2e/sidebar.spec.ts +++ b/packages/app/e2e/sidebar/sidebar.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { modKey } from "./utils" +import { test, expect } from "../fixtures" +import { modKey } from "../utils" test("sidebar can be collapsed and expanded", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/terminal-init.spec.ts b/packages/app/e2e/terminal/terminal-init.spec.ts similarity index 93% rename from packages/app/e2e/terminal-init.spec.ts rename to packages/app/e2e/terminal/terminal-init.spec.ts index cfde2d0193..6faa73a751 100644 --- a/packages/app/e2e/terminal-init.spec.ts +++ b/packages/app/e2e/terminal/terminal-init.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { promptSelector, terminalSelector, terminalToggleKey } from "./utils" +import { test, expect } from "../fixtures" +import { promptSelector, terminalSelector, terminalToggleKey } from "../utils" test("smoke terminal mounts and can create a second tab", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/terminal.spec.ts b/packages/app/e2e/terminal/terminal.spec.ts similarity index 79% rename from packages/app/e2e/terminal.spec.ts rename to packages/app/e2e/terminal/terminal.spec.ts index fc558b6325..aaf5c2d75d 100644 --- a/packages/app/e2e/terminal.spec.ts +++ b/packages/app/e2e/terminal/terminal.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "./fixtures" -import { terminalSelector, terminalToggleKey } from "./utils" +import { test, expect } from "../fixtures" +import { terminalSelector, terminalToggleKey } from "../utils" test("terminal panel can be toggled", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts index d47fdb7e6b..c648b1697d 100644 --- a/packages/app/e2e/utils.ts +++ b/packages/app/e2e/utils.ts @@ -14,6 +14,8 @@ export const promptSelector = '[data-component="prompt-input"]' export const terminalSelector = '[data-component="terminal"]' export const modelVariantCycleSelector = '[data-action="model-variant-cycle"]' +export const settingsLanguageSelectSelector = '[data-action="settings-language"]' + export function createSdk(directory?: string) { return createOpencodeClient({ baseUrl: serverUrl, directory, throwOnError: true }) } diff --git a/packages/app/package.json b/packages/app/package.json index 365878e952..cf4e0623e0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -36,7 +36,6 @@ }, "dependencies": { "@kobalte/core": "catalog:", - "@kilocode/kilo-i18n": "workspace:*", "@kilocode/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -65,6 +64,7 @@ "solid-list": "catalog:", "tailwindcss": "catalog:", "virtua": "catalog:", - "zod": "catalog:" + "zod": "catalog:", + "@kilocode/kilo-i18n": "workspace:*" } } diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 3d0d6c7938..02b9e862f1 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -89,7 +89,7 @@ const ModelList: Component<{ export function ModelSelectorPopover(props: { provider?: string - children?: JSX.Element + children?: JSX.Element | ((open: boolean) => JSX.Element) triggerAs?: T triggerProps?: ComponentProps }) { @@ -181,12 +181,13 @@ export function ModelSelectorPopover(props: { as={props.triggerAs ?? "div"} {...(props.triggerProps as any)} > - {props.children} + {typeof props.children === "function" ? props.children(store.open) : props.children} setStore("content", el)} - class="w-72 h-80 flex flex-col p-2 rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden" onEscapeKeyDown={(event) => { setStore("dismiss", "escape") setStore("open", false) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 091b433859..7edfd571e3 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -32,7 +32,9 @@ import { useNavigate, useParams } from "@solidjs/router" import { useSync } from "@/context/sync" import { useComments } from "@/context/comments" import { FileIcon } from "@opencode-ai/ui/file-icon" +import { MorphChevron } from "@opencode-ai/ui/morph-chevron" import { Button } from "@opencode-ai/ui/button" +import { CycleLabel } from "@opencode-ai/ui/cycle-label" import { Icon } from "@opencode-ai/ui/icon" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import type { IconName } from "@opencode-ai/ui/icons/provider" @@ -42,6 +44,7 @@ import { Select } from "@opencode-ai/ui/select" import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { ImagePreview } from "@opencode-ai/ui/image-preview" +import { ReasoningIcon } from "@opencode-ai/ui/reasoning-icon" import { ModelSelectorPopover } from "@/components/dialog-select-model" import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid" import { useProviders } from "@/hooks/use-providers" @@ -1252,7 +1255,7 @@ export const PromptInput: Component = (props) => { clearInput() client.session .shell({ - sessionID: session.id, + sessionID: session?.id || "", agent, model, command: text, @@ -1275,7 +1278,7 @@ export const PromptInput: Component = (props) => { clearInput() client.session .command({ - sessionID: session.id, + sessionID: session?.id || "", command: commandName, arguments: args.join(" "), agent, @@ -1431,13 +1434,13 @@ export const PromptInput: Component = (props) => { const optimisticParts = requestParts.map((part) => ({ ...part, - sessionID: session.id, + sessionID: session?.id || "", messageID, })) as unknown as Part[] const optimisticMessage: Message = { id: messageID, - sessionID: session.id, + sessionID: session?.id || "", role: "user", time: { created: Date.now() }, agent, @@ -1448,9 +1451,9 @@ export const PromptInput: Component = (props) => { if (sessionDirectory === projectDirectory) { sync.set( produce((draft) => { - const messages = draft.message[session.id] + const messages = draft.message[session?.id || ""] if (!messages) { - draft.message[session.id] = [optimisticMessage] + draft.message[session?.id || ""] = [optimisticMessage] } else { const result = Binary.search(messages, messageID, (m) => m.id) messages.splice(result.index, 0, optimisticMessage) @@ -1466,9 +1469,9 @@ export const PromptInput: Component = (props) => { globalSync.child(sessionDirectory)[1]( produce((draft) => { - const messages = draft.message[session.id] + const messages = draft.message[session?.id || ""] if (!messages) { - draft.message[session.id] = [optimisticMessage] + draft.message[session?.id || ""] = [optimisticMessage] } else { const result = Binary.search(messages, messageID, (m) => m.id) messages.splice(result.index, 0, optimisticMessage) @@ -1485,7 +1488,7 @@ export const PromptInput: Component = (props) => { if (sessionDirectory === projectDirectory) { sync.set( produce((draft) => { - const messages = draft.message[session.id] + const messages = draft.message[session?.id || ""] if (messages) { const result = Binary.search(messages, messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) @@ -1498,7 +1501,7 @@ export const PromptInput: Component = (props) => { globalSync.child(sessionDirectory)[1]( produce((draft) => { - const messages = draft.message[session.id] + const messages = draft.message[session?.id || ""] if (messages) { const result = Binary.search(messages, messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) @@ -1519,15 +1522,15 @@ export const PromptInput: Component = (props) => { const worktree = WorktreeState.get(sessionDirectory) if (!worktree || worktree.status !== "pending") return true - if (sessionDirectory === projectDirectory) { - sync.set("session_status", session.id, { type: "busy" }) + if (sessionDirectory === projectDirectory && session?.id) { + sync.set("session_status", session?.id, { type: "busy" }) } const controller = new AbortController() const cleanup = () => { - if (sessionDirectory === projectDirectory) { - sync.set("session_status", session.id, { type: "idle" }) + if (sessionDirectory === projectDirectory && session?.id) { + sync.set("session_status", session?.id, { type: "idle" }) } removeOptimisticMessage() for (const item of commentItems) { @@ -1544,7 +1547,7 @@ export const PromptInput: Component = (props) => { restoreInput() } - pending.set(session.id, { abort: controller, cleanup }) + pending.set(session?.id || "", { abort: controller, cleanup }) const abort = new Promise>>((resolve) => { if (controller.signal.aborted) { @@ -1572,7 +1575,7 @@ export const PromptInput: Component = (props) => { if (timer.id === undefined) return clearTimeout(timer.id) }) - pending.delete(session.id) + pending.delete(session?.id || "") if (controller.signal.aborted) return false if (result.status === "failed") throw new Error(result.message) return true @@ -1582,7 +1585,7 @@ export const PromptInput: Component = (props) => { const ok = await waitForWorktree() if (!ok) return await client.session.prompt({ - sessionID: session.id, + sessionID: session?.id || "", agent, model, messageID, @@ -1592,9 +1595,9 @@ export const PromptInput: Component = (props) => { } void send().catch((err) => { - pending.delete(session.id) - if (sessionDirectory === projectDirectory) { - sync.set("session_status", session.id, { type: "idle" }) + pending.delete(session?.id || "") + if (sessionDirectory === projectDirectory && session?.id) { + sync.set("session_status", session?.id, { type: "idle" }) } showToast({ title: language.t("prompt.toast.promptSendFailed.title"), @@ -1616,6 +1619,28 @@ export const PromptInput: Component = (props) => { }) } + const currrentModelVariant = createMemo(() => { + const modelVariant = local.model.variant.current() ?? "" + return modelVariant === "xhigh" + ? "xHigh" + : modelVariant.length > 0 + ? modelVariant[0].toUpperCase() + modelVariant.slice(1) + : "Default" + }) + + const reasoningPercentage = createMemo(() => { + const variants = local.model.variant.list() + const current = local.model.variant.current() + const totalEntries = variants.length + 1 + + if (totalEntries <= 2 || current === "Default") { + return 0 + } + + const currentIndex = current ? variants.indexOf(current) + 1 : 0 + return ((currentIndex + 1) / totalEntries) * 100 + }, [local.model.variant]) + return (
@@ -1668,7 +1693,7 @@ export const PromptInput: Component = (props) => { } > - + @{(item as { type: "agent"; name: string }).name} @@ -1729,9 +1754,9 @@ export const PromptInput: Component = (props) => { }} > -
+
- + {language.t("prompt.dropzone.label")}
@@ -1770,7 +1795,7 @@ export const PromptInput: Component = (props) => { }} >
- +
{getFilenameTruncated(item.path, 14)} @@ -1787,7 +1812,7 @@ export const PromptInput: Component = (props) => { type="button" icon="close-small" variant="ghost" - class="ml-auto h-5 w-5 opacity-0 group-hover:opacity-100 transition-all" + class="ml-auto size-7 opacity-0 group-hover:opacity-100 transition-all" onClick={(e) => { e.stopPropagation() if (item.commentID) comments.remove(item.path, item.commentID) @@ -1817,7 +1842,7 @@ export const PromptInput: Component = (props) => { when={attachment.mime.startsWith("image/")} fallback={
- +
} > @@ -1891,7 +1916,7 @@ export const PromptInput: Component = (props) => {
-
+
@@ -1922,12 +1947,17 @@ export const PromptInput: Component = (props) => { title={language.t("command.model.choose")} keybind={command.keybind("model.choose")} > - } @@ -1938,11 +1968,15 @@ export const PromptInput: Component = (props) => { keybind={command.keybind("model.choose")} > - - - - {local.model.current()?.name ?? language.t("dialog.model.select.title")} - + {(open) => ( + <> + + + + {local.model.current()?.name ?? language.t("dialog.model.select.title")} + + + )} @@ -1955,10 +1989,13 @@ export const PromptInput: Component = (props) => { @@ -1972,7 +2009,7 @@ export const PromptInput: Component = (props) => { variant="ghost" onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)} classList={{ - "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true, + "_hidden group-hover/prompt-input:flex items-center justify-center": true, "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory), "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory), }} @@ -1994,7 +2031,7 @@ export const PromptInput: Component = (props) => {
-
+
= (props) => { e.currentTarget.value = "" }} /> -
+
@@ -2036,7 +2074,7 @@ export const PromptInput: Component = (props) => {
{language.t("prompt.action.send")} - +
@@ -2047,7 +2085,7 @@ export const PromptInput: Component = (props) => { disabled={!prompt.dirty() && !working()} icon={working() ? "stop" : "arrow-up"} variant="primary" - class="h-6 w-4.5" + class="h-6 w-5.5" aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")} /> diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index eb46483318..01fd85e146 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -64,8 +64,8 @@ export function SessionContextUsage(props: SessionContextUsageProps) { } const circle = () => ( -
- +
+
) @@ -101,7 +101,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {