Skip to content

Commit 2002f08

Browse files
authored
fix(prompt): unmount model controls in shell mode (#20886)
1 parent c307505 commit 2002f08

2 files changed

Lines changed: 90 additions & 76 deletions

File tree

packages/app/e2e/prompt/prompt-shell.spec.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ToolPart } from "@opencode-ai/sdk/v2/client"
22
import { test, expect } from "../fixtures"
33
import { withSession } from "../actions"
4+
import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors"
45

56
const isBash = (part: unknown): part is ToolPart => {
67
if (!part || typeof part !== "object") return false
@@ -9,15 +10,6 @@ const isBash = (part: unknown): part is ToolPart => {
910
return "state" in part
1011
}
1112

12-
async function setAutoAccept(page: Parameters<typeof test>[0]["page"], enabled: boolean) {
13-
const button = page.locator('[data-action="prompt-permissions"]').first()
14-
await expect(button).toBeVisible()
15-
const pressed = (await button.getAttribute("aria-pressed")) === "true"
16-
if (pressed === enabled) return
17-
await button.click()
18-
await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false")
19-
}
20-
2113
test("shell mode runs a command in the project directory", async ({ page, project }) => {
2214
test.setTimeout(120_000)
2315

@@ -27,7 +19,12 @@ test("shell mode runs a command in the project directory", async ({ page, projec
2719
await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => {
2820
project.trackSession(session.id)
2921
await project.gotoSession(session.id)
30-
await setAutoAccept(page, true)
22+
const button = page.locator('[data-action="prompt-permissions"]').first()
23+
await expect(button).toBeVisible()
24+
if ((await button.getAttribute("aria-pressed")) !== "true") {
25+
await button.click()
26+
await expect(button).toHaveAttribute("aria-pressed", "true")
27+
}
3128
await project.shell(cmd)
3229

3330
await expect
@@ -57,3 +54,18 @@ test("shell mode runs a command in the project directory", async ({ page, projec
5754
.toEqual(expect.objectContaining({ cwd: project.directory, output: expect.stringContaining("README.md") }))
5855
})
5956
})
57+
58+
test("shell mode unmounts model and variant controls", async ({ page, project }) => {
59+
await project.open()
60+
61+
const prompt = page.locator(promptSelector).first()
62+
await expect(page.locator(promptModelSelector)).toHaveCount(1)
63+
await expect(page.locator(promptVariantSelector)).toHaveCount(1)
64+
65+
await prompt.click()
66+
await page.keyboard.type("!")
67+
68+
await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
69+
await expect(page.locator(promptModelSelector)).toHaveCount(0)
70+
await expect(page.locator(promptVariantSelector)).toHaveCount(0)
71+
})

packages/app/src/components/prompt-input.tsx

Lines changed: 68 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,27 +1480,60 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
14801480
/>
14811481
</TooltipKeybind>
14821482
</div>
1483-
<div data-component="prompt-model-control">
1484-
<Show
1485-
when={providers.paid().length > 0}
1486-
fallback={
1483+
<Show when={store.mode !== "shell"}>
1484+
<div data-component="prompt-model-control">
1485+
<Show
1486+
when={providers.paid().length > 0}
1487+
fallback={
1488+
<TooltipKeybind
1489+
placement="top"
1490+
gutter={4}
1491+
title={language.t("command.model.choose")}
1492+
keybind={command.keybind("model.choose")}
1493+
>
1494+
<Button
1495+
data-action="prompt-model"
1496+
as="div"
1497+
variant="ghost"
1498+
size="normal"
1499+
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
1500+
style={control()}
1501+
onClick={() => {
1502+
void import("@/components/dialog-select-model-unpaid").then((x) => {
1503+
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
1504+
})
1505+
}}
1506+
>
1507+
<Show when={local.model.current()?.provider?.id}>
1508+
<ProviderIcon
1509+
id={local.model.current()?.provider?.id ?? ""}
1510+
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
1511+
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
1512+
/>
1513+
</Show>
1514+
<span class="truncate">
1515+
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
1516+
</span>
1517+
<Icon name="chevron-down" size="small" class="shrink-0" />
1518+
</Button>
1519+
</TooltipKeybind>
1520+
}
1521+
>
14871522
<TooltipKeybind
14881523
placement="top"
14891524
gutter={4}
14901525
title={language.t("command.model.choose")}
14911526
keybind={command.keybind("model.choose")}
14921527
>
1493-
<Button
1494-
data-action="prompt-model"
1495-
as="div"
1496-
variant="ghost"
1497-
size="normal"
1498-
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
1499-
style={control()}
1500-
onClick={() => {
1501-
void import("@/components/dialog-select-model-unpaid").then((x) => {
1502-
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
1503-
})
1528+
<ModelSelectorPopover
1529+
model={local.model}
1530+
triggerAs={Button}
1531+
triggerProps={{
1532+
variant: "ghost",
1533+
size: "normal",
1534+
style: control(),
1535+
class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
1536+
"data-action": "prompt-model",
15041537
}}
15051538
>
15061539
<Show when={local.model.current()?.provider?.id}>
@@ -1514,63 +1547,32 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
15141547
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
15151548
</span>
15161549
<Icon name="chevron-down" size="small" class="shrink-0" />
1517-
</Button>
1550+
</ModelSelectorPopover>
15181551
</TooltipKeybind>
1519-
}
1520-
>
1552+
</Show>
1553+
</div>
1554+
<div data-component="prompt-variant-control">
15211555
<TooltipKeybind
15221556
placement="top"
15231557
gutter={4}
1524-
title={language.t("command.model.choose")}
1525-
keybind={command.keybind("model.choose")}
1558+
title={language.t("command.model.variant.cycle")}
1559+
keybind={command.keybind("model.variant.cycle")}
15261560
>
1527-
<ModelSelectorPopover
1528-
model={local.model}
1529-
triggerAs={Button}
1530-
triggerProps={{
1531-
variant: "ghost",
1532-
size: "normal",
1533-
style: control(),
1534-
class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
1535-
"data-action": "prompt-model",
1536-
}}
1537-
>
1538-
<Show when={local.model.current()?.provider?.id}>
1539-
<ProviderIcon
1540-
id={local.model.current()?.provider?.id ?? ""}
1541-
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
1542-
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
1543-
/>
1544-
</Show>
1545-
<span class="truncate">
1546-
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
1547-
</span>
1548-
<Icon name="chevron-down" size="small" class="shrink-0" />
1549-
</ModelSelectorPopover>
1561+
<Select
1562+
size="normal"
1563+
options={variants()}
1564+
current={local.model.variant.current() ?? "default"}
1565+
label={(x) => (x === "default" ? language.t("common.default") : x)}
1566+
onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)}
1567+
class="capitalize max-w-[160px] text-text-base"
1568+
valueClass="truncate text-13-regular text-text-base"
1569+
triggerStyle={control()}
1570+
triggerProps={{ "data-action": "prompt-model-variant" }}
1571+
variant="ghost"
1572+
/>
15501573
</TooltipKeybind>
1551-
</Show>
1552-
</div>
1553-
<div data-component="prompt-variant-control">
1554-
<TooltipKeybind
1555-
placement="top"
1556-
gutter={4}
1557-
title={language.t("command.model.variant.cycle")}
1558-
keybind={command.keybind("model.variant.cycle")}
1559-
>
1560-
<Select
1561-
size="normal"
1562-
options={variants()}
1563-
current={local.model.variant.current() ?? "default"}
1564-
label={(x) => (x === "default" ? language.t("common.default") : x)}
1565-
onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)}
1566-
class="capitalize max-w-[160px] text-text-base"
1567-
valueClass="truncate text-13-regular text-text-base"
1568-
triggerStyle={control()}
1569-
triggerProps={{ "data-action": "prompt-model-variant" }}
1570-
variant="ghost"
1571-
/>
1572-
</TooltipKeybind>
1573-
</div>
1574+
</div>
1575+
</Show>
15741576
<TooltipKeybind
15751577
placement="top"
15761578
gutter={8}

0 commit comments

Comments
 (0)