diff --git a/CHANGELOG.md b/CHANGELOG.md index 677e7b69..afd5a964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### 变更 +- 组织页顶部蓝色状态栏增加「下载最新源码」主按钮:点击后先打开系统文件夹选择器,再下载最新 openXYOS 主分支源码。不必先打开「组织OS管理」。成功提示保存路径;管理抽屉仍保留同一操作作为次要入口。 - 对话与工作区共用一份列表:侧栏「对话」进入工作区「对话」标签;点开一条才进入聊天画布。工作区侧栏默认打开「项目」标签。 - 用户可见用词对齐:员工 = 已入编部门;同事 = 同一组织里一起聊的人;专家 / 智能助手 = 尚未入编的帮手。个性化「子智能体」改为「专项助手」。群聊选择「同事或智能助手」。 - 桌面未注册的 local guest 也能打开「模型」和「知识库」(第一台电脑免登录)。 diff --git a/dashboard/src/layouts/sidebarNav.test.ts b/dashboard/src/layouts/sidebarNav.test.ts index cb66fe75..fade9f9e 100644 --- a/dashboard/src/layouts/sidebarNav.test.ts +++ b/dashboard/src/layouts/sidebarNav.test.ts @@ -81,8 +81,14 @@ describe("sidebarNav", () => { expect(en.nav.organization).toBe("Organization"); expect(zh.organization.enableModule).toBe("启用组织模块"); expect(zh.organization.manageOs).toBe("组织OS管理"); + expect(zh.organization.downloadSourceBar).toBe("下载最新源码"); + expect(zh.organization.downloadSource).toBe("下载最新 openXYOS 源码"); expect(en.organization.enableModule).toBe("Enable organization module"); expect(en.organization.manageOs).toBe("Organization OS"); + expect(en.organization.downloadSourceBar).toBe("Download latest source"); + expect(en.organization.downloadSource).toBe( + "Download latest openXYOS source", + ); expect(zh.chat.modelPickerManage).toBe("模型管理"); expect(en.chat.modelPickerManage).toBe("Model management"); }); diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json index e6e62758..5d49c14f 100644 --- a/dashboard/src/locales/en.json +++ b/dashboard/src/locales/en.json @@ -109,6 +109,7 @@ "fullscreen": "Fullscreen", "exitFullscreen": "Exit fullscreen", "downloadSource": "Download latest openXYOS source", + "downloadSourceBar": "Download latest source", "downloadSourceHint": "Pick any folder on any drive with the system dialog. FreeOS then downloads the latest openXYOS main-branch source from GitHub.", "downloadSourceDest": "Save to folder", "downloadSourceDone": "Downloaded to {{path}}", diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json index 7d22ec24..f98edf4b 100644 --- a/dashboard/src/locales/zh.json +++ b/dashboard/src/locales/zh.json @@ -109,6 +109,7 @@ "fullscreen": "全屏", "exitFullscreen": "退出全屏", "downloadSource": "下载最新 openXYOS 源码", + "downloadSourceBar": "下载最新源码", "downloadSourceHint": "用系统文件夹选择器任选盘符与目录,FreeOS 会从 GitHub 下载最新 openXYOS 主分支源码。", "downloadSourceDest": "保存到文件夹", "downloadSourceDone": "已下载到 {{path}}", diff --git a/dashboard/src/pages/Organization/Organization.module.less b/dashboard/src/pages/Organization/Organization.module.less index 658dac7b..c291b91e 100644 --- a/dashboard/src/pages/Organization/Organization.module.less +++ b/dashboard/src/pages/Organization/Organization.module.less @@ -71,6 +71,11 @@ background: #fff; } +.statusActions :global(.ant-btn-primary) { + font-weight: 650; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35); +} + .statusActions :global(.ant-btn:hover) { color: #0033ff !important; border-color: #fff !important; diff --git a/dashboard/src/pages/Organization/index.test.tsx b/dashboard/src/pages/Organization/index.test.tsx index a5a4c02e..904094ca 100644 --- a/dashboard/src/pages/Organization/index.test.tsx +++ b/dashboard/src/pages/Organization/index.test.tsx @@ -1,7 +1,8 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi, beforeEach } from "vitest"; import OrganizationPage from "./index"; import { orgModuleApi, type OrgOverview } from "../../api/modules/orgModule"; +import { message } from "../../utils/antdMessage"; const overview: OrgOverview = { enabled: true, @@ -65,7 +66,28 @@ vi.mock("../../hooks/useServerTimezone", () => ({ useServerTimezone: () => "UTC", })); +const pickDesktopFolder = vi.fn(); +const canPickDesktopFolder = vi.fn(); + +vi.mock("../../utils/desktopFolder", () => ({ + pickDesktopFolder: (...args: unknown[]) => pickDesktopFolder(...args), + canPickDesktopFolder: (...args: unknown[]) => canPickDesktopFolder(...args), +})); + +vi.mock("../../utils/antdMessage", () => ({ + message: { success: vi.fn(), error: vi.fn(), info: vi.fn() }, +})); + describe("OrganizationPage", () => { + beforeEach(() => { + canPickDesktopFolder.mockReturnValue(true); + pickDesktopFolder.mockReset(); + vi.mocked(orgModuleApi.overview).mockResolvedValue(overview); + vi.mocked(orgModuleApi.downloadSource).mockReset(); + vi.mocked(message.success).mockReset(); + vi.mocked(message.error).mockReset(); + }); + it("defaults to the local embed and keeps extras behind the two status buttons", async () => { render(); @@ -77,8 +99,12 @@ describe("OrganizationPage", () => { const frame = screen.getByTitle("organization.previewTitle"); expect(frame).toHaveAttribute("src", "http://127.0.0.1:3780/"); + expect(screen.getByTestId("org-download-source")).toHaveTextContent( + "organization.downloadSourceBar", + ); expect(screen.queryByText("organization.assembleAction")).toBeNull(); expect(screen.queryByText("organization.startSidecarAction")).toBeNull(); + expect(screen.queryByTestId("org-download-source-drawer")).toBeNull(); fireEvent.click(screen.getByTestId("org-enable-module")); expect( @@ -91,11 +117,45 @@ describe("OrganizationPage", () => { ).toBeInTheDocument(); expect(screen.getByText("organization.loopTitle")).toBeInTheDocument(); expect( - screen.getAllByText("organization.downloadSource").length, - ).toBeGreaterThan(0); + screen.getByTestId("org-download-source-drawer"), + ).toBeInTheDocument(); expect(screen.queryByTestId("org-preview-blank")).toBeNull(); }); + it("downloads latest source from the status bar after picking a folder", async () => { + pickDesktopFolder.mockResolvedValue("D:\\源码\\openXYOS"); + vi.mocked(orgModuleApi.downloadSource).mockResolvedValue({ + path: "D:\\源码\\openXYOS\\openXYOS-main.zip", + }); + + render(); + fireEvent.click(await screen.findByTestId("org-download-source")); + + await waitFor(() => { + expect(pickDesktopFolder).toHaveBeenCalled(); + expect(orgModuleApi.downloadSource).toHaveBeenCalledWith( + "D:\\源码\\openXYOS", + ); + }); + expect(message.success).toHaveBeenCalledWith( + "organization.downloadSourceDone", + ); + expect(screen.queryByText("organization.assembleTitle")).toBeNull(); + }); + + it("does not download when the folder picker is cancelled", async () => { + pickDesktopFolder.mockResolvedValue(null); + + render(); + fireEvent.click(await screen.findByTestId("org-download-source")); + + await waitFor(() => { + expect(pickDesktopFolder).toHaveBeenCalled(); + }); + expect(orgModuleApi.downloadSource).not.toHaveBeenCalled(); + expect(message.error).not.toHaveBeenCalled(); + }); + it("surfaces a blank-preview error when livez is up but embed origin fails", async () => { vi.mocked(orgModuleApi.overview).mockResolvedValue({ ...overview, diff --git a/dashboard/src/pages/Organization/index.tsx b/dashboard/src/pages/Organization/index.tsx index 288565d0..655ccd86 100644 --- a/dashboard/src/pages/Organization/index.tsx +++ b/dashboard/src/pages/Organization/index.tsx @@ -3,6 +3,7 @@ import { Button, Drawer, Input, Space, Switch, Tag } from "antd"; import { ArrowDownUp, Building2, + Download, ExternalLink, Package, Play, @@ -271,24 +272,6 @@ export default function OrganizationPage() { } }; - const browseSourceDest = async () => { - setPickingFolder(true); - try { - const path = await pickDesktopFolder(); - if (path) { - setSourceDest(path); - } else if (!canPickDesktopFolder()) { - message.info(t("organization.pickFolderFailed")); - } - } catch (err) { - message.error( - err instanceof Error ? err.message : t("organization.pickFolderFailed"), - ); - } finally { - setPickingFolder(false); - } - }; - const downloadSource = async () => { setPickingFolder(true); let dest: string | null = null; @@ -498,6 +481,16 @@ export default function OrganizationPage() {
+ - + )} diff --git a/tests/unit/desktop/test_nsis_uninstall.py b/tests/unit/desktop/test_nsis_uninstall.py index 4f0560c5..f0f0cbe6 100644 --- a/tests/unit/desktop/test_nsis_uninstall.py +++ b/tests/unit/desktop/test_nsis_uninstall.py @@ -321,7 +321,8 @@ def test_organization_embeds_local_openxyos_url() -> None: assert "pickDesktopFolder" in page assert "canPickDesktopFolder" in page assert "resolveOpenxyosSourceDest" in page - assert "browseSourceDest" in page + assert 'data-testid="org-download-source"' in page + assert "void downloadSource()" in page assert "http://127.0.0.1:3780" in page assert " void startSidecar()}" not in page