From 1274c762e855eadf1b1643cf98d6a87f5e75e456 Mon Sep 17 00:00:00 2001 From: mesalogo Date: Thu, 10 Sep 2026 23:00:01 +0800 Subject: [PATCH] fix: simplify runtime model choices in conversations and projects Remove redundant provider and model combinations from conversation and project Runtime selectors. Runtime model configuration stays in system settings, while saved fixed project selections remain visible for compatibility. Release note: Conversation and project Runtime menus are simpler; change Runtime models in system settings. --- src/renderer/src/App.test.tsx | 4 +- src/renderer/src/App.tsx | 22 --------- .../src/ProjectRuntimeSelector.test.tsx | 43 ------------------ src/renderer/src/ProjectRuntimeSelector.tsx | 45 ++----------------- src/renderer/src/i18n/locales/en-US/app.ts | 2 - .../src/i18n/locales/en-US/integrations.ts | 2 - src/renderer/src/i18n/locales/zh-CN/app.ts | 2 - .../src/i18n/locales/zh-CN/integrations.ts | 2 - 8 files changed, 5 insertions(+), 117 deletions(-) delete mode 100644 src/renderer/src/ProjectRuntimeSelector.test.tsx diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 7c00b55c..787f419f 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -6351,7 +6351,7 @@ describe("App", () => { name: /^OpenCode · 默认模型.*sonnet-5$/u, }), ).toBeInTheDocument(); - expect(within(runtimeMenu).getAllByRole("menuitemradio")).toHaveLength(2); + expect(within(runtimeMenu).getAllByRole("menuitemradio")).toHaveLength(1); expect(within(runtimeMenu).queryByText("直连模型")).not.toBeInTheDocument(); expect( within(runtimeMenu).queryByText("Continue Runtime"), @@ -8539,7 +8539,7 @@ describe("App", () => { expect(openCodeModel).toBeEnabled(); expect(continueModel).toBeEnabled(); expect(deepseekHarness).toBeEnabled(); - expect(screen.getAllByRole("menuitemradio")).toHaveLength(10); + expect(screen.getAllByRole("menuitemradio")).toHaveLength(6); expect(within(runtimeMenu).getAllByRole("separator")).toHaveLength(4); expect(within(runtimeMenu).queryByRole("menu")).not.toBeInTheDocument(); expect( diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 307cf687..fcee3ccb 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -76,8 +76,6 @@ import type { } from "../../shared/contracts"; import { defaultContextCompressionSettings, - isAgentRuntimeModelProtocol, - isDeepSeekHarnessModelProfile, maximumPastedImageBytes, } from "../../shared/contracts"; import { @@ -9802,26 +9800,6 @@ function App(): React.JSX.Element { > {t("runtime.manage")} - {runtimeSettings && ( - <> - {t("runtime.fixedRuntimeModel")} - {(["opencode", "continue", "deepseek-harness"] as const).flatMap((provider) => - runtimeSettings.modelProfiles.filter((profile) => - (!activeProjectUsesManagedSsh || provider === "opencode") && - (provider === "deepseek-harness" ? isDeepSeekHarnessModelProfile(profile) : isAgentRuntimeModelProtocol(profile.protocol)) - ).map((profile) => { - const selection = { provider, profileId: profile.id }; - const selected = activeRuntimeSelectionKey === agentRuntimeSelectionKey(selection); - return ; - }) - )} - - )} )} diff --git a/src/renderer/src/ProjectRuntimeSelector.test.tsx b/src/renderer/src/ProjectRuntimeSelector.test.tsx deleted file mode 100644 index d2d07898..00000000 --- a/src/renderer/src/ProjectRuntimeSelector.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { defaultRuntimeSettings, type RuntimeSettings } from '../../shared/contracts' -import { ProjectRuntimeSelector } from './ProjectRuntimeSelector' -import i18n from './i18n' - -afterEach(cleanup) - -describe('ProjectRuntimeSelector rules', () => { - it('allows fixed Runtime models and switching back to a profile-free reference', async () => { - await i18n.changeLanguage('zh-CN') - const profileId = '00000000-0000-4000-8000-000000000011' - const settings = { - ...defaultRuntimeSettings, - defaultModelProfileId: profileId, - modelProfiles: [{ - id: profileId, name: 'Model A', modelName: 'model-a', - baseUrl: 'https://example.com/v1', protocol: 'openai-chat-completions', - authentication: 'api-key', apiKeyConfigured: true - }] - } as RuntimeSettings - const onChange = vi.fn() - const props = { - ariaLabel: 'Project Runtime', label: 'Runtime', onChange, - runtimeSettings: settings - } - const view = render() - const select = screen.getByRole('combobox') - for (const provider of ['opencode', 'continue', 'deepseek-harness'] as const) { - expect(screen.getByRole('option', { - name: `${provider === 'opencode' ? 'OpenCode' : provider === 'continue' ? 'Continue' : 'DeepSeek Harness'} · Model A` - })).toHaveValue(`${provider}:${profileId}`) - fireEvent.change(select, { target: { value: `${provider}:${profileId}` } }) - expect(onChange).toHaveBeenLastCalledWith({ provider, profileId }) - } - view.rerender() - expect(screen.getByText('通过 OpenCode 运行,固定使用 Model A。')).toBeInTheDocument() - fireEvent.change(select, { target: { value: 'opencode:default' } }) - expect(onChange).toHaveBeenLastCalledWith({ provider: 'opencode' }) - fireEvent.change(select, { target: { value: 'model:default' } }) - expect(onChange).toHaveBeenLastCalledWith({ provider: 'model' }) - }) -}) diff --git a/src/renderer/src/ProjectRuntimeSelector.tsx b/src/renderer/src/ProjectRuntimeSelector.tsx index d944580f..ddb92766 100644 --- a/src/renderer/src/ProjectRuntimeSelector.tsx +++ b/src/renderer/src/ProjectRuntimeSelector.tsx @@ -1,10 +1,6 @@ -import type { TFunction } from 'i18next' +import type { TFunction } from 'i18next' import { useTranslation } from 'react-i18next' -import { - isAgentRuntimeModelProtocol, - isDeepSeekHarnessModelProfile, - type RuntimeSettings -} from '../../shared/contracts' +import type { RuntimeSettings } from '../../shared/contracts' import { agentRuntimeSelectionKey, getRuntimeSelectionForProvider, @@ -58,15 +54,6 @@ function runtimeSelectionDescription( : selection.provider === 'continue' ? 'Continue' : 'DeepSeek Harness' - if (selection.profileId) { - const profile = settings.modelProfiles.find( - (candidate) => candidate.id === selection.profileId - ) - return t('channels.project.fixedRuntimeDescription', { - runtime: runtimeLabel, - name: profile?.name ?? t('channels.project.missingProfile') - }) - } return t('channels.project.runtimeDescription', { runtime: runtimeLabel }) @@ -121,22 +108,9 @@ export function ProjectRuntimeSelector({ ...(selectionMode === 'configured' ? [{ provider: 'auto' as const }] : []), { provider: 'model' } ] - const fixedRuntimeSelections = selectionMode === 'configured' - ? runtimeProviders.flatMap((provider) => - directProfiles.filter((profile) => - provider === 'deepseek-harness' - ? isDeepSeekHarnessModelProfile(profile) - : isAgentRuntimeModelProtocol(profile.protocol) - ).map((profile) => ({ provider, profileId: profile.id })) - ) - : [] const fixedRuntimeSelection = runtimeSelection.provider !== 'model' && - 'profileId' in runtimeSelection && runtimeSelection.profileId && - !fixedRuntimeSelections.some((candidate) => - agentRuntimeSelectionKey(candidate) === agentRuntimeSelectionKey(runtimeSelection) - ) ? runtimeSelection : undefined + 'profileId' in runtimeSelection && runtimeSelection.profileId ? runtimeSelection : undefined const selections = [...inheritedSelections, ...directSelections, ...runtimeSelections, - ...fixedRuntimeSelections, ...(fixedRuntimeSelection ? [fixedRuntimeSelection] : [])] const selectionByKey = new Map( selections.map((candidate) => [ @@ -216,19 +190,6 @@ export function ProjectRuntimeSelector({ ))} - {fixedRuntimeSelections.length > 0 && ( - - {fixedRuntimeSelections.map((candidate) => ( - - ))} - - )} {runtimeSelectionDescription( diff --git a/src/renderer/src/i18n/locales/en-US/app.ts b/src/renderer/src/i18n/locales/en-US/app.ts index 4a30b60e..8384a031 100644 --- a/src/renderer/src/i18n/locales/en-US/app.ts +++ b/src/renderer/src/i18n/locales/en-US/app.ts @@ -210,8 +210,6 @@ export const app = { picker: 'Runtime and model', directModels: 'Direct models', defaultDirect: 'Direct · Follow the global default model', - fixedRuntimeModel: 'Fixed Runtime model', - fixedModel: 'Fixed', deepseekHarnessGroup: 'DeepSeek Harness (Developer preview · OpenAI-compatible)', manage: 'Manage Runtime and model connections', diff --git a/src/renderer/src/i18n/locales/en-US/integrations.ts b/src/renderer/src/i18n/locales/en-US/integrations.ts index dd3564dc..cbad87e2 100644 --- a/src/renderer/src/i18n/locales/en-US/integrations.ts +++ b/src/renderer/src/i18n/locales/en-US/integrations.ts @@ -46,8 +46,6 @@ export const integrations = { directDescription: 'Process messages directly with {{name}} ({{modelName}}).', defaultDirect: 'Direct · Follow the global default model', - fixedRuntimeModels: 'Agent Runtime · Fixed model', - fixedRuntimeDescription: 'Run through {{runtime}} with the fixed model {{name}}.', automaticDescription: 'Follow the global default Runtime setting.', runtimeDescription: diff --git a/src/renderer/src/i18n/locales/zh-CN/app.ts b/src/renderer/src/i18n/locales/zh-CN/app.ts index 344e62d7..dce86666 100644 --- a/src/renderer/src/i18n/locales/zh-CN/app.ts +++ b/src/renderer/src/i18n/locales/zh-CN/app.ts @@ -197,8 +197,6 @@ export const app = { picker: 'Runtime 和模型', directModels: '直连模型', defaultDirect: '直连 · 跟随全局默认模型', - fixedRuntimeModel: '固定 Runtime 模型', - fixedModel: '固定', deepseekHarnessGroup: 'DeepSeek Harness(开发者预览 · OpenAI 兼容)', manage: '管理 Runtime 和模型连接', diff --git a/src/renderer/src/i18n/locales/zh-CN/integrations.ts b/src/renderer/src/i18n/locales/zh-CN/integrations.ts index c8242960..b82d1e34 100644 --- a/src/renderer/src/i18n/locales/zh-CN/integrations.ts +++ b/src/renderer/src/i18n/locales/zh-CN/integrations.ts @@ -40,8 +40,6 @@ export const integrations = { '直接使用 {{name}}({{modelName}})处理消息。', automaticDescription: '跟随全局 Runtime 默认设置', defaultDirect: '直连 · 跟随全局默认模型', - fixedRuntimeModels: 'Agent Runtime · 固定模型', - fixedRuntimeDescription: '通过 {{runtime}} 运行,固定使用 {{name}}。', runtimeDescription: '通过 {{runtime}} Agent Runtime 运行,并跟随“Agent Runtime”设置中的全局 {{runtime}} 配置。', defaultMode: '默认模式',