diff --git a/.changeset/web-composer-default-model-switch.md b/.changeset/web-composer-default-model-switch.md new file mode 100644 index 0000000000..e35a51784d --- /dev/null +++ b/.changeset/web-composer-default-model-switch.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +web: The composer model switcher switches the active session's model as before and additionally bumps the global default model, so new sessions inherit the choice. diff --git a/apps/kimi-web/src/App.vue b/apps/kimi-web/src/App.vue index 6c82df9863..22f645ef2c 100644 --- a/apps/kimi-web/src/App.vue +++ b/apps/kimi-web/src/App.vue @@ -338,7 +338,27 @@ function openLogin(): void { async function handleSelectModel(modelId: string): Promise { showModelPicker.value = false; - await client.setModel(modelId); + // Same semantics as the composer dropdown rows: the overlay is just the + // "more models" continuation of the same flow, so it must also bump the + // global default (see handleComposerSelectModel). + await handleComposerSelectModel(modelId); +} + +async function handleComposerSelectModel(modelId: string): Promise { + // Primary action: switch the active session's model via POST /sessions/{id}/profile + // (same as the model picker overlay). Awaited so the model pill reflects the + // result and failures surface. In the onboarding draft this just stores the + // pick for the first session. + const switched = await client.setModel(modelId); + + // Side effect: also bump the daemon-wide default model via POST /config so + // new sessions inherit the choice. Fire-and-forget — it must not block the UI + // or mask the session switch. Only after a confirmed switch (a stale/invalid + // alias must not become the global default), and skip when it already + // matches the default. + if (switched && modelId !== client.defaultModel.value) { + void client.updateConfig({ defaultModel: modelId }); + } } async function handleAddProvider(input: { type: string; apiKey?: string; baseUrl?: string; defaultModel?: string }): Promise { @@ -759,7 +779,7 @@ function openPr(url: string): void { @archive-session="(id) => client.archiveSession(id)" @compact="client.compact()" @pick-model="openModelPicker()" - @select-model="client.setModel($event)" + @select-model="handleComposerSelectModel($event)" @open-file="openFilePreview($event)" @open-media="openMediaPreview($event)" @open-thinking="openThinkingPanel($event)" diff --git a/apps/kimi-web/src/composables/client/useModelProviderState.ts b/apps/kimi-web/src/composables/client/useModelProviderState.ts index 6eb852e1ff..fcd25624f2 100644 --- a/apps/kimi-web/src/composables/client/useModelProviderState.ts +++ b/apps/kimi-web/src/composables/client/useModelProviderState.ts @@ -181,8 +181,12 @@ export function useModelProviderState( * can return model '', so the authoritative current model comes from * GET /sessions/{id}/status, which we re-read right after. Optimistically show * the chosen id meanwhile. Never crashes. + * + * Returns whether the switch was accepted (true for the draft path too), so + * callers can gate follow-up persistence (e.g. bumping the global default) on + * a confirmed switch — errors are surfaced here, not thrown. */ - async function setModel(modelId: string): Promise { + async function setModel(modelId: string): Promise { const sid = rawState.activeSessionId; const nextThinking = coerceThinkingForModel(modelById(modelId), rawState.thinking); const prevThinking = rawState.thinking; @@ -191,7 +195,7 @@ export function useModelProviderState( // Remember the pick — startSessionAndSendPrompt applies it at create time. draftModel.value = modelId; applyThinkingLevel(nextThinking); - return; + return true; } // Optimistic: show the chosen model immediately, but remember the previous // one so we can roll back if the switch never reaches the daemon. @@ -217,12 +221,13 @@ export function useModelProviderState( saveThinkingToStorage(prevThinking); } pushOperationFailure('setModel', err, { sessionId: sid }); - return; + return false; } // refreshSessionStatus folds the authoritative current model from /status // back into the session (the profile echo can return ''). Best-effort: a // failure here does not mean the switch failed, so it must not roll back. await refreshSessionStatus(sid); + return true; } /** Toggle whether a model is starred (favorited) in the model picker. */