fix: align channel runtime and settings UI

This commit is contained in:
lofyer
2026-08-09 21:54:25 +08:00
parent 66b098ae36
commit 1cc969317d
17 changed files with 288 additions and 1528 deletions
+45
View File
@@ -863,6 +863,11 @@ describe('App', () => {
provider: 'model',
profileId: modelProfileId
})
expect(
screen
.getByText('正在连接 Agent Runtime')
.querySelector('.message__status-dot')
).toHaveClass('message__status-dot--active')
const userMessage = screen
.getAllByText('帮我分析项目')
.map((element) => element.closest('article'))
@@ -2179,6 +2184,15 @@ describe('App', () => {
})
expect(await screen.findByText('已取消')).toBeInTheDocument()
const cancelledStatus = screen
.getAllByText('请求已取消')
.find((element) => element.classList.contains('message__status'))
expect(cancelledStatus).toBeDefined()
const cancelledDot = cancelledStatus?.querySelector(
'.message__status-dot'
)
expect(cancelledDot).toHaveClass('message__status-dot')
expect(cancelledDot).not.toHaveClass('message__status-dot--active')
fireEvent.click(screen.getByText('任务与活动'))
expect((await screen.findAllByText('已取消')).length).toBeGreaterThan(0)
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
@@ -2187,6 +2201,37 @@ describe('App', () => {
).toBeInTheDocument()
})
it('keeps persisted completed message statuses static', async () => {
vi.mocked(api.conversations.list).mockResolvedValueOnce([
{
id: '00000000-0000-4000-8000-000000000210',
projectId,
title: '已完成会话',
updatedAt: 1_775_000_000_000,
messages: [
{
id: '00000000-0000-4000-8000-000000000211',
role: 'assistant',
content: '任务结果',
createdAt: 1_775_000_000_000,
state: 'complete',
status: '任务已完成'
}
]
}
])
render(<App />)
const completedStatus = await screen.findByText('任务已完成')
expect(
completedStatus.querySelector('.message__status-dot')
).toHaveClass('message__status-dot')
expect(
completedStatus.querySelector('.message__status-dot')
).not.toHaveClass('message__status-dot--active')
})
it('switches runtime profiles from the composer dropdown', async () => {
render(<App />)
+8 -1
View File
@@ -4925,7 +4925,14 @@ function App(): React.JSX.Element {
: 'message__status'
}
>
<span className="thinking-dot" />
<span
aria-hidden="true"
className={
message.state === 'streaming'
? 'message__status-dot message__status-dot--active'
: 'message__status-dot'
}
/>
{message.status}
</div>
)}
@@ -339,6 +339,50 @@ describe('ChannelSettingsSection', () => {
expect(trigger).toHaveFocus()
})
it('renders disconnecting a configured Weixin binding as a danger action', async () => {
const configuredSnapshot: ChannelSettingsSnapshot = {
...snapshot,
weixin: {
enabled: true,
bindingConfigured: true,
accountDisplay: '微信用户',
source: 'encrypted',
status: { state: 'running' }
}
}
const api = bindingApi()
Object.defineProperty(window, 'goodbuddy', {
configurable: true,
value: {
channels: {
...api,
getSnapshot: vi.fn(async () => configuredSnapshot),
apply: vi.fn(),
testConnection: vi.fn()
},
projects: {
list: vi.fn(async () => projects),
update: vi.fn()
},
settings: settingsApi()
} as unknown as DesktopApi
})
render(<ChannelSettingsSection />)
const disconnect = await screen.findByRole('button', {
name: '断开本机绑定'
})
expect(disconnect).toHaveClass(
'danger-button',
'danger-button--quiet'
)
fireEvent.click(disconnect)
await waitFor(() =>
expect(api.disconnectWeixin).toHaveBeenCalledOnce()
)
})
it('shows Weixin verification failures inside the QR dialog', async () => {
Object.defineProperty(window, 'goodbuddy', {
configurable: true,
@@ -451,6 +495,11 @@ describe('ChannelSettingsSection', () => {
value: agentRuntimeSelectionKey({ provider: 'opencode' })
}
})
expect(
screen.getByText(
'通过 OpenCode Agent Runtime 运行,并跟随“Agent Runtime”设置中的全局 OpenCode 配置。'
)
).toBeInTheDocument()
fireEvent.click(
screen.getByRole('button', { name: '保存通道设置' })
)
+2 -10
View File
@@ -239,15 +239,7 @@ function runtimeSelectionDescription(
}
const runtimeLabel =
selection.provider === 'opencode' ? 'OpenCode' : 'Continue'
const profile =
'profileId' in selection
? settings.modelProfiles.find(
(candidate) => candidate.id === selection.profileId
)
: undefined
return profile
? `通过 ${runtimeLabel} Agent Runtime 运行,并使用 ${profile.name}`
: `通过 ${runtimeLabel} Agent Runtime 及其当前模型配置运行。`
return `通过 ${runtimeLabel} Agent Runtime 运行,并跟随“Agent Runtime”设置中的全局 ${runtimeLabel} 配置。`
}
function ChannelProjectControls({
@@ -887,7 +879,7 @@ function WeixinChannelEditor({
</button>
{settings.bindingConfigured && (
<button
className="danger-ghost"
className="danger-button danger-button--quiet"
disabled={busy}
onClick={onDisconnect}
type="button"
+10 -2
View File
@@ -2816,14 +2816,18 @@ textarea:focus-visible {
color: #ff4d4f;
}
.thinking-dot {
.message__status-dot {
flex: 0 0 auto;
width: 6px;
height: 6px;
margin-top: 4px;
border-radius: 50%;
background: currentColor;
}
.message__status-dot--active {
animation: pulse 1.1s ease-in-out infinite;
background: #1677ff;
background: var(--accent-solid);
}
.tool-execution-list {
@@ -4191,6 +4195,10 @@ details.settings-section > :not(summary) {
margin-left: var(--space-4);
}
details.settings-section > :not(summary) + :not(summary) {
margin-top: var(--space-3);
}
.model-connection-manager {
display: grid;
min-width: 0;