diff --git a/FEATURES.md b/FEATURES.md index b1a63d4e..5701b3ab 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -141,10 +141,11 @@ records are listed separately and do not introduce another feature status. models, approval policy, budgets, and timeouts in a Project, with reusable templates. - [x] **Application center and navigation**: - Clicking the bottom App Center opens a lightweight upward anchored popup with enabled apps, + The footer shows equal-height Apps and Settings buttons with visible labels and a subtle vertical divider. + Clicking Apps, marked with a downward chevron, opens a lightweight upward anchored popup with enabled apps, regardless of pinning or opening history, and no modal backdrop. Clicking an app row closes the popup; Local Inference Monitor opens a separate modal preserving the workspace, while other apps - open their main content pages. Manage Apps opens searchable cards with settings details; + open their main content pages. Manage Apps uses a LayoutGrid icon and opens Application Center with searchable cards and settings details; layout and interaction rules are defined in the [UI design](./docs/features/application-tool-navigation/ui-design.md). All four application cards support arrow and drag reordering regardless of enablement or pinning. Cards, the launcher, and the filtered sidebar share one persisted order. Knowledge diff --git a/FEATURES.zh-CN.md b/FEATURES.zh-CN.md index ab9ff9b5..39c9eaf8 100644 --- a/FEATURES.zh-CN.md +++ b/FEATURES.zh-CN.md @@ -36,7 +36,7 @@ - [x] **AI 回复与完整会话复制**:已完成的 AI 回复可从消息底部复制不含推理、工具日志和引用元数据的 Markdown 原文;完整会话复制复用同一条经过 Preload/Main 校验的剪贴板路径。 - [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目和所有项目,范围随布局保存;无项目任务在所有项目中标注“未绑定项目”。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记编辑器的 AI 评论栏支持鼠标与键盘调宽;AI 评论栏记住显示状态及宽度,窄窗口下移至编辑器底部。 - [ ] **项目 Agent Space**(规划中):在 Project 中统一角色、知识、Skills/MCP、模型、审批策略、预算和超时,并支持模板复用。 -- [x] **应用中心与导航**:底部应用中心向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻筛选。点击应用行关闭菜单,本机推理监控打开保留底层工作区的独立 Modal,其余应用打开主内容区;底部“管理应用”打开可搜索卡片及设置详情,布局与交互见 [UI 设计](./docs/features/application-tool-navigation/ui-design.md)。四张应用卡片均支持上下移动和拖动排序,不受启用或常驻状态限制;卡片、菜单和经过过滤的侧栏共用一份保存顺序。知识库始终启用并常驻,提供“打开”和排序;监督者、魔法笔记和本机推理监控可设置启用与常驻。监督者沿用智能心跳身份,缺失启用设置时默认关闭,已明确保存的选择保留;关闭后保留计划和历史,拒绝新回顾并隐藏侧栏反馈,在途运行可完成。启用应用不会自动创建计划。旧应用顺序补齐默认项并保留已有相对顺序。设置持久化后的事件同步各入口,包括工具配置写入;重开中心刷新期间锁定修改,新快照覆盖迟到的旧读取。`local-inference` Modal 直接显示服务列表,不展示任务历史或外部连接;向量服务显示独立进程 CPU 与工作集内存,ASR/OCR 说明共享进程无法独立统计的原因。ASR 就绪状态未知,不从活动请求推断运行中。受支持的服务操作保留影响确认,失败后刷新列表并要求重新确认;TTS 显示不可用。取消中的推理在 Worker 确认或退出前仍计入活动任务及服务停止影响。实现已接入生产 App;验收记录分别列出完整 App、组件 fixture、真实本地引擎和剩余平台/安装包覆盖,不以历史失败代替当前全量结果,详见[验证进度](./docs/features/application-tool-navigation/progress.md)。 +- [x] **应用中心与导航**:底部“应用”和“设置”为显示文字的等高按钮,中间使用弱竖分隔线。“应用”带向下箭头,点击后向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻筛选。点击应用行关闭菜单,本机推理监控打开保留底层工作区的独立 Modal,其余应用打开主内容区;底部“管理应用”使用 `LayoutGrid` 图标,打开标题仍为“应用中心”的可搜索卡片及设置详情,布局与交互见 [UI 设计](./docs/features/application-tool-navigation/ui-design.md)。四张应用卡片均支持上下移动和拖动排序,不受启用或常驻状态限制;卡片、菜单和经过过滤的侧栏共用一份保存顺序。知识库始终启用并常驻,提供“打开”和排序;监督者、魔法笔记和本机推理监控可设置启用与常驻。监督者沿用智能心跳身份,缺失启用设置时默认关闭,已明确保存的选择保留;关闭后保留计划和历史,拒绝新回顾并隐藏侧栏反馈,在途运行可完成。启用应用不会自动创建计划。旧应用顺序补齐默认项并保留已有相对顺序。设置持久化后的事件同步各入口,包括工具配置写入;重开中心刷新期间锁定修改,新快照覆盖迟到的旧读取。`local-inference` Modal 直接显示服务列表,不展示任务历史或外部连接;向量服务显示独立进程 CPU 与工作集内存,ASR/OCR 说明共享进程无法独立统计的原因。ASR 就绪状态未知,不从活动请求推断运行中。受支持的服务操作保留影响确认,失败后刷新列表并要求重新确认;TTS 显示不可用。取消中的推理在 Worker 确认或退出前仍计入活动任务及服务停止影响。实现已接入生产 App;验收记录分别列出完整 App、组件 fixture、真实本地引擎和剩余平台/安装包覆盖,不以历史失败代替当前全量结果,详见[验证进度](./docs/features/application-tool-navigation/progress.md)。 - [ ] **私有化应用市场**(规划中):在应用管理基础上提供组织自己的目录与应用分发。当前只管理内置应用,尚无市场浏览或安装能力,详见 [FR-15](./docs/features/application-tool-navigation/prd.md#fr-15-私有化市场与-yaml-交换后续)。 - [ ] **通用助手工作栏与执行空间后续能力**(规划中):在现有工作栏和多终端基础上继续加入更完整的执行监督、统一 Runtime 监控、受管进程、可固定目标的工作区/浏览器/成果实例、底部停靠与独立窗口。Task Center 保持 Task 的单例索引,附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/features/assistant-workbar/prd.md)。 - [ ] **ShareServer Office 协同编辑**(设计中):通过可选 ShareServer 集成 ONLYOFFICE Docs,在助手工作栏中以文件名打开多个文档 Tab,支持 DOCX、XLSX、PPTX 的人工编辑、保存、撤销、源文件冲突保护和后续 AI 选区修改。Office 正文会由所选 ShareServer 和编辑引擎处理;Desktop 不内置或启动 Document Server,未配置服务时不宣称离线编辑。详见[设计文档](./docs/features/office-document-editing/README.md)。 diff --git a/UI-DESIGN.md b/UI-DESIGN.md index 9cd6d89e..0d8c86ce 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -329,7 +329,7 @@ 接受用户本机选择的 PNG、JPEG 或 WebP 内联数据,宽高不超过 `4096 × 4096`、总像素不 超过 1600 万且大小不超过 `512 KB`;不得读取远程 Logo URL。名称或副标题溢出品牌区时 单行截断,不能撑宽或挤压侧栏。 -- 侧栏底部提供应用中心入口,设置按钮独立靠右。点击应用中心向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻状态筛选;点击行关闭菜单,知识库、智能心跳和魔法笔记在主内容区打开页面。本机推理监控从菜单、常驻入口或管理 Modal 打开时均使用独立 Modal,保留底层工作区。菜单底部“管理应用”打开既有整窗管理 Modal,共同应用设置沿用设置 Modal。 +- 侧栏底部提供“应用 / Apps”和“设置 / Settings”两个横向等高 `nav-item` 按钮,均显示图标和文字,中间使用弱竖分隔线。“应用”带向下箭头,点击后向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻状态筛选;点击行关闭菜单,知识库、智能心跳和魔法笔记在主内容区打开页面。本机推理监控从菜单、常驻入口或管理 Modal 打开时均使用独立 Modal,保留底层工作区。菜单底部“管理应用”使用 `LayoutGrid` 图标,打开既有整窗管理 Modal,标题仍为“应用中心 / Application Center”,共同应用设置沿用设置 Modal。 四个内置应用的管理列表、菜单和侧栏共用保存顺序,菜单按启用过滤,侧栏按启用且常驻过滤。知识库、智能心跳始终启用并常驻,可调整位置;管理 Modal 提供“打开”和排序,不提供启用、常驻开关或通用空设置,标签为“始终显示”(英文 `Always shown`)。原工作区配置及心跳单条计划 enabled 保持不变。只有魔法笔记、本机推理监控可修改启用和常驻,取消常驻不关闭功能。当前对话、运行记录和设置保持固定系统位置,不参与应用排序。 设置持久化后的事件同步各入口及工具配置写入;重开中心刷新期间锁定修改,新快照不被旧读取覆盖。文件、终端、浏览器和资源监控只通过右侧工作栏访问,不在左侧复制入口。 管理 Modal 始终使用紧凑单列横向列表,纵向对应保存顺序;左侧为图标、名称及简短用途,右侧为打开、可用的设置及上下移动操作,行间使用弱分隔线,不加卡片外框。窄窗口操作换行且不溢出,保留共享键盘焦点环。可选应用关闭时禁用“打开”。启用、常驻位于紧凑设置详情;四行均提供上下移动及拖动排序,不受启用或常驻影响,箭头按完整顺序判断边界。搜索时禁用拖动,箭头仍按完整顺序移动,不丢失未命中项。尺寸、返回与滚动重置规则见[应用中心 UI 设计](./docs/features/application-tool-navigation/ui-design.md)。已有稳定设置入口时,不在顶栏重复提供同一入口。 diff --git a/docs/features/application-tool-navigation/README.md b/docs/features/application-tool-navigation/README.md index 34cee749..972d3037 100644 --- a/docs/features/application-tool-navigation/README.md +++ b/docs/features/application-tool-navigation/README.md @@ -1,7 +1,7 @@ # 应用导航与系统工具入口 本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,四个内置应用共用可调整的保存顺序,知识库始终启用并常驻; -智能心跳、魔法笔记和本机推理监控可通过应用管理控制启用和常驻。底部应用中心向上展开轻量锚定菜单,列出已启用应用;底部“管理应用”打开既有管理 Modal。系统工具由右侧工作栏 Tab 承载。应用商店是后续功能,首期不提供 +智能心跳、魔法笔记和本机推理监控可通过应用管理控制启用和常驻。底部“应用”向上展开轻量锚定菜单,列出已启用应用;底部“管理应用”打开既有“应用中心”管理 Modal。系统工具由右侧工作栏 Tab 承载。应用商店是后续功能,首期不提供 商店入口、推荐位或安装流程。 当前分期见 [PRD 第 8 节](./prd.md#8-分期),实施步骤见[技术设计第 11 节](./technical-design.md#11-实施顺序)。 diff --git a/docs/features/application-tool-navigation/prd.md b/docs/features/application-tool-navigation/prd.md index fe1756cc..e7c6f27e 100644 --- a/docs/features/application-tool-navigation/prd.md +++ b/docs/features/application-tool-navigation/prd.md @@ -86,7 +86,7 @@ ### FR-5 应用中心入口 -侧栏底部提供应用中心和独立设置入口。点击应用中心向上展开锚定按钮的轻量菜单,无模态遮罩。菜单列出已启用应用,不按是否打开过、历史记录或常驻状态筛选;点击应用行关闭菜单,本机推理监控打开 Modal,其余应用在主内容区打开页面。 +侧栏底部提供“应用”(英文 `Apps`)和独立“设置”入口,两个按钮均显示文字。点击“应用”向上展开锚定按钮的轻量菜单,无模态遮罩。菜单列出已启用应用,不按是否打开过、历史记录或常驻状态筛选;点击应用行关闭菜单,本机推理监控打开 Modal,其余应用在主内容区打开页面。 菜单底部“管理应用”关闭菜单并打开既有管理 Modal,在其中浏览、搜索全部内置应用,为已关闭的可选应用提供设置与重新开启入口。无搜索结果时显示空状态并可清除搜索。 首期不展示商店、安装、推荐或系统工具清单。入口具有可访问名称,Escape 关闭后恢复触发位置的焦点。 diff --git a/docs/features/application-tool-navigation/technical-design.md b/docs/features/application-tool-navigation/technical-design.md index b073d8a5..bd737ac9 100644 --- a/docs/features/application-tool-navigation/technical-design.md +++ b/docs/features/application-tool-navigation/technical-design.md @@ -175,17 +175,21 @@ Main 继续校验可信 sender、Zod 输入,复用 `ApplicationSettingsStore` 原 `user-card` 整行设置按钮已替换为容器和相邻独立按钮: ```tsx -
- -
``` -应用中心和设置为相邻独立按钮。设置按钮继续调用 `navigateFromSidebar('settings', trigger)`, +“应用”(`applications.menuLabel`:中文“应用”、英文 `Apps`)和设置为横向等高独立按钮,使用 `nav-item` 样式和弱竖分隔线;“管理应用”菜单项使用 `LayoutGrid` 图标,管理 Modal 标题保持“应用中心” / `Application Center`。设置按钮继续调用 `navigateFromSidebar('settings', trigger)`, 保留设置预加载、焦点恢复和未保存离开确认。该调用只打开设置 Modal,不更新当前工作区或 KeepAlive 缓存;`settingsOpen` 独立管理打开状态。关闭后卸载设置内容,下次按入口指定的分类 和消息通道重新挂载,避免缓存上次分类覆盖本次直达目标。底层工作区继续挂载,普通关闭不跳回聊天。 diff --git a/docs/features/application-tool-navigation/ui-design.md b/docs/features/application-tool-navigation/ui-design.md index 399aac56..60c1c047 100644 --- a/docs/features/application-tool-navigation/ui-design.md +++ b/docs/features/application-tool-navigation/ui-design.md @@ -32,7 +32,7 @@ ... ──────────────────────── -[应用中心] [设置] +[应用 v] | [设置] ``` “当前对话”和“运行记录”属于固定系统入口,常驻应用位于二者之间。首期应用清单为魔法笔记、 @@ -41,15 +41,15 @@ ## 2. 底部应用中心入口 -底部使用连续平整表面和顶部分隔线,应用中心在前,固定设置靠右,二者为独立按钮: +底部使用连续平整表面和顶部分隔线,“应用”在前,“设置”靠右,二者为横向排列的等高 `nav-item` 独立按钮,中间使用弱竖分隔线: -| 位置 | 图标 | 可见工具提示 | 点击结果 | +| 位置 | 图标 | 可见文字 | 点击结果 | | --- | --- | --- | --- | -| 1 | `Grid2X2` | 应用中心 | 向上展开已启用应用菜单,底部“管理应用”进入管理 Modal | -| 末位 | `Settings` | 设置 | 在当前工作区上打开设置 Modal | +| 1 | `Grid2X2` + `ChevronDown` | 应用 / Apps | 向上展开已启用应用菜单,底部“管理应用”进入管理 Modal | +| 末位 | `Settings` | 设置 / Settings | 在当前工作区上打开设置 Modal | -图标使用项目现有 Lucide 库。应用中心保留文字,设置图标采用统一的 `34px × 34px` 点击区域。 -底部使用共享间距和焦点样式,设置通过弹性空白靠右;不得把整个容器做成设置按钮。 +图标使用项目现有 Lucide 库。两个按钮均保留可见文字,沿用主导航的高度、间距和焦点样式; +“应用”文字后显示向下箭头,菜单仍向上展开。不得把整个容器做成设置按钮。 ### 2.1 状态 @@ -84,7 +84,7 @@ 菜单通过共享 `AnchoredMenu` 挂载到 body,宽度最多 `280px`,距窗口边缘至少 `16px`,与按钮上缘间隔 `8px`;短窗口限制高度并内部滚动,滚动、窗口和内容尺寸变化时重算位置。原生浏览器相交遮挡复用共享机制。 -管理 Modal 沿用设置 Modal 的表面、遮罩和焦点规则,提供全部内置应用的名称及用途搜索、打开,以及可选应用的常驻和设置: +“管理应用”菜单项使用 `LayoutGrid` 图标。管理 Modal 标题仍为“应用中心”(英文 `Application Center`),沿用设置 Modal 的表面、遮罩和焦点规则,提供全部内置应用的名称及用途搜索、打开,以及可选应用的常驻和设置: 应用管理始终采用紧凑单列横向列表,从上到下对应保存顺序;行间使用 `--border-subtle` 弱分隔线,不加卡片外框。概览与设置详情共用 `640px` 最大宽度,窄窗口保留两侧各 `16px` 外边距,高度随内容增长并受窗口限制。 每行左侧显示图标、名称、状态和简短用途,右侧对齐打开、可用的设置及上下移动操作;空间不足时操作组换到下一行并保持右对齐,组内也可换行,上下箭头保持成组。名称旁区分 @@ -189,7 +189,8 @@ CPU 4% · 内存 620 MB · 桌面进程 7 | 对象 | 文案 | | --- | --- | -| 底部入口 | 应用中心 | +| 底部入口 | 应用 / Apps | +| 管理 Modal 标题 | 应用中心 / Application Center | | 菜单底部管理入口 | 管理应用 | | 应用设置开关 | 启用应用 | | 文件系统工具 | 文件 | diff --git a/docs/features/application-tool-navigation/user-stories.md b/docs/features/application-tool-navigation/user-stories.md index 317be0b5..7a1de16d 100644 --- a/docs/features/application-tool-navigation/user-stories.md +++ b/docs/features/application-tool-navigation/user-stories.md @@ -42,7 +42,7 @@ - Given 任意主页面和任意侧栏宽度 - When 用户查看侧栏底部 -- Then 设置与应用中心为始终可见的独立按钮,点击区域不重叠 +- Then “设置”与“应用”为始终显示文字的独立按钮,点击区域不重叠 - And 点击后继续遵守未保存内容的离开确认 ## 3. Epic B:管理应用入口 diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 112db866..52b8d33e 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -6204,7 +6204,7 @@ describe("App", () => { name: "选择知识库,本次已启用 1 个", }), ).toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: '应用中心' })); + fireEvent.click(screen.getByRole('button', { name: '应用' })); fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })); fireEvent.change(screen.getByRole('searchbox'), { target: { value: '知识库' } }); expect(within(screen.getByRole('dialog')).queryByRole('switch')).not.toBeInTheDocument(); @@ -13904,7 +13904,7 @@ describe("App", () => { expect(screen.queryByRole("button", { name: "应用设置" })).not.toBeInTheDocument(); expect(api.updates.updateSettings).not.toHaveBeenCalled(); fireEvent.click(screen.getByRole("button", { name: "关闭设置" })); - fireEvent.click(screen.getByRole("button", { name: "应用中心" })); + fireEvent.click(screen.getByRole("button", { name: "应用" })); fireEvent.click(screen.getByRole("menuitem", { name: "管理应用" })); fireEvent.click(screen.getByRole("button", { name: "魔法笔记 应用设置" })); const toggle = await screen.findByRole("switch", { @@ -13942,7 +13942,7 @@ describe("App", () => { await updates.updateSettings({ magicNotesEnabled: true }) render() await screen.findByRole('button', { name: '魔法笔记' }) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) const move = screen.getByRole('button', { name: '下移 知识库' }) await waitFor(() => expect(move).toBeEnabled()) @@ -13962,7 +13962,7 @@ describe("App", () => { expect(within(screen.getByRole('navigation', { name: '主导航' })).getAllByRole('button').map(button => button.textContent)).toEqual([ '对话', '监督者', '知识库', '魔法笔记', '运行记录', ]) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) expect(screen.getAllByRole('menuitem').map(item => item.textContent)).toEqual(['监督者', '本机推理监控', '知识库', '魔法笔记', '管理应用']) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) expect(within(screen.getByRole('dialog', { name: '应用中心' })).getAllByRole('article').map(card => card.querySelector('strong')?.textContent)).toEqual(['监督者', '本机推理监控', '知识库', '魔法笔记']) @@ -13982,7 +13982,7 @@ describe("App", () => { }, } vi.mocked(updates.getSettings).mockResolvedValue({ ...external, applicationNavigation: { ...external.applicationNavigation, order: [...external.applicationNavigation.order] } }) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) fireEvent.click(screen.getByRole('button', { name: '本机推理监控 应用设置' })) const pin = screen.getByRole('switch', { name: '常驻左侧菜单' }) @@ -14014,7 +14014,7 @@ describe("App", () => { await screen.findByRole('button', { name: '监督者' }) const reply = deferred() if (operation === 'read') vi.mocked(updates.getSettings).mockReturnValueOnce(reply.promise) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) fireEvent.click(screen.getByRole('button', { name: '本机推理监控 应用设置' })) const pin = screen.getByRole('switch', { name: '常驻左侧菜单' }) @@ -14181,7 +14181,7 @@ describe("App", () => { fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ })) fireEvent.click(await screen.findByRole('button', { name: '新建笔记' })) fireEvent.change(screen.getByLabelText('笔记标题'), { target: { value: 'Unsaved quick note' } }) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) const center = await screen.findByRole('dialog', { name: '应用中心' }) fireEvent.click(within(center).getByRole('button', { name: /魔法笔记.*设置|设置.*魔法笔记/ })) @@ -14235,7 +14235,7 @@ describe("App", () => { expect(await screen.findByRole('alertdialog')).toBeVisible() expect(screen.queryByRole('button', { name: '关闭设置' })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: '继续编辑' })) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '知识库' })) expect(await screen.findByRole('alertdialog')).toBeVisible() fireEvent.click(screen.getByRole('button', { name: '放弃草稿并切换' })) @@ -14263,7 +14263,7 @@ describe("App", () => { if (entry === 'sidebar') await api.updates!.updateSettings({ applicationNavigation: { ...defaultTestApplicationNavigation, pinned: { ...defaultTestApplicationNavigation.pinned, 'local-inference': true } } }) render() await waitFor(() => expect(api.updates!.getSettings).toHaveBeenCalled()) - const nav = entry === 'sidebar' ? await screen.findByRole('button', { name: '本机推理监控' }) : screen.getByRole('button', { name: '应用中心' }) + const nav = entry === 'sidebar' ? await screen.findByRole('button', { name: '本机推理监控' }) : screen.getByRole('button', { name: '应用' }) if (entry !== 'sidebar') expect(within(screen.getByRole('navigation', { name: '主导航' })).getAllByRole('button').map(button => button.textContent)).toEqual(['对话', '知识库', '监督者', '运行记录']) fireEvent.click(screen.getByRole('button', { name: '知识库' })) const workspace = await screen.findByLabelText('知识工作区') @@ -14271,7 +14271,7 @@ describe("App", () => { nav.focus() fireEvent.click(nav) } else { - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) if (entry === 'menu') fireEvent.click(screen.getByRole('menuitem', { name: '本机推理监控' })) else { fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) @@ -14290,7 +14290,7 @@ describe("App", () => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument() expect(screen.getByLabelText('知识工作区')).toBe(workspace) expect(workspace.closest('.app-shell')).toHaveProperty('inert', false) - expect(entry === 'sidebar' ? nav : screen.getByRole('button', { name: '应用中心' })).toHaveFocus() + expect(entry === 'sidebar' ? nav : screen.getByRole('button', { name: '应用' })).toHaveFocus() }) it('saves Supervisor timeouts and concurrency through application settings and restores them on reopen', async () => { @@ -14342,7 +14342,7 @@ describe("App", () => { render() expect(await screen.findByRole('button', { name: '监督者' })).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) fireEvent.click(screen.getByRole('button', { name: '监督者 应用设置' })) const enable = screen.getByRole('switch', { name: '启用应用' }) @@ -14362,7 +14362,7 @@ describe("App", () => { { name: '监督者' }, ), ).not.toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) expect(screen.queryByRole('menuitem', { name: '监督者' })).not.toBeInTheDocument() expect(api.heartbeats.remove).not.toHaveBeenCalled() expect(await api.heartbeats.list()).toEqual([plan]) @@ -14377,7 +14377,7 @@ describe("App", () => { }) render() const inferenceNav = await screen.findByRole('button', { name: '本机推理监控' }) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) fireEvent.click(screen.getByRole('button', { name: '本机推理监控 应用设置' })) const pin = screen.getByRole('switch', { name: '常驻左侧菜单' }) @@ -14385,11 +14385,14 @@ describe("App", () => { fireEvent.click(pin) await waitFor(() => expect(inferenceNav).not.toBeInTheDocument()) fireEvent.click(screen.getByRole('button', { name: '关闭应用中心' })) - const trigger = screen.getByRole('button', { name: '应用中心' }) + const trigger = screen.getByRole('button', { name: '应用', expanded: false }) expect(trigger).toHaveFocus() + expect(within(screen.getByRole('button', { name: '设置' })).getByText('设置')).toBeVisible() fireEvent.click(trigger) + expect(trigger).toHaveAttribute('aria-expanded', 'true') expect(screen.queryByRole('dialog')).not.toBeInTheDocument() fireEvent.click(screen.getByRole('menuitem', { name: '本机推理监控' })) + expect(trigger).toHaveAttribute('aria-expanded', 'false') const workspace = await screen.findByRole('region', { name: '本机推理服务' }) expect(within(screen.getByRole('navigation', { name: '主导航' })).queryByRole('button', { name: '本机推理监控' })).not.toBeInTheDocument() await act(async () => changed(await api.updates!.updateSettings({ localInferenceEnabled: false }))) @@ -14403,14 +14406,14 @@ describe("App", () => { expect((await api.updates!.getSettings()).applicationNavigation.pinned['local-inference']).toBe(false) await act(async () => changed(await api.updates!.updateSettings({ localInferenceEnabled: false }))) fireEvent.click(screen.getByRole('button', { name: '关闭本机推理监控' })) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) fireEvent.click(screen.getByRole('button', { name: '本机推理监控 应用设置' })) await waitFor(() => expect(screen.getByRole('switch', { name: '启用应用' })).not.toBeDisabled()) fireEvent.click(screen.getByRole('switch', { name: '启用应用' })) await waitFor(() => expect(screen.getByRole('switch', { name: '启用应用' })).toBeChecked()) fireEvent.click(screen.getByRole('button', { name: '关闭应用中心' })) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '本机推理监控' })) expect(await screen.findByRole('region', { name: '本机推理服务' })).toBeVisible() }) @@ -14418,7 +14421,7 @@ describe("App", () => { it('keeps confirmed application preferences and locks edits until an unknown save is read back', async () => { render() await screen.findByRole('button', { name: '知识库' }) - fireEvent.click(screen.getByRole('button', { name: '应用中心' })) + fireEvent.click(screen.getByRole('button', { name: '应用' })) fireEvent.click(screen.getByRole('menuitem', { name: '管理应用' })) const updates = api.updates! fireEvent.click(screen.getByRole('button', { name: '本机推理监控 应用设置' })) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 46eb6a3f..52fee91b 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -9362,10 +9362,12 @@ function App(): React.JSX.Element { }} >