feat(app): v2 thinking level selector (#30646)
This commit is contained in:
86
packages/app/e2e/regression/prompt-thinking-level.spec.ts
Normal file
86
packages/app/e2e/regression/prompt-thinking-level.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
|
|
||||||
|
const directory = "C:/OpenCode/PromptThinkingLevelRegression"
|
||||||
|
const projectID = "proj_prompt_thinking_level_regression"
|
||||||
|
const sessionID = "ses_prompt_thinking_level_regression"
|
||||||
|
|
||||||
|
test("shows the V2 thinking level control while relevant", async ({ page }) => {
|
||||||
|
await mockOpenCodeServer(page, {
|
||||||
|
directory,
|
||||||
|
project: {
|
||||||
|
id: projectID,
|
||||||
|
worktree: directory,
|
||||||
|
vcs: "git",
|
||||||
|
name: "prompt-thinking-level-regression",
|
||||||
|
time: { created: 1700000000000, updated: 1700000000000 },
|
||||||
|
sandboxes: [],
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
all: [
|
||||||
|
{
|
||||||
|
id: "opencode",
|
||||||
|
name: "OpenCode",
|
||||||
|
models: {
|
||||||
|
"thinking-model": {
|
||||||
|
id: "thinking-model",
|
||||||
|
name: "Thinking Model",
|
||||||
|
limit: { context: 200_000 },
|
||||||
|
variants: { high: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connected: ["opencode"],
|
||||||
|
default: { providerID: "opencode", modelID: "thinking-model" },
|
||||||
|
},
|
||||||
|
sessions: [
|
||||||
|
{
|
||||||
|
id: sessionID,
|
||||||
|
slug: "prompt-thinking-level-regression",
|
||||||
|
projectID,
|
||||||
|
directory,
|
||||||
|
title: "Prompt thinking level regression",
|
||||||
|
version: "dev",
|
||||||
|
time: { created: 1700000000000, updated: 1700000000000 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageMessages: () => ({ items: [] }),
|
||||||
|
})
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
|
const composer = page.locator('[data-component="session-composer"]')
|
||||||
|
const input = composer.locator('[data-component="prompt-input"]')
|
||||||
|
const control = composer.locator('[data-component="prompt-variant-control"]')
|
||||||
|
await expect(composer).toBeVisible()
|
||||||
|
|
||||||
|
await idleComposer(page)
|
||||||
|
await expect(control).toBeHidden()
|
||||||
|
|
||||||
|
await composer.hover()
|
||||||
|
await expect(control).toBeVisible()
|
||||||
|
|
||||||
|
await control.locator('[data-action="prompt-model-variant"]').click()
|
||||||
|
const high = page.getByRole("option", { name: "high" })
|
||||||
|
await expect(high).toBeVisible()
|
||||||
|
await page.mouse.move(0, 0)
|
||||||
|
await expect(control).toBeVisible()
|
||||||
|
await expect(high).toBeVisible()
|
||||||
|
await high.click()
|
||||||
|
|
||||||
|
await idleComposer(page)
|
||||||
|
await input.focus()
|
||||||
|
await expect(control).toBeVisible()
|
||||||
|
|
||||||
|
await idleComposer(page)
|
||||||
|
await expect(control).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function idleComposer(page: Page) {
|
||||||
|
await page.mouse.move(0, 0)
|
||||||
|
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur())
|
||||||
|
}
|
||||||
@@ -277,6 +277,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
draggingType: "image" | "@mention" | null
|
draggingType: "image" | "@mention" | null
|
||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
applyingHistory: boolean
|
applyingHistory: boolean
|
||||||
|
variantOpen: boolean
|
||||||
}>({
|
}>({
|
||||||
popover: null,
|
popover: null,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
@@ -285,6 +286,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
draggingType: null,
|
draggingType: null,
|
||||||
mode: "normal",
|
mode: "normal",
|
||||||
applyingHistory: false,
|
applyingHistory: false,
|
||||||
|
variantOpen: false,
|
||||||
})
|
})
|
||||||
const [picker, setPicker] = createStore({
|
const [picker, setPicker] = createStore({
|
||||||
projectOpen: false,
|
projectOpen: false,
|
||||||
@@ -1101,6 +1103,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
||||||
|
// Check provider variants directly: `variants` also includes the UI-only default option.
|
||||||
|
const showVariantControl = createMemo(() => local.model.variant.list().length > 0)
|
||||||
const accepting = createMemo(() => {
|
const accepting = createMemo(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
|
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
|
||||||
@@ -1571,6 +1575,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<ComposerPickerTrigger state={newProjectTriggerState()} />
|
<ComposerPickerTrigger state={newProjectTriggerState()} />
|
||||||
</Show>
|
</Show>
|
||||||
<ComposerModelControl state={modelControlState()} />
|
<ComposerModelControl state={modelControlState()} />
|
||||||
|
<Show when={store.mode !== "shell" && showVariantControl()}>
|
||||||
|
<div
|
||||||
|
data-component="prompt-variant-control"
|
||||||
|
classList={{
|
||||||
|
"hidden group-hover/prompt-input:block group-focus-within/prompt-input:block":
|
||||||
|
!local.model.variant.current() && !store.variantOpen,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.model.variant.cycle")}
|
||||||
|
keybind={command.keybind("model.variant.cycle")}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
size="normal"
|
||||||
|
options={variants()}
|
||||||
|
current={local.model.variant.current() ?? "default"}
|
||||||
|
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||||
|
onOpenChange={(open) => setStore("variantOpen", open)}
|
||||||
|
onSelect={(value) => {
|
||||||
|
local.model.variant.set(value === "default" ? undefined : value)
|
||||||
|
restoreFocus()
|
||||||
|
}}
|
||||||
|
class="capitalize max-w-[160px] justify-start text-v2-text-text-faint"
|
||||||
|
valueClass="truncate text-[13px] font-[440] leading-5 text-v2-text-text-faint"
|
||||||
|
triggerStyle={control()}
|
||||||
|
triggerProps={{ "data-action": "prompt-model-variant" }}
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</TooltipKeybind>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip placement="top" inactive={!working() && blank()} value={tip()}>
|
<Tooltip placement="top" inactive={!working() && blank()} value={tip()}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -1890,7 +1927,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={variants().length > 2}>
|
<Show when={showVariantControl()}>
|
||||||
<div
|
<div
|
||||||
data-component="prompt-variant-control"
|
data-component="prompt-variant-control"
|
||||||
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
|
||||||
|
|||||||
Reference in New Issue
Block a user