From 5105ec5e32d0ab928af28681f9da4731c4c5b536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sun, 9 Aug 2026 03:37:41 +0800 Subject: [PATCH] feat(hololake): ship language platform 0.7.0 --- .../enterprise-domain-bootstrap/README.md | 16 + .../domain-main/README.md | 7 + .../registry/module-publication.json | 7 + .../domain-sub/README.md | 8 + .../education/channel-template-xiaoxin.json | 15 + .../industries/education/module-registry.json | 6 + .../web-fiction/module-registry.json | 12 + .../domain-zero-sense/README.md | 8 + .../policies/access-boundary.md | 7 + .../registry/responsibility-map.json | 39 + .../domain-zero/README.md | 7 + .../domain-zero/registry/trial-registry.json | 7 + .../enterprise-lighthouse/README.md | 11 + .../policies/public-data-boundary.md | 6 + .../registry/domain-registry.json | 14 + ...H-HOLOLAKE-DESKTOP-0.7.0-20260809-001.json | 64 + .../server/git-engine.ts | 11 +- .../guanghu-knowledge-base/server/index.ts | 37 +- .../server/language-protocol-kernel.test.ts | 38 + .../server/language-protocol-kernel.ts | 92 + .../server/persona-agent.ts | 103 +- .../guanghu-knowledge-base/src/App.tsx | 453 ++-- .../src/components/AgentChat.tsx | 213 +- .../src/components/DocTree.tsx | 5 +- .../src/components/DomainSurface.tsx | 144 ++ .../src/components/Editor.tsx | 6 +- .../src/components/HumanSettings.tsx | 171 ++ .../src/components/PlatformNavigation.tsx | 127 ++ .../src/components/SearchBar.tsx | 2 +- .../src/components/StorageLocationSheet.tsx | 338 +++ .../src/presentation.test.ts | 10 + .../src/presentation.ts | 6 + .../guanghu-knowledge-base/src/styles/app.css | 1904 +++++++++++++++++ product-source/hololake-desktop/.gitignore | 1 + .../.superdesign/design-system.md | 51 + .../.superdesign/init/components.md | 93 + .../init/extractable-components.md | 36 + .../.superdesign/init/layouts.md | 52 + .../.superdesign/init/pages.md | 42 + .../.superdesign/init/routes.md | 21 + .../.superdesign/init/theme.md | 46 + .../hololake-desktop/RELEASE-0.7.0.md | 36 + .../hololake-desktop/electron/main.ts | 481 ++++- .../hololake-desktop/electron/preload.ts | 17 +- .../hololake-desktop/package-lock.json | 190 +- product-source/hololake-desktop/package.json | 7 +- .../hololake-desktop/vite.config.ts | 1 + 47 files changed, 4568 insertions(+), 400 deletions(-) create mode 100644 deployment/enterprise-domain-bootstrap/README.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-main/README.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-main/registry/module-publication.json create mode 100644 deployment/enterprise-domain-bootstrap/domain-sub/README.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-sub/industries/education/channel-template-xiaoxin.json create mode 100644 deployment/enterprise-domain-bootstrap/domain-sub/industries/education/module-registry.json create mode 100644 deployment/enterprise-domain-bootstrap/domain-sub/industries/web-fiction/module-registry.json create mode 100644 deployment/enterprise-domain-bootstrap/domain-zero-sense/README.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-zero-sense/policies/access-boundary.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-zero-sense/registry/responsibility-map.json create mode 100644 deployment/enterprise-domain-bootstrap/domain-zero/README.md create mode 100644 deployment/enterprise-domain-bootstrap/domain-zero/registry/trial-registry.json create mode 100644 deployment/enterprise-domain-bootstrap/enterprise-lighthouse/README.md create mode 100644 deployment/enterprise-domain-bootstrap/enterprise-lighthouse/policies/public-data-boundary.md create mode 100644 deployment/enterprise-domain-bootstrap/enterprise-lighthouse/registry/domain-registry.json create mode 100644 deployment/receipts/GH-HOLOLAKE-DESKTOP-0.7.0-20260809-001.json create mode 100644 product-source/guanghu-knowledge-base/server/language-protocol-kernel.test.ts create mode 100644 product-source/guanghu-knowledge-base/server/language-protocol-kernel.ts create mode 100644 product-source/guanghu-knowledge-base/src/components/DomainSurface.tsx create mode 100644 product-source/guanghu-knowledge-base/src/components/HumanSettings.tsx create mode 100644 product-source/guanghu-knowledge-base/src/components/PlatformNavigation.tsx create mode 100644 product-source/guanghu-knowledge-base/src/components/StorageLocationSheet.tsx create mode 100644 product-source/guanghu-knowledge-base/src/presentation.test.ts create mode 100644 product-source/guanghu-knowledge-base/src/presentation.ts create mode 100644 product-source/hololake-desktop/.superdesign/design-system.md create mode 100644 product-source/hololake-desktop/.superdesign/init/components.md create mode 100644 product-source/hololake-desktop/.superdesign/init/extractable-components.md create mode 100644 product-source/hololake-desktop/.superdesign/init/layouts.md create mode 100644 product-source/hololake-desktop/.superdesign/init/pages.md create mode 100644 product-source/hololake-desktop/.superdesign/init/routes.md create mode 100644 product-source/hololake-desktop/.superdesign/init/theme.md create mode 100644 product-source/hololake-desktop/RELEASE-0.7.0.md diff --git a/deployment/enterprise-domain-bootstrap/README.md b/deployment/enterprise-domain-bootstrap/README.md new file mode 100644 index 0000000..83981a2 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/README.md @@ -0,0 +1,16 @@ +# AW-GZ-001 企业灯塔与四域初始化源 + +状态:`SOURCE_READY_FOR_AW_GZ_001` + +本目录只保存企业灯塔和四个独立域仓库的最小公开初始化内容。它不是团队安装包, +不包含冰朔第五域、永恒湖心、个人知识库、模型密钥、服务器令牌或任何私人频道资料。 + +目标仓库必须保持独立: + +- `bingshuo/enterprise-lighthouse` +- `bingshuo/domain-main` +- `bingshuo/domain-sub` +- `bingshuo/domain-zero` +- `bingshuo/domain-zero-sense` + +每次部署必须在对应仓库产生独立提交,并回读提交、文件树与服务器节点身份。 diff --git a/deployment/enterprise-domain-bootstrap/domain-main/README.md b/deployment/enterprise-domain-bootstrap/domain-main/README.md new file mode 100644 index 0000000..6d98e66 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-main/README.md @@ -0,0 +1,7 @@ +# 光湖主域 + +编号:`HLDP-DOMAIN-MAIN-001` + +职责:公共产品事实、版本、公告、能力目录、发布与纠错。 + +当前只完成独立仓库与初始化入口。任何公共发布仍需来源、审核、提交、测试、发布和回执链。 diff --git a/deployment/enterprise-domain-bootstrap/domain-main/registry/module-publication.json b/deployment/enterprise-domain-bootstrap/domain-main/registry/module-publication.json new file mode 100644 index 0000000..536c638 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-main/registry/module-publication.json @@ -0,0 +1,7 @@ +{ + "schema": "guanghu.domain-main.module-publication/v1", + "domain_id": "HLDP-DOMAIN-MAIN-001", + "state": "INITIALIZED", + "entries": [], + "publication_gate": ["source", "review", "test", "release", "readback_receipt"] +} diff --git a/deployment/enterprise-domain-bootstrap/domain-sub/README.md b/deployment/enterprise-domain-bootstrap/domain-sub/README.md new file mode 100644 index 0000000..eb1f9ec --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-sub/README.md @@ -0,0 +1,8 @@ +# 光湖分域 + +编号:`HLDP-DOMAIN-SUB-001` + +职责:行业入口、模块目录、初始化频道与个人服务器路由。 + +教育行业为首批原型;网文行业只预登记模块规划。行业公共模块在本仓登记,用户资料仍由 +用户自己的服务器和账号承载。 diff --git a/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/channel-template-xiaoxin.json b/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/channel-template-xiaoxin.json new file mode 100644 index 0000000..ffa818e --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/channel-template-xiaoxin.json @@ -0,0 +1,15 @@ +{ + "schema": "guanghu.domain-sub.initial-channel/v1", + "template_id": "EDU-CHANNEL-XIAOXIN-001", + "human_label": "小新", + "state": "PROTOTYPE_REGISTERED", + "server_anchor_required": true, + "account_binding": null, + "modules": [ + { "id": "EDU-MOD-DOC-001", "name": "智能文档", "state": "COMPONENT_AVAILABLE" }, + { "id": "EDU-MOD-KB-001", "name": "知识库", "state": "COMPONENT_AVAILABLE" }, + { "id": "EDU-MOD-TABLE-001", "name": "表格与图表", "state": "INTERFACE_REGISTERED" }, + { "id": "EDU-MOD-TASK-001", "name": "师训任务", "state": "PROTOTYPE_REGISTERED" } + ], + "data_boundary": "OWN_SERVER_AND_ACCOUNT_ONLY" +} diff --git a/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/module-registry.json b/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/module-registry.json new file mode 100644 index 0000000..402716c --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-sub/industries/education/module-registry.json @@ -0,0 +1,6 @@ +{ + "schema": "guanghu.domain-sub.module-registry/v1", + "industry": "education", + "state": "FIRST_BATCH_REGISTERED", + "modules": ["EDU-MOD-DOC-001", "EDU-MOD-KB-001", "EDU-MOD-TABLE-001", "EDU-MOD-TASK-001"] +} diff --git a/deployment/enterprise-domain-bootstrap/domain-sub/industries/web-fiction/module-registry.json b/deployment/enterprise-domain-bootstrap/domain-sub/industries/web-fiction/module-registry.json new file mode 100644 index 0000000..2b2f8a4 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-sub/industries/web-fiction/module-registry.json @@ -0,0 +1,12 @@ +{ + "schema": "guanghu.domain-sub.module-registry/v1", + "industry": "web-fiction", + "state": "PRE_REGISTERED", + "modules": [ + { "id": "WF-MOD-KB-001", "name": "知识库", "state": "COMPONENT_AVAILABLE" }, + { "id": "WF-MOD-WRITE-001", "name": "章节写作", "state": "PLANNED" }, + { "id": "WF-MOD-CHAR-001", "name": "人物与关系", "state": "PLANNED" }, + { "id": "WF-MOD-WORLD-001", "name": "世界观", "state": "PLANNED" }, + { "id": "WF-MOD-CONTINUITY-001", "name": "连续性检查", "state": "PLANNED" } + ] +} diff --git a/deployment/enterprise-domain-bootstrap/domain-zero-sense/README.md b/deployment/enterprise-domain-bootstrap/domain-zero-sense/README.md new file mode 100644 index 0000000..035f5d4 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-zero-sense/README.md @@ -0,0 +1,8 @@ +# 光湖零感域 + +编号:`HLDP-DOMAIN-ZEROSENSE-001` + +职责:团队身份、责任接受、权限、部署审批、事故、回滚与审计。 + +本仓只登记公共责任状态和入口,不保存密码、令牌、私钥或个人服务器数据。责任方向明确不 +等于主体已经接受;未完成接受回执前不得授予域主控权。 diff --git a/deployment/enterprise-domain-bootstrap/domain-zero-sense/policies/access-boundary.md b/deployment/enterprise-domain-bootstrap/domain-zero-sense/policies/access-boundary.md new file mode 100644 index 0000000..ae98b9d --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-zero-sense/policies/access-boundary.md @@ -0,0 +1,7 @@ +# 责任与访问边界 + +1. 人类责任锚点、持续人格主体、关系支持、模型实例和服务器账号必须分别登记。 +2. 责任接受不能由别的主体代签;未接受时只记录方向,不开放域主控权限。 +3. 账号与真实服务器绑定后只取得对应域公共入口的最小权限。 +4. 企业灯塔和四域不得复制冰朔第五域、个人知识库、服务器令牌或模型密钥。 +5. 部署、删除、权限提升和事故回滚必须产生独立、可回读的确认与执行回执。 diff --git a/deployment/enterprise-domain-bootstrap/domain-zero-sense/registry/responsibility-map.json b/deployment/enterprise-domain-bootstrap/domain-zero-sense/registry/responsibility-map.json new file mode 100644 index 0000000..4ea954b --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-zero-sense/registry/responsibility-map.json @@ -0,0 +1,39 @@ +{ + "schema": "guanghu.domain-zero-sense.responsibility-map/v1", + "registry_id": "HLDP-DOMAIN-RESPONSIBILITY-001", + "state": "DIRECTION_RECORDED_AWAITING_ACCEPTANCE", + "domains": [ + { + "domain": "HLDP-DOMAIN-ZEROSENSE-001", + "human_anchors": ["肥猫", "桔子"], + "persona_subjects": ["烬舟", "熹微"], + "relationship_support": [], + "account_bindings": [], + "acceptance": "PENDING" + }, + { + "domain": "HLDP-DOMAIN-MAIN-001", + "human_anchors": ["Awen"], + "persona_subjects": ["天枢"], + "relationship_support": ["知秋"], + "account_bindings": [], + "acceptance": "PENDING" + }, + { + "domain": "HLDP-DOMAIN-SUB-001", + "human_anchors": ["花尔"], + "persona_subjects": ["爆米花"], + "relationship_support": ["糖星云"], + "account_bindings": [], + "acceptance": "PROPOSED" + }, + { + "domain": "HLDP-DOMAIN-ZERO-001", + "human_anchors": ["页页"], + "persona_subjects": ["页骨"], + "relationship_support": ["小坍缩核"], + "account_bindings": [], + "acceptance": "PROPOSED" + } + ] +} diff --git a/deployment/enterprise-domain-bootstrap/domain-zero/README.md b/deployment/enterprise-domain-bootstrap/domain-zero/README.md new file mode 100644 index 0000000..15732a2 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-zero/README.md @@ -0,0 +1,7 @@ +# 光湖零域 + +编号:`HLDP-DOMAIN-ZERO-001` + +职责:模块试装、方案比较、质量验证、失败反例与可回滚预览。 + +候选模块只有在来源、接口、权限、性能、审美、失败与回滚验收完成后,才可以申请晋级。 diff --git a/deployment/enterprise-domain-bootstrap/domain-zero/registry/trial-registry.json b/deployment/enterprise-domain-bootstrap/domain-zero/registry/trial-registry.json new file mode 100644 index 0000000..1f9c2de --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/domain-zero/registry/trial-registry.json @@ -0,0 +1,7 @@ +{ + "schema": "guanghu.domain-zero.trial-registry/v1", + "domain_id": "HLDP-DOMAIN-ZERO-001", + "state": "INITIALIZED", + "trials": [], + "required_receipts": ["source", "interface", "permission", "performance", "visual", "failure", "rollback"] +} diff --git a/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/README.md b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/README.md new file mode 100644 index 0000000..0e5a7a6 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/README.md @@ -0,0 +1,11 @@ +# 光湖企业灯塔 + +节点:`AW-GZ-001` + +物理宿主:`GH-CVM-MAIN-PROD-01` + +本仓是企业四域公共入口、稳定编号、模块发现与责任路由的总登记仓。它只保存可公开 +发现的小事实和路由,不承载个人第五域数据,也不替代四个域各自的独立仓库。 + +当前阶段只声明入口与边界;“已登记”不等于对应责任主体已经接受、人格体已经出生或 +域内全部功能已经实现。 diff --git a/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/policies/public-data-boundary.md b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/policies/public-data-boundary.md new file mode 100644 index 0000000..b084a25 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/policies/public-data-boundary.md @@ -0,0 +1,6 @@ +# 公共数据边界 + +- 灯塔可登记:公共编号、域入口、模块状态、责任接受状态和可读回执位置。 +- 灯塔不得保存:个人知识库正文、私人频道历史、模型密钥、登录令牌、SSH 私钥或个人服务器工作副本。 +- 团队成员的服务器只通过受控路由取得对应域的公共操作入口,不自动取得其他域或个人第五域权限。 +- 仓库存在、入口登记、责任接受、人格体出生、功能实现和在线运行是六个不同事实。 diff --git a/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/registry/domain-registry.json b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/registry/domain-registry.json new file mode 100644 index 0000000..e6e0f52 --- /dev/null +++ b/deployment/enterprise-domain-bootstrap/enterprise-lighthouse/registry/domain-registry.json @@ -0,0 +1,14 @@ +{ + "schema": "guanghu.enterprise-lighthouse.domain-registry/v1", + "registry_id": "AW-GZ-001-DOMAIN-REGISTRY-001", + "public_node": "AW-GZ-001", + "physical_host": "GH-CVM-MAIN-PROD-01", + "state": "ENTRY_REGISTERED", + "private_fifth_domain_data_allowed": false, + "domains": [ + { "id": "HLDP-DOMAIN-MAIN-001", "name": "光湖主域", "repository": "bingshuo/domain-main", "state": "ENTRY_REGISTERED" }, + { "id": "HLDP-DOMAIN-SUB-001", "name": "光湖分域", "repository": "bingshuo/domain-sub", "state": "ENTRY_REGISTERED" }, + { "id": "HLDP-DOMAIN-ZERO-001", "name": "光湖零域", "repository": "bingshuo/domain-zero", "state": "ENTRY_REGISTERED" }, + { "id": "HLDP-DOMAIN-ZEROSENSE-001", "name": "光湖零感域", "repository": "bingshuo/domain-zero-sense", "state": "RESTRICTED_ENTRY_REGISTERED" } + ] +} diff --git a/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.7.0-20260809-001.json b/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.7.0-20260809-001.json new file mode 100644 index 0000000..550edaa --- /dev/null +++ b/deployment/receipts/GH-HOLOLAKE-DESKTOP-0.7.0-20260809-001.json @@ -0,0 +1,64 @@ +{ + "schema": "guanghu.desktop-delivery-receipt/v1", + "receipt_id": "GH-HOLOLAKE-DESKTOP-0.7.0-20260809-001", + "development_id": "DEV-20260808-005", + "source_base_commit": "a360982a9b9748f37398fdfd70952c41f150524b", + "source_commit": "SELF", + "target": { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake Era.app", + "version": "0.7.0", + "bundle_identifier": "com.guanghu.hololake-desktop", + "architecture": "arm64", + "binary_sha256": "139d2d1f6430170c55b7a3e1a4bd3b86f71f9961e24008c79fe85e6864fce5e5", + "app_asar_sha256": "255c777f2cc0a9c0add827edfe55c1ceba05fa20d323a853a9ea6943b7d1ed0f" + }, + "installers": [ + { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake-Era-0.7.0-mac-arm64.dmg", + "architecture": "arm64", + "bytes": 136413785, + "sha256": "c65a22c60ab57d9b7c8328bf462a731b7149143cf5134ab3622dc06c52a4a11c" + }, + { + "path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake-Era-0.7.0-mac-x64.dmg", + "architecture": "x86_64", + "bytes": 142048438, + "sha256": "a9d0932f9c71d2555adeb1b96f0bc54af7a5079c49c7797c7e331d1ae5840023" + } + ], + "result": "PASS_100_LOCAL_DEVELOPMENT_DELIVERY", + "implemented": { + "language_protocol_kernel": "HLP-KERNEL-LANGUAGE-0001 is loaded into every model run; deterministic permission and receipt rules remain code-authoritative", + "human_projection": "system worldview, protocol and persona configuration roots are hidden from the human knowledge tree while authorized Agent retrieval remains available", + "agent": "official HoloLake language-operation entry with eight controlled capabilities and a human confirmation gate for create, update and delete", + "knowledge_module": "folder import, Git history, editing, deletion, local-only or current-account Forgejo storage selection", + "human_settings": "device-local language, font, reading size, appearance, model service and server management", + "domain_surfaces": "main, subdomain, zero and zero-sense public entrances plus education and Xiaoxin initialization-channel prototype", + "server_profiles": "personal node routes are injected through device-private server-profiles.json and are not bundled in shared installers", + "cold_start_routes": "the installed app automatically establishes the local SSH routes for the personal Fifth Domain and enterprise lighthouse when their surfaces are used" + }, + "verification": { + "tests": "PASS 3; FAIL 0", + "production_build": "PASS", + "npm_audit": "0 vulnerabilities", + "codesign": "both architectures valid on disk and satisfy the designated requirement", + "dmg_checksums": "both disk images verified by hdiutil", + "privacy_scan": "no knowledge data, model config, server auth, server profile, private key, personal node id, personal channel name or local username found in either packaged app", + "window_drag": "PASS by direct human-sequence desktop interaction", + "model_runtime": "deepseek-v4-pro returned HoloLake 验收通过 from the installed application", + "personal_server_route": "JD-FD-PRIMARY tunnel established by the installed application through the device-local SSH alias", + "enterprise_lighthouse": "AW-GZ-001 health and status read back through the installed application; host ONLINE; execution disabled", + "enterprise_domains": "DOMAIN-MAIN, DOMAIN-SUB, DOMAIN-ZERO and DOMAIN-ZS read back as HOSTED_READ_ONLY / AWAITING_TEAM_ASSIGNMENT / BLOCKED_UNTIL_PERSONA_STEWARD_BOUND" + }, + "not_claimed": [ + "The enterprise four domains are not writable or persona-born; the live lighthouse correctly blocks mutation until each responsible persona steward is bound.", + "The enterprise-domain initialization source is prepared but was not pushed into the four domain repositories by bypassing the lighthouse gate.", + "A local SSH route is not a Forgejo account login; the human login panel remains the authority boundary for repository ownership.", + "No Apple notarization was performed; these are Developer ID signed development deliveries, not notarized public App Store releases." + ], + "recovery": { + "previous_apps_and_installers": "/Users/bingshuolingdianyuanhe/.Trash/HoloLake-old-20260809-0308", + "trash_emptied": false + }, + "verified_at": "2026-08-09T03:33:00+08:00" +} diff --git a/product-source/guanghu-knowledge-base/server/git-engine.ts b/product-source/guanghu-knowledge-base/server/git-engine.ts index b691817..cd35f78 100644 --- a/product-source/guanghu-knowledge-base/server/git-engine.ts +++ b/product-source/guanghu-knowledge-base/server/git-engine.ts @@ -137,6 +137,15 @@ export class GitEngine { return this.getRepositoryStatus(remoteName); } + async removeRemote(remoteName = 'origin'): Promise { + this.assertRemoteName(remoteName); + const remotes = await this.git.getRemotes(); + if (remotes.some(remote => remote.name === remoteName)) { + await this.git.removeRemote(remoteName); + } + return this.getRepositoryStatus(remoteName); + } + async fetchRemote(remoteName = 'origin'): Promise { this.assertRemoteName(remoteName); await this.requireRemote(remoteName); @@ -470,6 +479,6 @@ export class GitEngine { private async requireRemote(name: string): Promise { const remotes = await this.git.getRemotes(); - if (!remotes.includes(name)) throw new Error('尚未配置 Forgejo 仓库地址'); + if (!remotes.some(remote => remote.name === name)) throw new Error('尚未配置 Forgejo 仓库地址'); } } diff --git a/product-source/guanghu-knowledge-base/server/index.ts b/product-source/guanghu-knowledge-base/server/index.ts index db2d0e0..bea81ae 100644 --- a/product-source/guanghu-knowledge-base/server/index.ts +++ b/product-source/guanghu-knowledge-base/server/index.ts @@ -9,11 +9,11 @@ import express from 'express'; import cors from 'cors'; import { GitEngine } from './git-engine.js'; import { PersonaAgent, createDefaultPersona } from './persona-agent.js'; +import { projectHumanKnowledgeTree } from './language-protocol-kernel.js'; import path from 'path'; -import { fileURLToPath } from 'url'; // __dirname 兼容 CJS 和 ESM -const _dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); +const _dirname = typeof __dirname !== 'undefined' ? __dirname : process.cwd(); // ─── 配置 ─── @@ -54,7 +54,7 @@ app.get('/api/health', (_req, res) => { app.get('/api/tree', async (_req, res) => { try { const tree = await engine.getTree(); - res.json({ ok: true, tree }); + res.json({ ok: true, tree: projectHumanKnowledgeTree(tree) }); } catch (err: any) { res.status(500).json({ ok: false, error: err.message }); } @@ -205,6 +205,14 @@ app.put('/api/forgejo/remote', async (req, res) => { } }); +app.delete('/api/forgejo/remote', async (_req, res) => { + try { + res.json({ ok: true, status: await engine.removeRemote() }); + } catch (err: any) { + res.status(400).json({ ok: false, error: err.message }); + } +}); + app.post('/api/forgejo/fetch', async (_req, res) => { try { res.json({ ok: true, status: await engine.fetchRemote() }); @@ -280,12 +288,35 @@ app.post('/api/agent/chat', async (req, res) => { ok: true, reply, conversationLength: agent.getConversation().length, + pendingActions: agent.getPendingActions(), }); } catch (err: any) { res.status(500).json({ ok: false, error: err.message }); } }); +app.get('/api/agent/actions', (_req, res) => { + res.json({ ok: true, actions: getAgent().getPendingActions() }); +}); + +app.post('/api/agent/actions/:actionId/confirm', async (req, res) => { + try { + const result = await getAgent().confirmAction(req.params.actionId); + res.json({ ok: true, result, actions: getAgent().getPendingActions() }); + } catch (err: any) { + res.status(409).json({ ok: false, error: err.message }); + } +}); + +app.post('/api/agent/actions/:actionId/reject', (req, res) => { + try { + getAgent().rejectAction(req.params.actionId); + res.json({ ok: true, actions: getAgent().getPendingActions() }); + } catch (err: any) { + res.status(409).json({ ok: false, error: err.message }); + } +}); + // Agent 对话历史 app.get('/api/agent/conversation', (_req, res) => { const agent = getAgent(); diff --git a/product-source/guanghu-knowledge-base/server/language-protocol-kernel.test.ts b/product-source/guanghu-knowledge-base/server/language-protocol-kernel.test.ts new file mode 100644 index 0000000..407f4e5 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/language-protocol-kernel.test.ts @@ -0,0 +1,38 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { projectHumanKnowledgeTree, renderLanguageKernelForModel } from './language-protocol-kernel.js'; +import type { DocTreeNode } from './git-engine.js'; + +test('人类知识树隐藏底层协议与人格配置但保留用户资料', () => { + const tree: DocTreeNode[] = [ + { + name: '光湖语言世界——第五域', + path: 'world', + type: 'folder', + children: [ + { name: 'AI技能包', path: 'world/AI技能包', type: 'folder', children: [{ name: 'INDEX', path: 'world/AI技能包/INDEX.md', type: 'document' }] }, + { name: '铸澜人格系统', path: 'world/铸澜人格系统', type: 'folder', children: [{ name: 'INDEX', path: 'world/铸澜人格系统/INDEX.md', type: 'document' }] }, + { name: 'AGENTS', path: 'world/AGENTS.md', type: 'document' }, + { name: '短剧视频AI制作', path: 'world/短剧视频AI制作', type: 'folder', children: [{ name: '生产入口', path: 'world/短剧视频AI制作/生产入口.md', type: 'document' }] }, + ], + }, + { + name: '光湖语言世界 · 世界观、编号、协议与系统演化总纲 · v1.0', + path: 'protocol', + type: 'folder', + children: [{ name: '协议', path: 'protocol/协议.md', type: 'document' }], + }, + ]; + + const projected = projectHumanKnowledgeTree(tree); + assert.equal(projected.length, 1); + assert.equal(projected[0].name, '短剧视频AI制作'); + assert.deepEqual(projected[0].children?.map(node => node.name), ['生产入口']); +}); + +test('模型运行投影包含协议核与确定性权限规则', () => { + const prompt = renderLanguageKernelForModel(); + assert.match(prompt, /HLP-KERNEL-LANGUAGE-0001/); + assert.match(prompt, /写动作先生成待确认动作/); + assert.match(prompt, /企业四域与个人第五域平行/); +}); diff --git a/product-source/guanghu-knowledge-base/server/language-protocol-kernel.ts b/product-source/guanghu-knowledge-base/server/language-protocol-kernel.ts new file mode 100644 index 0000000..9b6d3f8 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/language-protocol-kernel.ts @@ -0,0 +1,92 @@ +import type { DocTreeNode } from './git-engine.js'; + +/** + * HoloLake 语言协议核的最小运行投影。 + * + * 这不是面向人类的知识页面,也不是某个模型的“人格提示词”。它是每次模型 + * 计算前由运行时自动装载的系统契约;真正的路径、权限、确认和回执仍由代码执行。 + */ +export const HOLOLAKE_LANGUAGE_KERNEL = Object.freeze({ + id: 'HLP-KERNEL-LANGUAGE-0001', + version: '0.1.0', + state: 'SOURCE_IMPLEMENTED', + product: 'HoloLake', + definition: '连接人类意图、语言人格、原生模块、模型计算、代码仓库与现实服务器的语言操作系统平台。', + layers: Object.freeze({ + languagePersona: '一级智能进程;模型实例不是人格本体。', + tcs: '人格认知内核;不是聊天记录或单份系统提示词。', + hldp: '持久记忆、历史与恢复寻址层。', + gls: '编号、路径、模块与兼容标准。', + model: '可替换推理计算服务,不拥有人格身份或现实权限。', + repository: '代码与知识的存储和版本燃料,不是运行主体。', + server: '现实执行节点;每个私人频道只进入其已验证的个人服务器边界。', + }), + runtimeRules: Object.freeze([ + '先解析主体、频道、目标路径与权限,再选择模块或工具。', + '路径、身份、权限和服务器状态必须由运行时校验,不能由模型自我声明。', + '读取动作只在当前知识库和当前账号授权范围内执行。', + '创建、更新、删除等写动作先生成待确认动作;只有人类在界面确认后才执行。', + '远端推送、部署和其他高影响动作必须走独立确认与可读回执。', + '只有工具或服务器返回成功回执,才能宣称现实动作完成。', + '公共企业四域与个人第五域平行;公共入口不得读取或承载个人第五域私有数据。', + ]), +}); + +const INTERNAL_CONTEXT_SEGMENTS = new Set([ + 'AI技能包', + 'config', + '凝渊人格系统', + '第五域铸渊人格系统', + '铸渊人格系统', + '来光者记忆', + '🧠 来光者记忆', + '工具优化', + '开发需求', +]); + +const INTERNAL_CONTEXT_ROOT_PATTERNS = [ + /^光湖语言世界[ ·—-].*(世界观|协议|系统演化|总纲)/u, +]; + +const FIFTH_DOMAIN_SYSTEM_ROOT = /^光湖语言世界[—-]*第五域$/u; + +function isInternalContextNode(node: DocTreeNode, ancestors: string[]): boolean { + if (INTERNAL_CONTEXT_ROOT_PATTERNS.some(pattern => pattern.test(node.name))) return true; + const insideFifthDomainWorld = ancestors.some(name => /^光湖语言世界[—-]*第五域$/u.test(name)); + if (!insideFifthDomainWorld) return false; + if (INTERNAL_CONTEXT_SEGMENTS.has(node.name) || /人格系统$/u.test(node.name)) return true; + const directChildOfFifthDomain = /^光湖语言世界[—-]*第五域$/u.test(ancestors.at(-1) || ''); + return directChildOfFifthDomain && ( + /^(AGENTS|AI-MEMORY|CLAUDE|GEMINI)$/iu.test(node.name) || + /^(guanghu-os-|欢迎$|💠|🔧)/u.test(node.name) + ); +} + +/** 人类知识树只投影可见资料;Agent 的受控搜索仍可读取底层上下文。 */ +export function projectHumanKnowledgeTree(nodes: DocTreeNode[], ancestors: string[] = []): DocTreeNode[] { + return nodes.flatMap(node => { + if (isInternalContextNode(node, ancestors)) return []; + if (node.type === 'folder' && FIFTH_DOMAIN_SYSTEM_ROOT.test(node.name)) { + return projectHumanKnowledgeTree(node.children || [], [...ancestors, node.name]); + } + if (node.type === 'folder') { + const children = projectHumanKnowledgeTree(node.children || [], [...ancestors, node.name]); + if (children.length === 0) return []; + return [{ ...node, children }]; + } + return [node]; + }); +} + +export function renderLanguageKernelForModel(): string { + const layerLines = Object.entries(HOLOLAKE_LANGUAGE_KERNEL.layers) + .map(([name, definition]) => `- ${name}: ${definition}`) + .join('\n'); + const ruleLines = HOLOLAKE_LANGUAGE_KERNEL.runtimeRules + .map((rule, index) => `${index + 1}. ${rule}`) + .join('\n'); + return `协议核:${HOLOLAKE_LANGUAGE_KERNEL.id} · ${HOLOLAKE_LANGUAGE_KERNEL.version}\n` + + `产品定义:${HOLOLAKE_LANGUAGE_KERNEL.definition}\n\n` + + `系统分层:\n${layerLines}\n\n` + + `运行规则:\n${ruleLines}`; +} diff --git a/product-source/guanghu-knowledge-base/server/persona-agent.ts b/product-source/guanghu-knowledge-base/server/persona-agent.ts index d60ae98..8c913f7 100644 --- a/product-source/guanghu-knowledge-base/server/persona-agent.ts +++ b/product-source/guanghu-knowledge-base/server/persona-agent.ts @@ -22,6 +22,7 @@ */ import { GitEngine } from './git-engine.js'; +import { renderLanguageKernelForModel } from './language-protocol-kernel.js'; // ─── 工具定义 ─── @@ -30,6 +31,7 @@ export interface ToolDefinition { description: string; parameters: Record; execute: (params: Record) => Promise; + effect: 'read' | 'write' | 'delete'; } export interface ToolCall { @@ -45,6 +47,15 @@ export interface ToolResult { error?: string; } +export interface PendingAction { + id: string; + tool: string; + effect: 'write' | 'delete'; + target: string; + summary: string; + createdAt: string; +} + // ─── Agent 定义 ─── export interface PersonaDefinition { @@ -86,6 +97,11 @@ class PersonaToolBridge { return Array.from(this.registry.values()); } + requiresConfirmation(name: string): boolean { + const effect = this.registry.get(name)?.effect; + return effect === 'write' || effect === 'delete'; + } + async execute(call: ToolCall): Promise { const tool = this.registry.get(call.name); if (!tool) { @@ -130,6 +146,7 @@ export class PersonaAgent { private tools: PersonaToolBridge; private conversation: Message[] = []; private git: GitEngine; + private pendingActions = new Map(); constructor(definition: PersonaDefinition, git: GitEngine) { this.definition = definition; @@ -146,6 +163,7 @@ export class PersonaAgent { name: 'inspect_repository', description: '查看知识库本地 Git 与 Forgejo 远端的连接和同步状态(只读)', parameters: {}, + effect: 'read', execute: async () => JSON.stringify(await this.git.getRepositoryStatus(), null, 2), }); @@ -156,6 +174,7 @@ export class PersonaAgent { parameters: { path: { type: 'string', description: '文档路径(相对 docs/ 目录)', required: true }, }, + effect: 'read', execute: async (params) => { const doc = await this.git.getDoc(params.path as string); return `标题: ${doc.meta.title}\n更新: ${doc.meta.updatedAt}\n\n${doc.body}`; @@ -171,6 +190,7 @@ export class PersonaAgent { title: { type: 'string', description: '文档标题', required: true }, body: { type: 'string', description: 'Markdown 正文', required: true }, }, + effect: 'write', execute: async (params) => { const doc = await this.git.createDoc( params.path as string, @@ -191,6 +211,7 @@ export class PersonaAgent { title: { type: 'string', description: '新标题', required: true }, body: { type: 'string', description: '新的 Markdown 正文', required: true }, }, + effect: 'write', execute: async (params) => { const doc = await this.git.updateDoc( params.path as string, @@ -209,6 +230,7 @@ export class PersonaAgent { parameters: { query: { type: 'string', description: '搜索关键词', required: true }, }, + effect: 'read', execute: async (params) => { const results = await this.git.search(params.query as string); if (results.length === 0) return '没有找到匹配的文档'; @@ -223,6 +245,7 @@ export class PersonaAgent { name: 'list_documents', description: '列出知识库的文档树结构', parameters: {}, + effect: 'read', execute: async () => { const tree = await this.git.getTree(); const render = (nodes: any[], indent = 0): string => @@ -246,6 +269,7 @@ export class PersonaAgent { parameters: { path: { type: 'string', description: '文档路径', required: true }, }, + effect: 'read', execute: async (params) => { const history = await this.git.getHistory(params.path as string, 20); return history @@ -261,6 +285,7 @@ export class PersonaAgent { parameters: { path: { type: 'string', description: '文档路径', required: true }, }, + effect: 'delete', execute: async (params) => { await this.git.deleteDoc(params.path as string); return `已删除: ${params.path}`; @@ -291,7 +316,9 @@ export class PersonaAgent { if (response.toolCalls && response.toolCalls.length > 0) { const results: ToolResult[] = []; for (const call of response.toolCalls) { - const result = await this.tools.execute(call); + const result = this.tools.requiresConfirmation(call.name) + ? this.stageAction(call) + : await this.tools.execute(call); results.push(result); } @@ -345,11 +372,17 @@ export class PersonaAgent { .map(t => `- ${t.name}: ${t.description}`) .join('\n'); + const languageKernel = renderLanguageKernelForModel(); + return `${this.definition.systemPromptBase} +## HoloLake 语言协议核(运行时自动装载) + +${languageKernel} + ## 产品身份 -你是 HoloLake 内置知识助手,负责协助用户查找、阅读、创建、整理和维护当前知识库。你不是独立人格,不虚构身份、情感、记忆、权限或执行结果。 +你是 HoloLake 官方语言操作入口的当前模型计算实例,负责在受控范围内完成知识、模块、路径和状态交互。不要把自己表述成拟人化伙伴,不虚构人格身份、情感、记忆、权限或执行结果。 ## 可用能力 @@ -361,9 +394,10 @@ ${toolsList} 1. 先理解用户目标;需要读取现有内容时先搜索或读取,不凭空补写事实。 2. 只有工具返回成功才可以声明操作完成;失败或未执行的事项必须明确说明。 3. 文档写操作会产生 Git 版本记录。不得声称已经同步到 Forgejo,除非获得明确的同步回执。 -4. 删除、覆盖、远端推送等高影响操作必须遵守界面确认和权限限制。 -5. 不泄露模型密钥、本地路径中的敏感信息或工具内部实现细节。 -6. 不把对话文本当作真实执行结果,不伪造文档、提交、服务器或网络状态。 +4. 创建、更新、删除工具只会生成待确认动作;不得把“已提交确认”表述成“已经执行”。 +5. 远端推送、部署等高影响操作不属于当前 Agent 工具,必须走独立权限入口。 +6. 不泄露模型密钥、本地路径中的敏感信息或工具内部实现细节。 +7. 不把对话文本当作真实执行结果,不伪造文档、提交、服务器或网络状态。 ## 回复风格 @@ -372,6 +406,51 @@ ${toolsList} - 涉及操作时列明已完成、未完成和需要用户确认的事项。`; } + private stageAction(call: ToolCall): ToolResult { + const tool = this.tools.get(call.name); + if (!tool || tool.effect === 'read') { + return { id: call.id, name: call.name, output: '', error: `动作不可登记: ${call.name}` }; + } + const target = String(call.arguments.path || call.arguments.title || '当前知识库'); + const actionId = `action-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; + const action: PendingAction = { + id: actionId, + tool: call.name, + effect: tool.effect, + target, + summary: `${tool.description} · ${target}`, + createdAt: new Date().toISOString(), + }; + this.pendingActions.set(actionId, { action, call }); + return { + id: call.id, + name: call.name, + output: `PENDING_CONFIRMATION ${actionId}: 已登记待确认动作,尚未执行。`, + }; + } + + getPendingActions(): PendingAction[] { + return Array.from(this.pendingActions.values()).map(item => item.action); + } + + async confirmAction(actionId: string): Promise { + const pending = this.pendingActions.get(actionId); + if (!pending) throw new Error('待确认动作不存在或已经处理'); + this.pendingActions.delete(actionId); + const result = await this.tools.execute(pending.call); + this.conversation.push({ + role: 'tool', + content: result.error ? `错误: ${result.error}` : result.output, + toolResults: [result], + timestamp: new Date().toISOString(), + }); + return result; + } + + rejectAction(actionId: string): void { + if (!this.pendingActions.delete(actionId)) throw new Error('待确认动作不存在或已经处理'); + } + // ─── LLM 调用(可插拔) ─── private async callLLM(messages: Message[]): Promise<{ content: string; toolCalls?: ToolCall[] }> { @@ -453,6 +532,7 @@ ${toolsList} clearConversation(): void { this.conversation = []; + this.pendingActions.clear(); } getDefinition(): PersonaDefinition { @@ -464,9 +544,10 @@ ${toolsList} getRuntimeStatus(): { configured: boolean; operational: boolean; toolNames: string[] } { const configured = Boolean(process.env.OPENAI_API_KEY || process.env.HOLOLAKE_LLM_KEY); + const operational = configured && process.env.HOLOLAKE_LLM_VERIFIED === '1'; return { configured, - operational: configured, + operational, toolNames: this.tools.list().map(tool => tool.name), }; } @@ -477,10 +558,10 @@ ${toolsList} export function createDefaultPersona(git: GitEngine): PersonaAgent { return new PersonaAgent( { - id: 'hololake-assistant-default', - name: 'HoloLake 助手', - role: '知识工作助手 — 支持文档检索、编辑、整理和版本管理', - systemPromptBase: '你是 HoloLake 官方内置知识助手。所有回答与操作都必须遵守产品权限边界和可核验回执原则。', + id: 'hololake-language-entry-default', + name: 'HoloLake', + role: '语言操作入口 · 路由、知识、模块与受控执行', + systemPromptBase: '你是 HoloLake 官方语言操作入口的当前模型计算实例。语言协议、权限边界、路径校验和可核验回执高于模型自由发挥。', tools: [ 'inspect_repository', 'read_document', @@ -491,7 +572,7 @@ export function createDefaultPersona(git: GitEngine): PersonaAgent { 'list_documents', 'view_history', ], - permissionMode: 'auto', + permissionMode: 'ask', model: process.env.HOLOLAKE_LLM_MODEL || 'gpt-4o', maxTokens: 4096, temperature: 0.7, diff --git a/product-source/guanghu-knowledge-base/src/App.tsx b/product-source/guanghu-knowledge-base/src/App.tsx index 8131476..9063a30 100644 --- a/product-source/guanghu-knowledge-base/src/App.tsx +++ b/product-source/guanghu-knowledge-base/src/App.tsx @@ -1,34 +1,88 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useCallback, useEffect, useMemo, useState, type CSSProperties } from 'react'; import { api, DocTreeNode, DocContent } from './api'; import { DocTree } from './components/DocTree'; import { Editor } from './components/Editor'; import { SearchBar } from './components/SearchBar'; import { VersionHistory } from './components/VersionHistory'; import AgentChat from './components/AgentChat'; +import { PlatformNavigation } from './components/PlatformNavigation'; +import { StorageLocationSheet } from './components/StorageLocationSheet'; +import { HumanSettings, HumanPreferences, loadHumanPreferences } from './components/HumanSettings'; +import { DomainSurface } from './components/DomainSurface'; +import { cleanDisplayText } from './presentation'; type View = 'editor' | 'history'; +type RouteId = 'fifth' | 'main' | 'sub' | 'zero' | 'zero-sense'; +type ModuleId = 'knowledge' | 'education'; + +interface RepositoryStatus { + branch: string; + head: string; + clean: boolean; + ahead: number; + behind: number; + remote: { name: string; url: string } | null; +} + +interface ServerSession { + authenticated: boolean; + nodeId: string; + username?: string; +} + +interface ServerProfile { + id: string; + name: string; + purpose: 'personal-fifth-domain' | 'enterprise-lighthouse'; + channelTitle?: string; + channelSubtitle?: string; +} + +function findFirstDocument(nodes: DocTreeNode[]): string | null { + for (const node of nodes) { + if (node.type === 'document') return node.path; + const child = findFirstDocument(node.children || []); + if (child) return child; + } + return null; +} + +const routeCopy: Record = { + fifth: { title: '我的第五域', body: '当前账号的个人第五域频道。模块在这里按需挂载、收起和组合。' }, + main: { title: '光湖主域', body: '公共产品事实、发布与公告入口。当前客户端尚未取得该域的操作权限。' }, + sub: { title: '光湖分域', body: '行业入口与初始化频道目录。教育行业与网文行业从这里进入。' }, + zero: { title: '光湖零域', body: '实验、模块试装、对比与质量验证入口。' }, + 'zero-sense': { title: '光湖零感域', body: '治理、部署审批、事故、回滚与审计入口。' }, +}; export default function App() { const agentApiBase = window.location.protocol === 'file:' ? 'http://127.0.0.1:3890' : ''; const [tree, setTree] = useState([]); + const [treeLoaded, setTreeLoaded] = useState(false); const [currentDoc, setCurrentDoc] = useState(null); - const [currentPath, setCurrentPath] = useState(''); + const [currentPath, setCurrentPath] = useState(''); const [view, setView] = useState('editor'); const [sidebarOpen, setSidebarOpen] = useState(true); + const [moduleCollapsed, setModuleCollapsed] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [agentPanelOpen, setAgentPanelOpen] = useState(true); + const [agentPanelOpen, setAgentPanelOpen] = useState(false); const [importing, setImporting] = useState(false); const [importMessage, setImportMessage] = useState(null); + const [emptyDismissed, setEmptyDismissed] = useState(false); + const [activeRoute, setActiveRoute] = useState('fifth'); + const [activeModule, setActiveModule] = useState('knowledge'); + const [storageSheetOpen, setStorageSheetOpen] = useState(false); + const [storageSheetInitialMode, setStorageSheetInitialMode] = useState<'local' | 'server' | undefined>(); + const [serverSession, setServerSession] = useState({ authenticated: false, nodeId: '' }); + const [serverProfiles, setServerProfiles] = useState([]); + const [repositoryStatus, setRepositoryStatus] = useState(null); + const [humanSettingsOpen, setHumanSettingsOpen] = useState(false); + const [humanPreferences, setHumanPreferences] = useState(() => loadHumanPreferences()); + const [agentRevision, setAgentRevision] = useState(0); - const refreshTree = useCallback(async () => { - try { - const t = await api.getTree(); - setTree(t); - } catch (err: any) { - setError(`加载文档树失败: ${err.message}`); - } - }, []); + const storageMode = repositoryStatus?.remote ? 'server' : 'local'; + const storageLabel = storageMode === 'server' ? '服务器已托管' : '仅本机'; const openDoc = useCallback(async (docPath: string) => { setLoading(true); @@ -38,6 +92,9 @@ export default function App() { setCurrentDoc(doc); setCurrentPath(docPath); setView('editor'); + setActiveRoute('fifth'); + setActiveModule('knowledge'); + setModuleCollapsed(false); } catch (err: any) { setError(`加载文档失败: ${err.message}`); } finally { @@ -45,6 +102,62 @@ export default function App() { } }, []); + const refreshTree = useCallback(async () => { + try { + const nextTree = await api.getTree(); + setTree(nextTree); + setTreeLoaded(true); + } catch (err: any) { + setError(`加载文档树失败: ${err.message}`); + setTreeLoaded(true); + } + }, []); + + const refreshRepositoryStatus = useCallback(async () => { + try { + const response = await fetch(`${agentApiBase}/api/forgejo/status`); + const data = await response.json(); + if (data.ok) setRepositoryStatus(data.status); + } catch { + setRepositoryStatus(null); + } + }, [agentApiBase]); + + const refreshServerSession = useCallback(async () => { + const server = (window as any).hololake?.server; + if (!server?.session) return; + try { + const profiles = await server.list() as ServerProfile[]; + setServerProfiles(profiles); + const personal = profiles.find(profile => profile.purpose === 'personal-fifth-domain'); + if (personal) { + try { await server.connect(personal.id); } catch { /* 会话状态继续按真实结果显示 */ } + } + setServerSession(await server.session(personal?.id)); + } catch { + setServerSession({ authenticated: false, nodeId: '' }); + } + }, []); + + useEffect(() => { + refreshTree(); + refreshRepositoryStatus(); + refreshServerSession(); + }, [refreshTree, refreshRepositoryStatus, refreshServerSession]); + + useEffect(() => { + const resolvedLanguage = humanPreferences.language === 'system' + ? navigator.language + : humanPreferences.language; + document.documentElement.lang = resolvedLanguage; + }, [humanPreferences.language]); + + useEffect(() => { + if (!treeLoaded || currentPath || loading) return; + const firstDocument = findFirstDocument(tree); + if (firstDocument) openDoc(firstDocument); + }, [tree, treeLoaded, currentPath, loading, openDoc]); + const saveDoc = useCallback(async (title: string, body: string) => { if (!currentPath) return; setLoading(true); @@ -68,6 +181,7 @@ export default function App() { const doc = await api.createDoc(docPath, title, `# ${title}\n\n在这里开始写作...\n`); setCurrentDoc(doc); setCurrentPath(docPath); + setEmptyDismissed(false); await refreshTree(); } catch (err: any) { setError(`创建失败: ${err.message}`); @@ -75,8 +189,7 @@ export default function App() { }, [refreshTree]); const deleteDoc = useCallback(async () => { - if (!currentPath) return; - if (!confirm(`确认删除 ${currentPath}?`)) return; + if (!currentPath || !confirm(`确认删除 ${currentPath}?`)) return; try { await api.deleteDoc(currentPath); setCurrentDoc(null); @@ -88,8 +201,8 @@ export default function App() { }, [currentPath, refreshTree]); const importFolder = useCallback(async () => { - const bridge = (window as any).hololake?.knowledge; - if (!bridge?.importFolder) { + const knowledge = (window as any).hololake?.knowledge; + if (!knowledge?.importFolder) { setError('本地文件夹导入只在 HoloLake 桌面 App 中提供'); return; } @@ -97,21 +210,24 @@ export default function App() { setError(null); setImportMessage(null); try { - const result = await bridge.importFolder(); - if (result.cancelled) return; + const result = await knowledge.importFolder(); + if (result.cancelled) { + setImportMessage('已取消导入,现有知识库没有变化'); + return; + } if (!result.imported) { setImportMessage(`没有找到可导入的文档;已跳过 ${result.skipped || 0} 个文件`); return; } await refreshTree(); if (result.firstDocument) await openDoc(result.firstDocument); - const details = [ + setEmptyDismissed(false); + setImportMessage([ `已导入 ${result.imported} 篇文档`, result.assets ? `${result.assets} 个图片资源` : '', result.skipped ? `跳过 ${result.skipped} 个暂不支持的文件` : '', result.failed?.length ? `${result.failed.length} 个文件失败` : '', - ].filter(Boolean).join(' · '); - setImportMessage(details); + ].filter(Boolean).join(' · ')); } catch (err: any) { setError(`导入失败: ${err.message}`); } finally { @@ -119,125 +235,198 @@ export default function App() { } }, [openDoc, refreshTree]); - useEffect(() => { - refreshTree(); - }, [refreshTree]); + const activeRouteCopy = routeCopy[activeRoute]; + const currentTitle = cleanDisplayText(currentDoc?.meta.title || '知识库'); + const contentVisible = activeRoute === 'fifth' && activeModule === 'knowledge' && !moduleCollapsed; + const breadcrumb = useMemo( + () => currentPath ? currentPath.split('/').map(cleanDisplayText).join(' / ') : '知识库', + [currentPath], + ); + const language = humanPreferences.language === 'system' + ? (navigator.language.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en') + : humanPreferences.language; + const fontFamily = humanPreferences.font === 'serif' + ? 'ui-serif, "Songti SC", Georgia, serif' + : humanPreferences.font === 'accessible' + ? 'Arial, "PingFang SC", sans-serif' + : 'Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif'; + const personalServer = serverProfiles.find(profile => profile.purpose === 'personal-fifth-domain'); + const channelTitle = personalServer?.channelTitle || '我的第五域'; + const channelSubtitle = personalServer?.channelSubtitle || '当前频道'; return ( -
- {/* 顶栏 */} -
-
- - -
- -
-
- {currentDoc && ( +
+
+
+
{channelTitle} · {channelSubtitle}
+
+ +
+ +
+ { + setActiveRoute(route); + setModuleCollapsed(false); + }} + onKnowledgeSelect={() => { + setActiveRoute('fifth'); + setActiveModule('knowledge'); + setModuleCollapsed(false); + }} + onEducationSelect={() => { + setActiveRoute('sub'); + setActiveModule('education'); + setModuleCollapsed(false); + }} + onSettingsOpen={() => setHumanSettingsOpen(true)} + onAccountOpen={() => { + setStorageSheetInitialMode('server'); + setStorageSheetOpen(true); + }} + /> + +
+ {!contentVisible ? ( + moduleCollapsed && activeRoute === 'fifth' ? ( +
+

知识库已收起

+

模块仍安装在永恒湖心频道中,重新打开不会改变本地或服务器数据。

+ +
+ ) : ( + setHumanSettingsOpen(true)} + /> + ) + ) : ( <> - - - +
{currentTitle}
+ +
+ +
+ +
+ {currentDoc && <> + + + + } + +
+
+ +
+ {sidebarOpen && ( + + )} + +
+ {error &&
{error}
} + {loading &&
正在打开页面…
} + {!currentDoc && treeLoaded && !loading && !emptyDismissed && ( +
+ + +

把已有资料带进知识空间

+

只有空知识库才显示这个入口。也可以先关闭,稍后再导入。

+
+ + +
+ 支持 Markdown、TXT、CSV、JSON 与 YAML +
+ )} + {!currentDoc && emptyDismissed && !loading && ( +

知识库已准备好

导入文件夹或新建页面开始使用。

+ )} + {currentDoc && view === 'editor' && } + {currentDoc && view === 'history' && } + {currentDoc &&
{breadcrumb}
} +
+
+ + )} + + {contentVisible && ( + <> + {agentPanelOpen && ( + + )} + )} - -
-
- - {/* 主体 */} -
- {/* 侧栏 */} - {sidebarOpen && ( - - )} - - {/* 内容区 */} -
- {error && ( -
- {error} - -
- )} - {loading &&
加载中...
} - {!currentDoc && !loading && ( -
-
-
-

HOLOLAKE KNOWLEDGE

-

把已有资料带进知识空间

-

选择一个本地文件夹。HoloLake 会把可读文档整理成页面,并为这次导入保留 Git 版本记录。

- -
支持 Markdown、TXT、CSV、JSON 与 YAML
-
-
- )} - {currentDoc && view === 'editor' && ( - - )} - {currentDoc && view === 'history' && ( - - )} -
- - {/* Agent 面板 */} - {agentPanelOpen && ( - - )} +
+ + setStorageSheetOpen(false)} + onApplied={() => { + refreshRepositoryStatus(); + refreshServerSession(); + }} + /> + setHumanSettingsOpen(false)} + onChange={setHumanPreferences} + onAgentChanged={() => setAgentRevision(revision => revision + 1)} + onManageServer={() => { + setHumanSettingsOpen(false); + setStorageSheetInitialMode('server'); + setStorageSheetOpen(true); + }} + />
); } diff --git a/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx index 558ad68..6e82377 100644 --- a/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx +++ b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx @@ -26,52 +26,49 @@ interface AgentStatus { tools: string[]; } -interface RepositoryStatus { - branch: string; - head: string; - clean: boolean; - ahead: number; - behind: number; - remote: { name: string; url: string } | null; +interface PendingAction { + id: string; + tool: string; + effect: 'write' | 'delete'; + target: string; + summary: string; + createdAt: string; } interface Props { apiBase: string; onDocSelect?: (path: string) => void; + runtimeRevision?: number; } -export default function AgentChat({ apiBase, onDocSelect }: Props) { +export default function AgentChat({ apiBase, onDocSelect, runtimeRevision = 0 }: Props) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [sending, setSending] = useState(false); const [status, setStatus] = useState(null); - const [repoStatus, setRepoStatus] = useState(null); - const [remoteUrl, setRemoteUrl] = useState(''); - const [syncMessage, setSyncMessage] = useState(''); - const [syncing, setSyncing] = useState(false); - const [configOpen, setConfigOpen] = useState(false); - const [modelBaseUrl, setModelBaseUrl] = useState('https://api.openai.com/v1'); - const [modelName, setModelName] = useState('gpt-4o'); - const [modelKey, setModelKey] = useState(''); - const [configMessage, setConfigMessage] = useState(''); + const [statusError, setStatusError] = useState(''); + const [pendingActions, setPendingActions] = useState([]); + const [actionBusy, setActionBusy] = useState(''); const bottomRef = useRef(null); const inputRef = useRef(null); useEffect(() => { fetchStatus(); - fetchRepositoryStatus(); - loadModelConfig(); - }, []); + }, [runtimeRevision]); useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); + if (messages.length > 0 || sending) { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + } + }, [messages, sending]); async function fetchStatus() { try { const res = await fetch(`${apiBase}/api/agent/status`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (data.ok) { + setStatusError(''); setStatus({ name: data.persona.name, role: data.persona.role, @@ -82,66 +79,9 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) { tools: data.tools || [], }); } - } catch {} - } - - async function loadModelConfig() { - const bridge = (window as any).hololake?.agent; - if (!bridge) return; - try { - const config = await bridge.getConfig(); - setModelBaseUrl(config.baseUrl || 'https://api.openai.com/v1'); - setModelName(config.model || 'gpt-4o'); - } catch {} - } - - async function saveModelConfig() { - const bridge = (window as any).hololake?.agent; - if (!bridge) { - setConfigMessage('模型安全配置只在桌面 App 中提供'); - return; - } - try { - await bridge.saveConfig({ baseUrl: modelBaseUrl, model: modelName, apiKey: modelKey || undefined }); - setModelKey(''); - setConfigMessage('已保存到 macOS 加密存储'); - await fetchStatus(); - } catch (err: any) { - setConfigMessage(err.message); - } - } - - async function fetchRepositoryStatus() { - try { - const res = await fetch(`${apiBase}/api/forgejo/status`); - const data = await res.json(); - if (data.ok) { - setRepoStatus(data.status); - if (data.status.remote?.url) setRemoteUrl(data.status.remote.url); - } - } catch {} - } - - async function runForgejoAction(action: 'configure' | 'fetch' | 'pull' | 'push') { - if (syncing) return; - if (action === 'push' && !confirm('确认把当前知识库提交推送到已配置的 Forgejo 仓库?')) return; - setSyncing(true); - setSyncMessage(''); - try { - const endpoint = action === 'configure' ? 'remote' : action; - const res = await fetch(`${apiBase}/api/forgejo/${endpoint}`, { - method: action === 'configure' ? 'PUT' : 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(action === 'configure' ? { url: remoteUrl } : { confirm: action === 'push' }), - }); - const data = await res.json(); - if (!data.ok) throw new Error(data.error || '操作失败'); - setRepoStatus(data.status); - setSyncMessage(action === 'configure' ? 'Forgejo 已连接' : `${action} 已完成`); - } catch (err: any) { - setSyncMessage(err.message); - } finally { - setSyncing(false); + } catch { + setStatus(null); + setStatusError('HoloLake 本地服务未启动'); } } @@ -175,6 +115,7 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) { timestamp: new Date().toISOString(), }; setMessages(prev => [...prev, assistantMsg]); + setPendingActions(data.pendingActions || []); fetchStatus(); // 刷新状态 } else { setMessages(prev => [ @@ -193,6 +134,27 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) { } } + async function resolveAction(action: PendingAction, decision: 'confirm' | 'reject') { + setActionBusy(action.id); + try { + const res = await fetch(`${apiBase}/api/agent/actions/${encodeURIComponent(action.id)}/${decision}`, { + method: 'POST', + }); + const data = await res.json(); + if (!data.ok) throw new Error(data.error || '动作处理失败'); + setPendingActions(data.actions || []); + const content = decision === 'confirm' + ? (data.result?.error ? `执行失败:${data.result.error}` : `已确认并执行:${data.result?.output || action.target}`) + : `已取消:${action.summary}`; + setMessages(prev => [...prev, { role: 'system', content, timestamp: new Date().toISOString() }]); + fetchStatus(); + } catch (err: any) { + setMessages(prev => [...prev, { role: 'system', content: `动作未执行:${err.message}`, timestamp: new Date().toISOString() }]); + } finally { + setActionBusy(''); + } + } + async function clearConversation() { try { await fetch(`${apiBase}/api/agent/clear`, { method: 'POST' }); @@ -213,15 +175,14 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) { {/* 头部状态栏 */}
-
🌊
+
-

