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 @@ -8,6 +8,7 @@

### 新增

- Organization Phase 3(设置切片):宿主内 Organization Settings。Dashboard `/organization/settings` 使用共享 `dashboard/src/org-ui` 的 `SettingsPage`。本页只编辑 `org_os` 目录模块开关(复用 `GET/PUT /api/org-module/modules`)与组织本地偏好(`GET /api/org-module/settings` · `PUT /api/org-module/prefs`,`{FREEOS_HOME}/org-os/prefs.json`)。**不是** FreeOS 系统设置:大模型密钥、用户、时区仍链到 `/system-settings`。没有边车,也不复制 sidecar 的 AI/用户/数据库页。`freeos org export-standalone` 同时列出 Settings。未迁:Agents、Chat、Workspace、边车系统设置。
- Organization Phase 3(反思切片):宿主内 Reflections。Dashboard `/organization/reflections` 使用共享 `dashboard/src/org-ui` 的 `ReflectionsPage`。列表/创建/删除走 `/api/org-module/reflections*`,SQLite 在 `{FREEOS_HOME}/org/reflections.sqlite`。可选 `employee_id` / `task_id` 指向同一租户的目录员工与组织任务。这是组织复盘,不是 Chat,也不是第二套技能运行时。`freeos org export-standalone` 同时列出 Reflections。未迁:Chat、边车技能统计、边车部署铁律样例、其余 Open-12。
- Organization Phase 3(任务切片):宿主内 Tasks。Dashboard `/organization/tasks` 与 `/organization/tasks/:id` 使用共享 `dashboard/src/org-ui` 的 `TasksPage` / `TaskDetailPage`。CRUD、状态流转、子任务与人工评论走 `/api/org-module/tasks*`,SQLite 在 `{FREEOS_HOME}/org/tasks.sqlite`。这是组织待办,不是 Octop cron,也不是项目/Chat 对话。`freeos org export-standalone` 同时列出 Tasks。未迁:Chat、附件上传、边车任务库、其余 Open-12。
- Organization Phase 3(知识切片):宿主内 Knowledge。Dashboard `/organization/knowledge` 使用共享 `dashboard/src/org-ui` 的 `KnowledgePage`。列表/详情/笔记走 `/api/org-module/knowledge*`,包装已有 `KnowledgeService`(Octop `knowledge_bases` / `knowledge_documents`),与 Chat 检索同一套行。不克隆 openXYOS sidecar notes/files DB。上传、文件夹、嵌入模型仍在 `/knowledge-bases`。`freeos org export-standalone` 同时列出 Knowledge。未迁:Chat、边车知识库、其余 Open-12。
Expand Down
6 changes: 5 additions & 1 deletion dashboard/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
"navKnowledge": "Knowledge",
"navTasks": "Tasks",
"navReflections": "Reflections",
"navSettings": "Settings",
"announcementsTitle": "Announcements",
"announcementsBody": "Publish notices and track who has read them. This page lives in the host — no sidecar or second port.",
"openAnnouncements": "Open announcements",
Expand All @@ -213,7 +214,10 @@
"openTasks": "Open tasks",
"reflectionsTitle": "Reflections",
"reflectionsBody": "Capture lessons and reusable experience on the host org store (FREEOS_HOME/org). Not a sidecar and not Chat.",
"openReflections": "Open reflections"
"openReflections": "Open reflections",
"settingsTitle": "Organization settings",
"settingsBody": "Org-module catalog toggles and local preferences. LLM keys, users, and timezone stay on FreeOS system settings — no sidecar.",
"openSettings": "Open settings"
},
"systemSettings": {
"subtitle": "Workbench, remote desktop, and host administration in one place."
Expand Down
6 changes: 5 additions & 1 deletion dashboard/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
"navKnowledge": "知识",
"navTasks": "任务",
"navReflections": "反思",
"navSettings": "设置",
"announcementsTitle": "通知公告",
"announcementsBody": "发布公告并跟踪已读。此页跑在宿主内,不需要边车或第二端口。",
"openAnnouncements": "打开通知公告",
Expand All @@ -213,7 +214,10 @@
"openTasks": "打开任务",
"reflectionsTitle": "反思引擎",
"reflectionsBody": "在宿主组织库里整理复盘与经验(FREEOS_HOME/org)。不是边车,也不是对话。",
"openReflections": "打开反思"
"openReflections": "打开反思",
"settingsTitle": "组织设置",
"settingsBody": "组织模块的目录开关与本地偏好。大模型密钥、用户和时区仍在 FreeOS 系统设置,没有边车。",
"openSettings": "打开设置"
},
"systemSettings": {
"subtitle": "工作台、远程桌面与主机管理集中在这里。"
Expand Down
21 changes: 21 additions & 0 deletions dashboard/src/org-ui/api/createClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ describe("createOrgApiClient", () => {
],
};
}
if (path === "/org-module/settings") {
return {
success: true,
data: {
scope: "org_module",
catalog: [{ key: "settings", locked: true }],
modules: { settings: true, chat: false },
prefs: { name: "Acme", description: "" },
system_settings: { overview: "/system-settings" },
not_on_this_page: ["llm_keys"],
},
};
}
if (path === "/org-module/modules") {
return { updates: { settings: true, chat: false } };
}
throw new Error(`unexpected ${path}`);
});
const client = createOrgApiClient({ fetchJson });
Expand All @@ -101,6 +117,11 @@ describe("createOrgApiClient", () => {
expect((await client.reflections.list())[0]?.failure_reasons).toBe(
"const dead zone",
);
expect((await client.settings.snapshot()).prefs.name).toBe("Acme");
expect(await client.settings.saveModules({ chat: false })).toEqual({
settings: true,
chat: false,
});
expect(fetchJson).toHaveBeenCalled();
});

