From 4ad8116ce37a0e77e7f3c0e9e4e1002bba05b15e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:05:08 -0500 Subject: fix(app): model selection persist by session (#17348) --- .../src/components/dialog-select-model-unpaid.tsx | 12 +- .../app/src/components/dialog-select-model.tsx | 19 ++- packages/app/src/components/prompt-input.tsx | 155 +++++++++++---------- .../app/src/components/prompt-input/submit.test.ts | 12 ++ packages/app/src/components/prompt-input/submit.ts | 3 +- 5 files changed, 116 insertions(+), 85 deletions(-) (limited to 'packages/app/src/components') diff --git a/packages/app/src/components/dialog-select-model-unpaid.tsx b/packages/app/src/components/dialog-select-model-unpaid.tsx index bcee3f501..2106b3a01 100644 --- a/packages/app/src/components/dialog-select-model-unpaid.tsx +++ b/packages/app/src/components/dialog-select-model-unpaid.tsx @@ -13,8 +13,10 @@ import { DialogSelectProvider } from "./dialog-select-provider" import { ModelTooltip } from "./model-tooltip" import { useLanguage } from "@/context/language" -export const DialogSelectModelUnpaid: Component = () => { - const local = useLocal() +type ModelState = ReturnType["model"] + +export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props) => { + const model = props.model ?? useLocal().model const dialog = useDialog() const providers = useProviders() const language = useLanguage() @@ -35,8 +37,8 @@ export const DialogSelectModelUnpaid: Component = () => { (listRef = ref)} - items={local.model.list} - current={local.model.current()} + items={model.list} + current={model.current()} key={(x) => `${x.provider.id}:${x.id}`} itemWrapper={(item, node) => ( { )} onSelect={(x) => { - local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { + model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { recent: true, }) dialog.close() diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 9f7afb8cd..3654aab85 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -18,19 +18,22 @@ import { useLanguage } from "@/context/language" const isFree = (provider: string, cost: { input: number } | undefined) => provider === "opencode" && (!cost || cost.input === 0) +type ModelState = ReturnType["model"] + const ModelList: Component<{ provider?: string class?: string onSelect: () => void action?: JSX.Element + model?: ModelState }> = (props) => { - const local = useLocal() + const model = props.model ?? useLocal().model const language = useLanguage() const models = createMemo(() => - local.model + model .list() - .filter((m) => local.model.visible({ modelID: m.id, providerID: m.provider.id })) + .filter((m) => model.visible({ modelID: m.id, providerID: m.provider.id })) .filter((m) => (props.provider ? m.provider.id === props.provider : true)), ) @@ -41,7 +44,7 @@ const ModelList: Component<{ emptyMessage={language.t("dialog.model.empty")} key={(x) => `${x.provider.id}:${x.id}`} items={models} - current={local.model.current()} + current={model.current()} filterKeys={["provider.name", "name", "id"]} sortBy={(a, b) => a.name.localeCompare(b.name)} groupBy={(x) => x.provider.name} @@ -63,7 +66,7 @@ const ModelList: Component<{ )} onSelect={(x) => { - local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { + model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, { recent: true, }) props.onSelect() @@ -88,6 +91,7 @@ type ModelSelectorTriggerProps = Omit, "a export function ModelSelectorPopover(props: { provider?: string + model?: ModelState children?: JSX.Element triggerAs?: ValidComponent triggerProps?: ModelSelectorTriggerProps @@ -151,6 +155,7 @@ export function ModelSelectorPopover(props: { {language.t("dialog.model.select.title")} setStore("open", false)} class="p-1" action={ @@ -184,7 +189,7 @@ export function ModelSelectorPopover(props: { ) } -export const DialogSelectModel: Component<{ provider?: string }> = (props) => { +export const DialogSelectModel: Component<{ provider?: string; model?: ModelState }> = (props) => { const dialog = useDialog() const language = useLanguage() @@ -202,7 +207,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => { } > - dialog.close()} /> + dialog.close()} /> + + } + > - + - } - > + + +
- - - - - - {local.model.current()?.name ?? language.t("dialog.model.select.title")} - - - + (x === "default" ? language.t("common.default") : x)} - onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)} - class="capitalize max-w-[160px] text-text-base" - valueClass="truncate text-13-regular text-text-base" - triggerStyle={control()} - variant="ghost" - /> - +
= [] const optimisticSeeded: boolean[] = [] const storedSessions: Record> = {} +const promoted: Array<{ directory: string; sessionID: string }> = [] const sentShell: string[] = [] const syncedDirectories: string[] = [] @@ -86,6 +87,11 @@ beforeAll(async () => { agent: { current: () => ({ name: "agent" }), }, + session: { + promote(directory: string, sessionID: string) { + promoted.push({ directory, sessionID }) + }, + }, }), })) @@ -201,6 +207,7 @@ beforeEach(() => { enabledAutoAccept.length = 0 optimistic.length = 0 optimisticSeeded.length = 0 + promoted.length = 0 params = {} sentShell.length = 0 syncedDirectories.length = 0 @@ -240,6 +247,11 @@ describe("prompt submit worktree selection", () => { expect(createdSessions).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"]) + expect(promoted).toEqual([ + { directory: "/repo/worktree-a", sessionID: "session-1" }, + { directory: "/repo/worktree-b", sessionID: "session-2" }, + ]) + expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"]) }) test("applies auto-accept to newly created sessions", async () => { diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index e8d765cd9..ba299fe36 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -296,6 +296,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { const currentModel = local.model.current() const currentAgent = local.agent.current() + const variant = local.model.variant.current() if (!currentModel || !currentAgent) { showToast({ title: language.t("prompt.toast.modelAgentRequired.title"), @@ -370,6 +371,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { seed(sessionDirectory, created) session = created if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory) + local.session.promote(sessionDirectory, session.id) layout.handoff.setTabs(base64Encode(sessionDirectory), session.id) navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`) } @@ -387,7 +389,6 @@ export function createPromptSubmit(input: PromptSubmitInput) { providerID: currentModel.provider.id, } const agent = currentAgent.name - const variant = local.model.variant.current() const context = prompt.context.items().slice() const draft: FollowupDraft = { sessionID: session.id, -- cgit v1.2.3