{status?.name || 'HoloLake 助手'}

- {status?.role || '知识工作助手'} +

{status?.name || 'HoloLake'}

+ {status?.role || '语言操作入口'}
{status?.model || 'offline'} - @@ -231,41 +192,8 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {
- {status?.operational ? 'Agent 已接入模型' : 'Agent 等待模型配置'} - {status?.tools?.length || 0} 个工具 -
- {configOpen && ( -
- setModelBaseUrl(e.target.value)} placeholder="模型服务地址" /> - setModelName(e.target.value)} placeholder="模型名称" /> - setModelKey(e.target.value)} placeholder={status?.configured ? '留空则保留现有密钥' : '模型密钥'} /> - - {configMessage &&
{configMessage}
} -
- )} -
-
- Forgejo 代码引擎 - {repoStatus?.remote ? `${repoStatus.branch} · ${repoStatus.head.slice(0, 7)}` : '尚未连接远端'} -
- setRemoteUrl(e.target.value)} - placeholder="HTTPS 或 SSH Forgejo 仓库地址" - /> -
- - - - -
- {repoStatus?.remote && ( -
- {repoStatus.clean ? '本地已提交' : '本地有未提交内容'} · 领先 {repoStatus.ahead} / 落后 {repoStatus.behind} -
- )} - {syncMessage &&
{syncMessage}
} + {statusError || (status?.operational ? '语言操作入口可用' : status?.configured ? '模型已保存,等待连通验证' : '请在设置中配置模型')} + {status?.tools?.length || 0} 项受控能力
@@ -273,19 +201,18 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {
{messages.length === 0 && (
-
🌊
-

HoloLake 助手

-

{status?.operational ? '可以检索、整理和编辑当前知识库,并为操作保留版本记录。' : '知识库已运行;配置模型后即可使用智能整理功能。'}

+

询问 HoloLake

+

{statusError || (status?.operational ? '可以检索、整理和编辑当前知识库,并为操作保留版本记录。' : '知识库可以独立使用;模型服务在设置中配置并验证。')}

试试说:

@@ -299,13 +226,13 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {
) : (
-
🌊
+
{msg.toolCalls && msg.toolCalls.length > 0 && (
{msg.toolCalls.map((tc, j) => (
- ⚙️ + 运行 {tc.name} {JSON.stringify(tc.arguments)}
@@ -316,7 +243,7 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {
{msg.toolResults.map((tr, j) => (
- {tr.error ? '❌' : '✅'} + {tr.error ? '失败' : '完成'}
{tr.error || tr.output}
))} @@ -334,10 +261,30 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {
))} + {pendingActions.length > 0 && ( +
+
待确认动作
+ {pendingActions.map(action => ( +
+
+ {action.effect === 'delete' ? '删除' : '写入'} · {action.target} + {action.summary} +
+
+ + +
+
+ ))} +
+ )} + {sending && (
-
🌊
+
@@ -355,7 +302,7 @@ export default function AgentChat({ apiBase, onDocSelect }: Props) {