Expand Down
79 changes: 79 additions & 0 deletions dashboard/src/org-ui/api/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,48 @@ export interface OrgReflectionsClient {
remove(id: number): Promise<void>;
}

export interface OrgCapability {
key: string;
label: string;
label_zh: string;
description: string;
description_zh: string;
locked: boolean;
}

export interface OrgPrefs {
name: string;
description: string;
}

export interface OrgSystemSettingsLinks {
overview: string;
models: string;
users: string;
}

export interface OrgSettingsSnapshot {
scope: string;
catalog: OrgCapability[];
modules: Record<string, boolean>;
prefs: OrgPrefs;
system_settings: OrgSystemSettingsLinks;
not_on_this_page: string[];
}

export interface OrgPrefsWrite {
name?: string;
description?: string;
}

export interface OrgSettingsClient {
snapshot(): Promise<OrgSettingsSnapshot>;
savePrefs(body: OrgPrefsWrite): Promise<OrgPrefs>;
saveModules(
updates: Record<string, boolean>,
): Promise<Record<string, boolean>>;
}

export interface OrgApiClient {
announcements: OrgAnnouncementsClient;
org: OrgChartClient;
Expand All @@ -510,6 +552,7 @@ export interface OrgApiClient {
knowledge: OrgKnowledgeClient;
tasks: OrgTasksClient;
reflections: OrgReflectionsClient;
settings: OrgSettingsClient;
}

async function unwrap<T>(payload: OrgEnvelope<T>): Promise<T> {
Expand All @@ -532,6 +575,7 @@ export function createOrgApiClient(opts: {
knowledgePrefix?: string;
tasksPrefix?: string;
reflectionsPrefix?: string;
settingsPrefix?: string;
}): OrgApiClient {
const prefix = opts.announcementsPrefix ?? "/org-module/announcements";
const orgPrefix = opts.orgPrefix ?? "/org-module/org";
Expand All @@ -540,6 +584,7 @@ export function createOrgApiClient(opts: {
const knowledgePrefix = opts.knowledgePrefix ?? "/org-module/knowledge";
const tasksPrefix = opts.tasksPrefix ?? "/org-module/tasks";
const reflectionsPrefix = opts.reflectionsPrefix ?? "/org-module/reflections";
const settingsPrefix = opts.settingsPrefix ?? "/org-module";
const fetchJson = opts.fetchJson;

const announcements: OrgAnnouncementsClient = {
Expand Down Expand Up @@ -985,6 +1030,39 @@ export function createOrgApiClient(opts: {
},
};

const settings: OrgSettingsClient = {
async snapshot() {
return unwrap(
await fetchJson<OrgEnvelope<OrgSettingsSnapshot>>(
`${settingsPrefix}/settings`,
),
);
},
async savePrefs(body) {
return unwrap(
await fetchJson<OrgEnvelope<OrgPrefs>>(`${settingsPrefix}/prefs`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}),
);
},
async saveModules(updates) {
const raw = await fetchJson<{
updates?: Record<string, boolean>;
success?: boolean;
data?: { updates?: Record<string, boolean> };
}>(`${settingsPrefix}/modules`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ updates }),
});
if (raw.updates) return raw.updates;
if (raw.data?.updates) return raw.data.updates;
throw new Error("request failed");
},
};

return {
announcements,
org,
Expand All @@ -994,5 +1072,6 @@ export function createOrgApiClient(opts: {
knowledge,
tasks,
reflections,
settings,
};
}
1 change: 1 addition & 0 deletions dashboard/src/org-ui/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const SHARED_ORG_UI_MODULES = [
"knowledge",
"tasks",
"reflections",
"settings",
] as const;

export type SharedOrgUiModule = (typeof SHARED_ORG_UI_MODULES)[number];
8 changes: 8 additions & 0 deletions dashboard/src/org-ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export type {
OrgReflectionType,
OrgReflectionWrite,
OrgReflectionsClient,
OrgCapability,
OrgPrefs,
OrgPrefsWrite,
OrgSettingsClient,
OrgSettingsSnapshot,
OrgSystemSettingsLinks,
OrgSkill,
OrgSkillCatalogRow,
OrgSkillGenerateResult,
Expand Down Expand Up @@ -75,6 +81,8 @@ export { TaskDetailPage } from "./pages/tasks/TaskDetailPage";
export type { TaskDetailPageProps } from "./pages/tasks/TaskDetailPage";
export { ReflectionsPage } from "./pages/reflections/ReflectionsPage";
export type { ReflectionsPageProps } from "./pages/reflections/ReflectionsPage";
export { SettingsPage } from "./pages/settings/SettingsPage";
export type { SettingsPageProps } from "./pages/settings/SettingsPage";
export type {
IdentityBridge,
OrgFetcher,
Expand Down
152 changes: 152 additions & 0 deletions dashboard/src/org-ui/pages/settings/SettingsPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
.page {
max-width: 880px;
margin: 0 auto;
padding: 8px 4px 32px;
display: flex;
flex-direction: column;
gap: 16px;
}

.header {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}

.titleRow {
display: flex;
align-items: center;
gap: 10px;
}

.title {
margin: 0;
font-size: 20px;
font-weight: 650;
}

.subtitle {
margin: 4px 0 0;
color: var(--fn-text-secondary, #667085);
font-size: 13px;
max-width: 640px;
line-height: 1.5;
}

.hint {
margin: 0;
color: var(--fn-text-secondary, #667085);
font-size: 13px;
line-height: 1.5;
}

.callout {
border: 1px solid var(--fn-border, #e5e7eb);
border-radius: 12px;
padding: 14px 16px;
background: var(--fn-bg, #f9fafb);
display: flex;
flex-direction: column;
gap: 8px;
}

.calloutTitle {
margin: 0;
font-size: 13px;
font-weight: 650;
}

.links {
display: flex;
flex-wrap: wrap;
gap: 8px;
}

.card {
border: 1px solid var(--fn-border, #e5e7eb);
border-radius: 12px;
padding: 16px;
background: var(--fn-bg-container, #fff);
display: flex;
flex-direction: column;
gap: 12px;
}

.cardTitle {
margin: 0;
font-size: 15px;
font-weight: 650;
}

.field {
display: flex;
flex-direction: column;
gap: 6px;
}

.fieldLabel {
font-size: 12px;
font-weight: 600;
color: var(--fn-text-secondary, #667085);
}

.actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}

.status {
font-size: 12px;
color: var(--fn-text-secondary, #667085);
}

.error {
margin: 0;
color: #b42318;
font-size: 13px;
}

.moduleList {
display: flex;
flex-direction: column;
gap: 8px;
}

.moduleRow {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--fn-border, #e5e7eb);
border-radius: 10px;
}

.moduleMeta {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}

.moduleName {
font-size: 13px;
font-weight: 600;
}

.moduleDesc {
font-size: 12px;
color: var(--fn-text-secondary, #667085);
line-height: 1.4;
}

.empty {
text-align: center;
padding: 48px 12px;
color: var(--fn-text-secondary, #667085);
}
Loading
Loading