diff --git a/docs/features/assistant-workbar/runtime-native-client-proposal.md b/docs/features/assistant-workbar/runtime-native-client-proposal.md index 638698c9..5ecaf003 100644 --- a/docs/features/assistant-workbar/runtime-native-client-proposal.md +++ b/docs/features/assistant-workbar/runtime-native-client-proposal.md @@ -18,7 +18,7 @@ Status: Local shortcut/coordinator paths and configured-model responses verified | --- | --- | --- | --- | | Continue | 在终端中打开 | 工作栏新终端页签,启动原生 CLI | 本地和托管 SSH 项目 | | OpenCode | 在终端中打开 | 工作栏新终端页签,启动原生 TUI | 本地和托管 SSH 项目 | -| DeepSeek Harness | 在浏览器中打开 | 系统默认浏览器,访问官方 Web 应用 | 本地项目 | +| DeepSeek Harness | 打开DSH Web | 系统默认浏览器,访问官方 Web 应用 | 本地项目 | | 内置模型 | 不显示此快捷入口 | 无 | 保持现状 | DS 采用官方 `@deepseek-ai/dsh` Web 应用,不开发新的 DS 终端前端,也不在首轮嵌入 @@ -164,7 +164,7 @@ DS 后台的 `read` 和原生 `skill` 可在 Ask 执行;`write`、`edit`、`pw ## 5. 界面设计 -输入区下方从左到右排列“在终端中打开/在浏览器中打开”“压缩上下文”和现有用量信息。 +输入区下方从左到右排列“在终端中打开/打开DSH Web”“压缩上下文”和现有用量信息。 两个按钮独立判断可用性。采用同一轻量操作组,避免两组绝对定位相互覆盖;窄输入区允许 操作组换行,保留模式说明、用量信息和对称边距。稳定预留元信息高度的实现应与 [统一 UI 规范](../../../UI-DESIGN.md)一起更新并验证。 @@ -173,8 +173,9 @@ DS 后台的 `read` 和原生 `skill` 可在 Ask 执行;`write`、`edit`、`pw 进入新终端;外部浏览器由系统处理焦点。启动期间保留按钮宽度并显示“正在打开…”,完成后 恢复原文案。缺少项目目录时在相关位置提供选择目录的操作;其他异步失败进入全局通知视口。 -DS 服务运行后,在入口旁提供轻量菜单“重新打开”“停止此服务”;操作指向当前匹配服务。 -若正在生成,停止前说明会中断该服务中的请求。输入区不常驻显示独立会话和服务生命周期说明。不在每次启动时增加确认弹窗。 +DS 服务运行后,打开入口仍显示“打开DSH Web”,旁边显示“停止DSH服务”;操作指向当前匹配服务。 +点击停止后,在同一操作行追加紧凑的“确认”“取消”按钮,不再展开整行警告。窄窗口下确认和取消作为一组换行,两个按钮不拆行。 +确认按钮的悬停提示和无障碍描述说明网页会断开、进行中的请求会中断;执行时由确认按钮显示“正在停止…”。输入区不常驻显示独立会话和服务生命周期说明。不在每次启动时增加确认弹窗。 按钮快捷入口根据所选 Runtime 显示,不改变工作栏“+”中的普通终端和浏览器入口。已有服务 的停止入口应在切换回来后仍可找到。 diff --git a/docs/features/deepseek-harness/progress.md b/docs/features/deepseek-harness/progress.md index 1d5b88b1..42824100 100644 --- a/docs/features/deepseek-harness/progress.md +++ b/docs/features/deepseek-harness/progress.md @@ -82,7 +82,7 @@ Agent 配置、Shell 执行和系统消息接口。 和 Electron UI 测试失败;未取得全量通过结果,未修改这些并行工作的文件。 本次依赖升级回归的真实供应商模型调用为 0 次;后续真实模型及 Web 实测见上节。上述升级验证不包含官方 Web 应用, -“在浏览器中打开”入口仍按[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)实施。 +“打开DSH Web”入口仍按[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)实施。 远程启动契约和资源打包仅支持 Continue、OpenCode,`create-runtime.ts` 拒绝 SSH 执行空间使用 DS,因此本次升级未改变远程 Runtime 路径,也未进行 Linux Host 验证。 diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 10d001d4..a8670d14 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -10677,7 +10677,7 @@ describe("App", () => { await screen.findByRole("textbox", { name: "向 GoodBuddy 提问" }); await waitFor(() => expect(api.settings.getRuntime).toHaveBeenCalled()); expect(screen.queryByRole("button", { name: "在终端中打开" })).not.toBeInTheDocument(); - expect(screen.queryByRole("button", { name: "在浏览器中打开" })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "打开DSH Web" })).not.toBeInTheDocument(); }); it("opens local DS in the external browser without opening the workbar", async () => { @@ -10688,13 +10688,13 @@ describe("App", () => { }]); vi.mocked(api.openRuntimeNativeClient).mockResolvedValue({ kind: "browser", serviceId: "ds-service" }); render(); - const open = await screen.findByRole("button", { name: "在浏览器中打开" }); + const open = await screen.findByRole("button", { name: "打开DSH Web" }); await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledWith({ conversationId })); fireEvent.click(open); await waitFor(() => { expect(api.openRuntimeNativeClient).toHaveBeenCalledWith({ conversationId }); - expect(screen.getByRole("button", { name: "重新打开" })).toBeEnabled(); - expect(screen.getByRole("button", { name: "停止此服务" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "打开DSH Web" })).toBeEnabled(); + expect(screen.getByRole("button", { name: "停止DSH服务" })).toBeEnabled(); }); expect(screen.getByRole("button", { name: "切换助手工作栏" })).toHaveAttribute("aria-expanded", "false"); expect(api.terminal.create).not.toHaveBeenCalled(); diff --git a/src/renderer/src/RuntimeNativeClientActions.test.tsx b/src/renderer/src/RuntimeNativeClientActions.test.tsx index 45c0e531..b5dcd031 100644 --- a/src/renderer/src/RuntimeNativeClientActions.test.tsx +++ b/src/renderer/src/RuntimeNativeClientActions.test.tsx @@ -49,17 +49,17 @@ describe('RuntimeNativeClientActions', () => { it('refreshes the matching DS service on selection changes and stops only that service', async () => { api.getRuntimeNativeClient.mockResolvedValueOnce({ serviceId: 'first-service' }).mockResolvedValue({ serviceId: 'second-service' }) const { rerender } = render() - await screen.findByRole('button', { name: 'Reopen' }) + await screen.findByRole('button', { name: 'Stop DSH service' }) rerender() - expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument() - await screen.findByRole('button', { name: 'Reopen' }) - fireEvent.click(screen.getByRole('button', { name: 'Stop this service' })) + expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument() + await screen.findByRole('button', { name: 'Stop DSH service' }) + fireEvent.click(screen.getByRole('button', { name: 'Stop DSH service' })) expect(api.stopRuntimeNativeClient).not.toHaveBeenCalled() - fireEvent.click(screen.getByRole('button', { name: 'Stop service' })) + fireEvent.click(screen.getByRole('button', { name: 'Confirm' })) await waitFor(() => { expect(api.stopRuntimeNativeClient).toHaveBeenCalledWith({ serviceId: 'second-service' }) - expect(screen.getByRole('button', { name: 'Open in browser' })).toBeEnabled() - expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Open DSH Web' })).toBeEnabled() + expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument() }) }) @@ -68,13 +68,14 @@ describe('RuntimeNativeClientActions', () => { render() await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledTimes(1)) api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'ready-service' }) - fireEvent.click(screen.getByRole('button', { name: 'Open in browser' })) + fireEvent.click(screen.getByRole('button', { name: 'Open DSH Web' })) await waitFor(() => { expect(props.notify).toHaveBeenCalledWith({ tone: 'error', message: 'Browser could not open' }) - expect(screen.getByRole('button', { name: 'Reopen' })).toBeEnabled() + expect(screen.getByRole('button', { name: 'Open DSH Web' })).toBeEnabled() + expect(screen.getByRole('button', { name: 'Stop DSH service' })).toBeEnabled() }) api.openRuntimeNativeClient.mockResolvedValue({ kind: 'browser', serviceId: 'ready-service' }) - fireEvent.click(screen.getByRole('button', { name: 'Reopen' })) + fireEvent.click(screen.getByRole('button', { name: 'Open DSH Web' })) await waitFor(() => expect(api.openRuntimeNativeClient).toHaveBeenCalledTimes(2)) expect(props.onTerminal).not.toHaveBeenCalled() }) @@ -87,18 +88,39 @@ describe('RuntimeNativeClientActions', () => { rerender() await waitFor(() => expect(api.getRuntimeNativeClient).toHaveBeenCalledTimes(2)) await act(async () => finish({ serviceId: 'old-service' })) - expect(screen.queryByRole('button', { name: 'Stop this service' })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'Stop DSH service' })).not.toBeInTheDocument() }) it('retains controls and reports stop errors for retry', async () => { api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'service' }) api.stopRuntimeNativeClient.mockRejectedValueOnce(new Error('Stop failed')) render() - fireEvent.click(await screen.findByRole('button', { name: 'Stop this service' })) - fireEvent.click(screen.getByRole('button', { name: 'Stop service' })) + fireEvent.click(await screen.findByRole('button', { name: 'Stop DSH service' })) + fireEvent.click(screen.getByRole('button', { name: 'Confirm' })) await waitFor(() => { expect(props.notify).toHaveBeenCalledWith({ tone: 'error', message: 'Stop failed' }) - expect(screen.getByRole('button', { name: 'Stop service' })).toBeEnabled() + expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() }) }) + + it('cancels inline confirmation and shows stopping on the confirmation button', async () => { + await changeUiLocale('zh-CN') + api.getRuntimeNativeClient.mockResolvedValue({ serviceId: 'service' }) + let finish!: () => void + api.stopRuntimeNativeClient.mockImplementation(() => new Promise(resolve => { finish = resolve })) + render() + fireEvent.click(await screen.findByRole('button', { name: '停止DSH服务' })) + expect(screen.getByRole('button', { name: '确认' })).toHaveAccessibleDescription('停止后此服务的网页会断开,进行中的请求将中断。') + fireEvent.click(screen.getByRole('button', { name: '取消' })) + expect(screen.queryByRole('button', { name: '确认' })).not.toBeInTheDocument() + expect(api.stopRuntimeNativeClient).not.toHaveBeenCalled() + fireEvent.click(screen.getByRole('button', { name: '停止DSH服务' })) + fireEvent.click(screen.getByRole('button', { name: '确认' })) + expect(screen.getByRole('button', { name: '正在停止…' })).toBeDisabled() + expect(screen.getByRole('button', { name: '打开DSH Web' })).toBeDisabled() + expect(screen.getByRole('button', { name: '取消' })).toBeDisabled() + await act(async () => finish()) + expect(screen.getByRole('button', { name: '打开DSH Web' })).toBeEnabled() + expect(screen.queryByRole('button', { name: '取消' })).not.toBeInTheDocument() + }) }) diff --git a/src/renderer/src/RuntimeNativeClientActions.tsx b/src/renderer/src/RuntimeNativeClientActions.tsx index 879cbfe3..25578bde 100644 --- a/src/renderer/src/RuntimeNativeClientActions.tsx +++ b/src/renderer/src/RuntimeNativeClientActions.tsx @@ -86,14 +86,16 @@ export function RuntimeNativeClientActions({ browser, contextKey, conversationId return <> {browser && serviceId && } {serviceId && confirmStop === serviceId && - {t('composer.nativeClient.stopWarning')} - - + + } } diff --git a/src/renderer/src/i18n/locales/en-US/app.ts b/src/renderer/src/i18n/locales/en-US/app.ts index 0d42872e..bdcc1b0c 100644 --- a/src/renderer/src/i18n/locales/en-US/app.ts +++ b/src/renderer/src/i18n/locales/en-US/app.ts @@ -631,8 +631,8 @@ export const app = { compactFailed: 'Context compaction failed' }, nativeClient: { - terminal: 'Open in terminal', browser: 'Open in browser', opening: 'Opening...', stopping: 'Stopping...', - reopen: 'Reopen', stop: 'Stop this service', confirmStop: 'Stop service', cancel: 'Cancel', + terminal: 'Open in terminal', browser: 'Open DSH Web', opening: 'Opening...', stopping: 'Stopping...', + stop: 'Stop DSH service', confirmStop: 'Confirm', cancel: 'Cancel', description: 'Independent session. Closing the page keeps the service running; stop it here or quit GoodBuddy.', stopWarning: 'Stopping disconnects this service and interrupts its requests.', stopped: 'Native client service stopped', failed: 'Could not open the native client' diff --git a/src/renderer/src/i18n/locales/zh-CN/app.ts b/src/renderer/src/i18n/locales/zh-CN/app.ts index 5e51c04f..d2f78101 100644 --- a/src/renderer/src/i18n/locales/zh-CN/app.ts +++ b/src/renderer/src/i18n/locales/zh-CN/app.ts @@ -607,8 +607,8 @@ export const app = { compactFailed: '上下文压缩失败' }, nativeClient: { - terminal: '在终端中打开', browser: '在浏览器中打开', opening: '正在打开…', stopping: '正在停止…', - reopen: '重新打开', stop: '停止此服务', confirmStop: '停止服务', cancel: '取消', + terminal: '在终端中打开', browser: '打开DSH Web', opening: '正在打开…', stopping: '正在停止…', + stop: '停止DSH服务', confirmStop: '确认', cancel: '取消', description: '独立会话;关闭网页后服务仍运行,可在此停止,退出 GoodBuddy 时停止。', stopWarning: '停止后此服务的网页会断开,进行中的请求将中断。', stopped: '原生客户端服务已停止', failed: '无法打开原生客户端' diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css index fd3d700a..45ed1cd3 100644 --- a/src/renderer/src/styles.css +++ b/src/renderer/src/styles.css @@ -8871,19 +8871,23 @@ button > svg { min-width: 0; } -.composer-native-client-note, -.composer-native-client-confirm { +.composer-native-client-note { flex-basis: 100%; color: var(--text-muted); } .composer-native-client-confirm { - display: flex; - flex-wrap: wrap; + display: inline-flex; + flex-wrap: nowrap; align-items: center; gap: var(--space-2); } +.composer-native-client-confirm .composer-context-compact { + min-width: 0; + white-space: nowrap; +} + .composer-meta__error { margin-left: auto; }