Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

### 变更

- 组织页顶部蓝色状态栏增加「下载最新源码」主按钮:点击后先打开系统文件夹选择器,再下载最新 openXYOS 主分支源码。不必先打开「组织OS管理」。成功提示保存路径;管理抽屉仍保留同一操作作为次要入口。
- 对话与工作区共用一份列表:侧栏「对话」进入工作区「对话」标签;点开一条才进入聊天画布。工作区侧栏默认打开「项目」标签。
- 用户可见用词对齐:员工 = 已入编部门;同事 = 同一组织里一起聊的人;专家 / 智能助手 = 尚未入编的帮手。个性化「子智能体」改为「专项助手」。群聊选择「同事或智能助手」。
- 桌面未注册的 local guest 也能打开「模型」和「知识库」(第一台电脑免登录)。
Expand Down
6 changes: 6 additions & 0 deletions dashboard/src/layouts/sidebarNav.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}}",
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"fullscreen": "全屏",
"exitFullscreen": "退出全屏",
"downloadSource": "下载最新 openXYOS 源码",
"downloadSourceBar": "下载最新源码",
"downloadSourceHint": "用系统文件夹选择器任选盘符与目录,FreeOS 会从 GitHub 下载最新 openXYOS 主分支源码。",
"downloadSourceDest": "保存到文件夹",
"downloadSourceDone": "已下载到 {{path}}",
Expand Down
5 changes: 5 additions & 0 deletions dashboard/src/pages/Organization/Organization.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
66 changes: 63 additions & 3 deletions dashboard/src/pages/Organization/index.test.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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(<OrganizationPage />);

Expand All @@ -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(
Expand All @@ -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(<OrganizationPage />);
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(<OrganizationPage />);
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,
Expand Down
43 changes: 15 additions & 28 deletions dashboard/src/pages/Organization/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Button, Drawer, Input, Space, Switch, Tag } from "antd";
import {
ArrowDownUp,
Building2,
Download,
ExternalLink,
Package,
Play,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -498,6 +481,16 @@ export default function OrganizationPage() {
</span>
</div>
<div className={styles.statusActions}>
<Button
type="primary"
icon={<Download size={14} />}
loading={downloading || pickingFolder}
onClick={() => void downloadSource()}
data-testid="org-download-source"
title={t("organization.downloadSource")}
>
{t("organization.downloadSourceBar")}
</Button>
<Button
type="default"
onClick={() => setDrawer("module")}
Expand Down Expand Up @@ -902,24 +895,18 @@ export default function OrganizationPage() {
{t("organization.downloadSourceHint")}
</p>
<Space wrap style={{ marginTop: 12 }}>
<Space.Compact style={{ minWidth: 320 }}>
{!canPickDesktopFolder() && (
<Input
value={sourceDest}
onChange={(event) => setSourceDest(event.target.value)}
placeholder={t("organization.downloadSourceDest")}
readOnly={canPickDesktopFolder()}
style={{ minWidth: 280 }}
/>
<Button
loading={pickingFolder}
onClick={() => void browseSourceDest()}
>
{t("organization.browseDest")}
</Button>
</Space.Compact>
)}
<Button
type="primary"
loading={downloading || pickingFolder}
onClick={() => void downloadSource()}
data-testid="org-download-source-drawer"
>
{t("organization.downloadSource")}
</Button>
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/desktop/test_nsis_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<iframe" in page
assert "onClick={() => void startSidecar()}" not in page
Expand Down
Loading