fix: describe saved fixed project models accurately
Projects with a saved fixed Runtime model could incorrectly claim to follow the global Runtime configuration. Restore the fixed-model description while keeping the simplified menu and the ability to return to inherited settings. Model resolution, permissions, and remote Agent behavior are unchanged. Validation: 3911 tests passed, 66 skipped; typecheck, lint, and release-note validation passed. Added bilingual regressions for all three Agent Runtimes. Release note: 项目已固定 Runtime 模型时,现在明确显示所用固定模型,不再误报为跟随全局设置。
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
# 模型连接请求定制进度
|
||||
|
||||
## 2026-09-11:发布前固定模型说明修复
|
||||
|
||||
- 项目已保存固定 Runtime 模型时,恢复显示实际固定模型的帮助文字,保留简化后的菜单;
|
||||
规则见 [会话与项目 Runtime 选择](./ui-design.md#会话与项目-runtime-选择)。
|
||||
- `npm exec vitest run src/renderer/src/ProjectRuntimeSelector.test.tsx`:2 项通过,
|
||||
覆盖中英文下 OpenCode、Continue、DeepSeek Harness 的固定选项及恢复跟随操作。
|
||||
- `npm test`:343 个文件、3,911 项通过,66 项按平台或手工/真实服务条件跳过;
|
||||
`npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 通过。
|
||||
- 本次新增修复仅影响 Renderer 帮助文字,未修改共享模型解析、Desktop-to-Agent 请求、
|
||||
Agent 或 Runtime 启动实现;不需要单独远程修复。真实模型调用 0 次,未运行真实 Host
|
||||
或桌面端到端模型会话。本地未构建或打包,发布候选生产构建由主分支 CI 验证。
|
||||
|
||||
## 2026-09-10:配置准确性修复
|
||||
|
||||
- 通道直连回退修复已按[技术设计第 8 节](./technical-design.md#8-通道直连模型选择修复)
|
||||
|
||||
@@ -37,6 +37,13 @@ OpenCode 与 DeepSeek Harness 不使用该值。
|
||||
- 文本域遵循标准键盘编辑、选择、复制和粘贴行为;保存继续使用设置页唯一的主保存操作。
|
||||
- 焦点边框、字体、间距、颜色和深浅主题全部复用全局语义令牌。
|
||||
|
||||
## 会话与项目 Runtime 选择
|
||||
|
||||
会话与项目选择器只提供直连模型和各 Runtime 入口,不再枚举每个 Runtime 与每个模型的
|
||||
组合;Runtime 模型在系统设置中配置。已保存的固定 Runtime 模型选择继续保留,项目
|
||||
选择器的选项与帮助文字均显示其固定模型,不能误报为跟随全局设置;用户可选择普通
|
||||
Runtime 入口恢复跟随。该展示规则不改变模型解析、远程 Agent 协议或执行权限。
|
||||
|
||||
## Harness 模型来源展示
|
||||
|
||||
DeepSeek Harness 的 `platform` 选项命名为“管理员环境优先,回退 GoodBuddy 兼容连接”,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
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 saved model references', () => {
|
||||
it.each(['zh-CN', 'en-US'])('describes fixed selections accurately in %s without restoring duplicate menus', async (language) => {
|
||||
await i18n.changeLanguage(language)
|
||||
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()
|
||||
for (const provider of ['opencode', 'continue', 'deepseek-harness'] as const) {
|
||||
const runtime = provider === 'opencode' ? 'OpenCode'
|
||||
: provider === 'continue' ? 'Continue' : 'DeepSeek Harness'
|
||||
const props = {
|
||||
ariaLabel: 'Project Runtime', label: 'Runtime', onChange,
|
||||
runtimeSettings: settings
|
||||
}
|
||||
const view = render(<ProjectRuntimeSelector {...props} selection={{ provider, profileId }} />)
|
||||
const select = screen.getByRole('combobox')
|
||||
expect(select).toHaveValue(`${provider}:${profileId}`)
|
||||
expect(screen.getByText(language === 'zh-CN'
|
||||
? `通过 ${runtime} 运行,固定使用 Model A。`
|
||||
: `Run through ${runtime} with the fixed model Model A.`)).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('option').filter((option) =>
|
||||
(option as HTMLOptionElement).value.endsWith(`:${profileId}`))).toHaveLength(2)
|
||||
|
||||
fireEvent.change(select, { target: { value: `${provider}:default` } })
|
||||
expect(onChange).toHaveBeenLastCalledWith({ provider })
|
||||
view.rerender(<ProjectRuntimeSelector {...props} selection={{ provider }} />)
|
||||
expect(select).toHaveValue(`${provider}:default`)
|
||||
expect(screen.queryByText(language === 'zh-CN'
|
||||
? `通过 ${runtime} 运行,固定使用 Model A。`
|
||||
: `Run through ${runtime} with the fixed model Model A.`)).not.toBeInTheDocument()
|
||||
expect(screen.getAllByRole('option').filter((option) =>
|
||||
(option as HTMLOptionElement).value.endsWith(`:${profileId}`))).toHaveLength(1)
|
||||
view.unmount()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -54,6 +54,15 @@ 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
|
||||
})
|
||||
|
||||
@@ -46,6 +46,7 @@ export const integrations = {
|
||||
directDescription:
|
||||
'Process messages directly with {{name}} ({{modelName}}).',
|
||||
defaultDirect: 'Direct · Follow the global default model',
|
||||
fixedRuntimeDescription: 'Run through {{runtime}} with the fixed model {{name}}.',
|
||||
automaticDescription:
|
||||
'Follow the global default Runtime setting.',
|
||||
runtimeDescription:
|
||||
|
||||
@@ -40,6 +40,7 @@ export const integrations = {
|
||||
'直接使用 {{name}}({{modelName}})处理消息。',
|
||||
automaticDescription: '跟随全局 Runtime 默认设置',
|
||||
defaultDirect: '直连 · 跟随全局默认模型',
|
||||
fixedRuntimeDescription: '通过 {{runtime}} 运行,固定使用 {{name}}。',
|
||||
runtimeDescription:
|
||||
'通过 {{runtime}} Agent Runtime 运行,并跟随“Agent Runtime”设置中的全局 {{runtime}} 配置。',
|
||||
defaultMode: '默认模式',
|
||||
|
||||
Reference in New Issue
Block a user