feat(knowledge): restore complete human rendering layer

This commit is contained in:
冰朔 2026-09-03 16:25:43 +08:00
commit 3466b507c6
15 changed files with 665 additions and 23 deletions

View file

@ -14,3 +14,7 @@
有意差异:概念图中的人格回复和日历执行是视觉示例;运行产品没有绑定人格模型和日历能力,因此不显示也不伪造这些结果。实现改为明确的 `WAITING_PERSONA` 回执。
首屏可见文字来自冰朔本轮要求或产品登记名,没有新增营销口号、指标、徽章或模型选择器。
## 知识库人类视觉渲染层纠正
冰朔指出首轮只有 Markdown 文本编辑器,不能称为完整知识库模块。现已从只读旧产品供体精确拆入 `knowledge-render` 切片并在新壳中重新接线目录、阅读渲染、frontmatter、标签、大纲、wikilink、callout、代码块、表格、来源证据、编辑/阅读切换均可见。验收截图为 `design/concepts/hololake-knowledge-human-render-1536x1024.png`;旧产品其他模块未合并。

View file

@ -18,12 +18,13 @@
"rust_clippy_denied_warnings": "PASS_100",
"production_dependency_audit": "0_VULNERABILITIES",
"browser_channel_create_message_separation_knowledge_create_edit_save": "PASS_100",
"knowledge_human_visual_rendering_frontmatter_outline_wikilink_callout_table_code_evidence": "PASS_100",
"responsive_viewports": ["1536x1024_PASS", "820x900_PASS"],
"developer_id_signature": "PASS_100",
"installed_application": "/Applications/HoloLake.app",
"installed_version": "1.0.0",
"installed_binary_sha256": "f3cd899f99bf7667cd70a897c98ef82ea862567409f61ed6f0a485fe9bc3a96e",
"updater_archive_sha256": "87ae061b942e631a55d7a83002686f1be939431a4cfae62986e2d637c91ffefe",
"installed_binary_sha256": "50e6206be32ce7c70d42998b6217956aaa99687976e3d9ee383d3671e03c250d",
"updater_archive_sha256": "b126efa723415beacfad916621dbecceff7ea3ff24c79e6adacabd5bb343736a",
"installed_process_readback": "PASS_100",
"updater_artifact_signature": "PASS_100",
"update_endpoint_http": 204,

View file

@ -2,7 +2,7 @@
结论新的独立干净产品根已经建立HoloLake 1.0.0 已签名安装并在本机运行。它不是公众发行完成回执。
已通过TCS→GIR 编译与双语投影、模块编号唯一性、七种来源分流、前端生产构建、Rust 测试与严格 Clippy、生产依赖审计、频道建立、语言/回执分离、知识页新建编辑保存、1536×1024 与 820×900 视口、Developer ID 签名、更新包签名、`/Applications/HoloLake.app` 进程读回。更新入口现场返回 HTTP 204即服务在线且暂无更新。
已通过TCS→GIR 编译与双语投影、模块编号唯一性、七种来源分流、前端生产构建、Rust 测试与严格 Clippy、生产依赖审计、频道建立、语言/回执分离、知识页新建编辑保存、完整人类视觉阅读渲染frontmatter、大纲、wikilink、callout、表格、代码块、来源证据1536×1024 与 820×900 视口、Developer ID 签名、更新包签名、`/Applications/HoloLake.app` 进程读回。更新入口现场返回 HTTP 204即服务在线且暂无更新。
企业服务器读回:蓝环境保存 5 个中央仓和 5 个负责人工作仓;团队域权限尚未分配,公共切换尚未发生。冰朔语言主控工作仓没有独立编号与登记,因此本轮没有猜名创建。

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -18,7 +18,7 @@ HoloLake 是公众个人初始化频道语言驱动操作系统。第一版只
- 对话和执行事实使用不同来源类型,不把系统文字塞进用户或人格消息。
- 公共四域由企业门户和企业服务器承载,不嵌入个人频道。
- 第五域、冰朔私人核 `TCS-iZero∞` 及私人语料不进入公众发行。
- 当前知识库可处理 Markdown 和纯文本文件夹;PDF、Office、网页结构化转换尚未实现。
- 当前知识库具备人类视觉阅读层:安全 Markdown 渲染、frontmatter、页面大纲、callout、表格、代码块与 wikilink 跳转;可处理 Markdown 和纯文本文件夹。PDF、Office、网页结构化转换尚未实现。
- 当前外部 AI 入口是本机来源明确的 inbox/outbox实时加密通信尚未实现。
- 更新客户端只信任光湖控制的 HTTPS 清单与签名;服务器发行清单尚未发布。

View file

@ -12,6 +12,8 @@
"@tauri-apps/plugin-dialog": "2.7.2",
"@tauri-apps/plugin-process": "2.3.1",
"@tauri-apps/plugin-updater": "2.10.0",
"dompurify": "^3.4.13",
"marked": "^16.4.1",
"react": "19.2.0",
"react-dom": "19.2.0"
},
@ -1509,6 +1511,13 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@vitejs/plugin-react": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
@ -1630,6 +1639,15 @@
}
}
},
"node_modules/dompurify": {
"version": "3.4.13",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.420",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz",
@ -1775,6 +1793,18 @@
"yallist": "^3.0.2"
}
},
"node_modules/marked": {
"version": "16.4.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz",
"integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View file

@ -14,6 +14,8 @@
"@tauri-apps/plugin-dialog": "2.7.2",
"@tauri-apps/plugin-process": "2.3.1",
"@tauri-apps/plugin-updater": "2.10.0",
"dompurify": "^3.4.13",
"marked": "^16.4.1",
"react": "19.2.0",
"react-dom": "19.2.0"
},

View file

@ -9,6 +9,6 @@
{"donor_id":"HLP-DONOR-GROK-BUILD-0001","path":"/Volumes/JZAO/HoloLake/reference-materials/grok-build-v-780d138","allowed_use":"BUILD_EXECUTOR_RESEARCH_ONLY","imported":false},
{"donor_id":"HLP-DONOR-GUADA-0001","path":"/Volumes/JZAO/HoloLake/reference-materials/guada-v0.5.6","allowed_use":"KNOWLEDGE_UX_BEHAVIOR_ONLY","imported":false},
{"donor_id":"HLP-DONOR-OUTLINE-0001","path":"/Volumes/JZAO/HoloLake/reference-materials/outline","allowed_use":"KNOWLEDGE_MODEL_AND_IMPORT_RESEARCH_ONLY","imported":false},
{"donor_id":"HLP-DONOR-HOLOLAKE-LEGACY-0001","path":"product-source/hololake-native-desktop","allowed_use":"TAURI_SIGNING_UPDATE_AND_ACCEPTANCE_ORACLE_ONLY","imported":false}
{"donor_id":"HLP-DONOR-HOLOLAKE-LEGACY-0001","path":"product-source/hololake-native-desktop","allowed_use":"TAURI_SIGNING_UPDATE_ACCEPTANCE_ORACLE_AND_KNOWLEDGE_RENDER_SLICE_ONLY","imported":true,"extracted_slices":["src/modules/knowledge-render/index.tsx","src/modules/knowledge-render/manifest.ts"],"whole_product_merge":false}
]
}

View file

@ -7,7 +7,7 @@
"module_id": "HLP-MOD-CHANNEL-0001",
"name_zh": "个人初始化频道",
"kind": "SYSTEM_FOUNDATION",
"state": "LOCAL_INSTALLED_VERIFIED",
"state": "LOCAL_INSTALLED_VERIFIED_WITH_HUMAN_VISUAL_RENDERING",
"source": "src-tauri/src/lib.rs#create_channel",
"data_owner": "CURRENT_LOCAL_USER"
},
@ -17,7 +17,7 @@
"kind": "PREINSTALLED_USER_MODULE",
"state": "LOCAL_INSTALLED_VERIFIED",
"source": "src-tauri/src/lib.rs#knowledge",
"capabilities": ["CREATE", "READ", "EDIT", "IMPORT_FOLDER", "EXPORT", "MOVE_TO_TRASH", "PRIVATE_GIT_COMMIT"]
"capabilities": ["CREATE", "READ", "EDIT", "HUMAN_MARKDOWN_RENDERING", "FRONTMATTER", "OUTLINE", "WIKILINK_NAVIGATION", "IMPORT_FOLDER", "EXPORT", "MOVE_TO_TRASH", "PRIVATE_GIT_COMMIT"]
},
{
"module_id": "HLP-MOD-GLP-BRIDGE-0001",

View file

@ -25,6 +25,12 @@ package = json.loads((ROOT / "package.json").read_text())
for forbidden in ("openai", "anthropic", "@google/generative-ai", "langchain"):
if forbidden in package.get("dependencies", {}):
errors.append(f"internal model dependency forbidden: {forbidden}")
for required in ("marked", "dompurify"):
if required not in package.get("dependencies", {}):
errors.append(f"knowledge human renderer dependency missing: {required}")
renderer = ROOT / "src/modules/knowledge-render/index.tsx"
if not renderer.exists() or "MarkdownDocument" not in renderer.read_text():
errors.append("knowledge human visual renderer missing")
tauri = json.loads((ROOT / "src-tauri/tauri.conf.json").read_text())
endpoints = tauri["plugins"]["updater"]["endpoints"]

View file

@ -4,6 +4,12 @@ import { relaunch } from "@tauri-apps/plugin-process";
import { Icon } from "./icons";
import * as api from "./runtime";
import type { KnowledgeDocument, SourceKind, SystemSnapshot } from "./types";
import {
MarkdownDocument,
documentOutline,
jumpToHeading,
splitFrontmatter,
} from "./modules/knowledge-render";
type View =
| "channel"
@ -248,9 +254,10 @@ function Channel({
<article>
<h2></h2>
<strong>
{data.publicDistributionState === "SIGNED_UPDATE_FEED_READY_NO_RELEASE"
? "更新入口在线 · 暂无发行"
: data.publicDistributionState}
{data.publicDistributionState ===
"SIGNED_UPDATE_FEED_READY_NO_RELEASE"
? "更新入口在线 · 暂无发行"
: data.publicDistributionState}
</strong>
<p> Git </p>
</article>
@ -310,12 +317,15 @@ function Knowledge({
[doc, setDoc] = useState<KnowledgeDocument | null>(null),
[body, setBody] = useState(""),
[query, setQuery] = useState(""),
[newTitle, setNewTitle] = useState<string | null>(null);
[newTitle, setNewTitle] = useState<string | null>(null),
[editing, setEditing] = useState(false),
[inspectorOpen, setInspectorOpen] = useState(true);
useEffect(() => {
if (selected)
api.readDocument(selected).then((d) => {
setDoc(d);
setBody(d.body);
setEditing(false);
});
}, [selected]);
const list = useMemo(
@ -337,6 +347,8 @@ function Knowledge({
const save = async () => {
if (doc) {
await api.saveDocument(doc.path, body);
setDoc({ ...doc, body });
setEditing(false);
refresh();
}
};
@ -348,6 +360,20 @@ function Knowledge({
refresh();
}
};
const parsed = useMemo(() => splitFrontmatter(doc?.body ?? ""), [doc?.body]);
const outline = useMemo(() => documentOutline(doc?.body ?? ""), [doc?.body]);
const stats = useMemo(() => {
const chars = parsed.content.replace(/\s/g, "").length;
return { chars, minutes: Math.max(1, Math.ceil(chars / 450)) };
}, [parsed.content]);
const openWiki = (target: string) => {
const normalized = target.replace(/[\s·•・\-_|*`]/g, "").toLowerCase();
const hit = data.documents.find(
(item) =>
item.title.replace(/[\s·•・\-_|*`]/g, "").toLowerCase() === normalized,
);
if (hit) setSelected(hit.path);
};
return (
<div className="page knowledge-page">
<header>
@ -368,8 +394,14 @@ function Knowledge({
<button onClick={() => setNewTitle("")}></button>
</div>
</header>
<div className="knowledge-workspace">
<aside>
<div
className={`knowledge-workspace complete ${inspectorOpen ? "" : "inspector-closed"}`}
>
<aside className="knowledge-browser">
<div className="browser-title">
<strong></strong>
<span>{data.documents.length} </span>
</div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
@ -392,25 +424,69 @@ function Knowledge({
))
)}
</aside>
<section>
<section className="document-workspace">
{doc ? (
<>
<div className="editor-tools">
<strong>{doc.title}</strong>
<span />
<button onClick={() => setInspectorOpen((value) => !value)}>
{inspectorOpen ? "收起大纲" : "展开大纲"}
</button>
<button onClick={() => api.exportDocument(doc.path)}>
</button>
<button className="danger" onClick={del}>
</button>
<button onClick={save}></button>
{editing ? (
<>
<button
onClick={() => {
setBody(doc.body);
setEditing(false);
}}
>
</button>
<button onClick={save}></button>
</>
) : (
<>
<button className="danger" onClick={del}>
</button>
<button onClick={() => setEditing(true)}></button>
</>
)}
</div>
<textarea
className="editor"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
{editing ? (
<textarea
className="editor"
aria-label="Markdown 编辑器"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
) : (
<div className="document-scroll">
<header className="reader-heading">
<h1>{doc.title}</h1>
<p>
{stats.chars.toLocaleString()} · {stats.minutes}{" "}
</p>
{parsed.tags.length > 0 && (
<div className="meta-tags">
{parsed.tags.map((tag) => (
<span key={tag}>{tag}</span>
))}
</div>
)}
</header>
<MarkdownDocument
body={doc.body}
knownTitles={data.documents.map((item) => item.title)}
onWiki={openWiki}
/>
</div>
)}
</>
) : (
<div className="welcome">
@ -421,6 +497,53 @@ function Knowledge({
</div>
)}
</section>
{inspectorOpen && (
<aside className="document-inspector">
<header>
<strong></strong>
<span></span>
</header>
{doc ? (
<>
<nav className="outline-list">
{outline.length ? (
outline.map((item) => (
<button
key={item.id}
style={{ paddingLeft: 12 + (item.level - 1) * 10 }}
onClick={() => jumpToHeading(item.id)}
>
{item.title}
</button>
))
) : (
<p></p>
)}
</nav>
<dl className="document-evidence">
<div>
<dt></dt>
<dd> Git</dd>
</div>
<div>
<dt></dt>
<dd>{doc.path}</dd>
</div>
<div>
<dt></dt>
<dd>{doc.sha256.slice(0, 16)}</dd>
</div>
<div>
<dt></dt>
<dd> · </dd>
</div>
</dl>
</>
) : (
<p className="empty"></p>
)}
</aside>
)}
</div>
{newTitle !== null && (
<div className="modal-backdrop" onMouseDown={() => setNewTitle(null)}>

View file

@ -0,0 +1,223 @@
//! 知识渲染件 · 渲染入口(常驻模块 · 插座口 knowledge-render
//!
//! 职责:把仓库里的 Markdown 文件变成可读的页面——frontmatter 解析、
//! Notion/Outline 导出兼容、标题锚点、大纲同源、跳转。
import { useMemo } from "react";
import DOMPurify from "dompurify";
import { marked, Parser, type Tokens } from "marked";
export { knowledgeRenderManifest } from "./manifest";
function escapeHtml(value: string) {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
}
export function splitFrontmatter(body: string) {
const normalized = body.replace(/\r\n/g, "\n");
if (!normalized.startsWith("---\n"))
return {
content: normalized,
metadata: {} as Record<string, string>,
tags: [] as string[],
};
const closing = normalized.indexOf("\n---\n", 4);
if (closing < 0)
return {
content: normalized,
metadata: {} as Record<string, string>,
tags: [] as string[],
};
const metadata: Record<string, string> = {};
const tags: string[] = [];
let pendingListKey = "";
for (const line of normalized.slice(4, closing).split("\n")) {
const listItem = /^\s+-\s+(.+)$/.exec(line);
if (listItem && pendingListKey) {
if (pendingListKey === "tags")
tags.push(listItem[1].trim().replace(/^['"]|['"]$/g, ""));
continue;
}
const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
if (!match) continue;
pendingListKey = match[2] === "" ? match[1] : "";
const value = match[2].replace(/^['"]|['"]$/g, "");
metadata[match[1]] = value;
if (match[1] === "tags") {
const inline = /^\[(.*)\]$/.exec(match[2].trim());
const source = inline ? inline[1] : value;
source
.split(",")
.map((part) => part.trim().replace(/^['"]|['"]$/g, ""))
.filter(Boolean)
.forEach((tag) => tags.push(tag));
}
}
return { content: normalized.slice(closing + 5), metadata, tags };
}
function stripNotionLinks(line: string) {
// Notion 页面链接 → 只留链接文字(跳转关系按谕旨失效)
return line.replace(
/\[([^\]]*)\]\(([^)]*)\)/g,
(whole, text: string, href: string) =>
/notion\.(?:so|site)/.test(href) ? text : whole,
);
}
export function notionCompat(content: string) {
// 存量旧库文件尚未洗净:渲染时按"进门即洗"同规矩兜底过滤一遍。
// 内容一字不改,只把外来知识库的外壳换成光湖原生 Markdown冰朔 2026-08-15 谕:不兼容,进门就转)。
const result: string[] = [];
for (const line of content.replace(/\r\n/g, "\n").split("\n")) {
const trimmed = line.trim();
if (trimmed === "<aside>" || trimmed === "</aside>") continue;
if (trimmed.startsWith(":::toggle")) {
const title = trimmed.slice(":::toggle".length).trim();
result.push(title ? `**▸ ${title}**` : "**▸ 详情**");
continue;
}
if (trimmed === ":::" || trimmed.startsWith(":::toc")) continue;
result.push(stripNotionLinks(line.replace(/<br\s*\/?>/g, "\n")));
}
// Notion 粗体写法(**文字:**紧接正文)不合 CommonMark给收尾星号补一口气
return result
.join("\n")
.replace(/(\*\*[^*\n]+\*\*)(?=[^\s.,;:!?,。;:!?、)\]"'])/g, "$1 ");
}
const CALLOUT_TINTS: [RegExp, string][] = [
[/📌|🌌|🧭|💜|🗂|🏷|📚/, "callout-lavender"],
[/💡|⚡|🌟|☀|✨|🔑/, "callout-amber"],
[/⚠|🔥|❗|🚨|❌/, "callout-rose"],
[/✅|🌿|🍀|💚|✔/, "callout-mint"],
[/🌊|💧|🔵|❄|🧊/, "callout-sky"],
];
function normalizeTitle(value: string) {
return value.replace(/[\s·•・\-_|*`]/g, "").toLowerCase();
}
function calloutTint(text: string) {
for (const [pattern, tint] of CALLOUT_TINTS)
if (pattern.test(text)) return tint;
return "callout-slate";
}
export function markdownHtml(
body: string,
knownTitles: readonly string[] = [],
) {
const compat = notionCompat(body);
const titleMap = new Map<string, string>();
for (const title of knownTitles) titleMap.set(normalizeTitle(title), title);
const withWikiLinks = compat.replace(
/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,
(_whole, target: string, label?: string) =>
`<button class="wiki-link" type="button" data-wiki="${escapeHtml(target.trim())}">${escapeHtml((label || target).trim())}</button>`,
);
let headingIndex = 0;
const renderer = new marked.Renderer();
renderer.heading = (token) => {
const id = `heading-${headingIndex}`;
headingIndex += 1;
const inlineHtml = token.tokens.length
? Parser.parseInline(token.tokens)
: escapeHtml(token.text);
return `<h${token.depth} id="${id}">${inlineHtml}</h${token.depth}>\n`;
};
renderer.blockquote = (token) => {
const inner = Parser.parse(token.tokens) as string;
return `<blockquote class="callout ${calloutTint(token.raw)}">${inner}</blockquote>\n`;
};
renderer.link = (token) => {
const inner = token.tokens.length
? Parser.parseInline(token.tokens)
: escapeHtml(token.text);
const href = token.href || "";
if (/notion\.(?:so|site)/i.test(href)) {
// Notion 残留链接:认得出是自家页面就转成内部跳转,认不出标"外来页面"。
const hit = titleMap.get(normalizeTitle(token.text));
if (hit)
return `<button class="wiki-link" type="button" data-wiki="${escapeHtml(hit)}">${inner}</button>`;
return `<a class="external-link" href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}<span class="external-mark">外来页面</span></a>`;
}
if (/^https?:\/\//i.test(href))
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}</a>`;
return `<a href="${escapeHtml(href)}">${inner}</a>`;
};
const html = marked.parse(withWikiLinks, {
async: false,
gfm: true,
breaks: false,
renderer,
}) as string;
return DOMPurify.sanitize(html, {
ADD_ATTR: ["data-wiki", "id", "target", "rel"],
ADD_TAGS: ["button"],
});
}
export function documentOutline(body: string) {
const headings: { id: string; level: number; title: string }[] = [];
const plain = (tokens: unknown): string =>
(tokens as { type: string; text?: string; tokens?: unknown[] }[])
.map((token) =>
token.tokens && token.tokens.length
? plain(token.tokens)
: token.text || "",
)
.join("");
marked
.lexer(splitFrontmatter(body).content, { gfm: true })
.filter((token): token is Tokens.Heading => token.type === "heading")
.forEach((token, index) => {
if (index >= 24) return;
headings.push({
id: `heading-${index}`,
level: token.depth,
title: plain(token.tokens).trim(),
});
});
return headings;
}
export function jumpToHeading(id: string) {
const element = document.querySelector<HTMLElement>(
`.document-scroll [id="${id}"]`,
);
if (!element) return;
element.scrollIntoView({ behavior: "smooth", block: "start" });
element.classList.remove("heading-flash");
void element.offsetWidth;
element.classList.add("heading-flash");
}
export function MarkdownDocument({
body,
knownTitles,
onWiki,
}: {
body: string;
knownTitles?: readonly string[];
onWiki?: (target: string) => void;
}) {
const parsed = useMemo(() => splitFrontmatter(body), [body]);
const html = useMemo(
() => markdownHtml(parsed.content, knownTitles),
[parsed.content, knownTitles],
);
return (
<article
className="markdown-document"
onClick={(event) => {
const element = (event.target as HTMLElement).closest<HTMLElement>(
"[data-wiki]",
);
if (element?.dataset.wiki && onWiki) onWiki(element.dataset.wiki);
}}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}

View file

@ -0,0 +1,27 @@
//! 模块形状声明 · 插座制(冰朔 2026-08-15 架构谕)
//!
//! 软件只认这一个插座形状:每个模块 = manifest我是谁/插哪个口/从哪来)
//! + index渲染入口导出。常驻模块住在频道里将来 origin='repo' 的
//! 模块躺代码仓库,人格体按需拉取部署。
export const knowledgeRenderManifest = {
moduleId: "hololake.knowledge-render",
name: "知识渲染件",
version: "0.1.0",
slot: "knowledge-render",
origin: "resident",
exports: [
"splitFrontmatter",
"documentOutline",
"jumpToHeading",
"MarkdownDocument",
],
} as const;
export type ModuleManifest = {
moduleId: string;
name: string;
version: string;
slot: string;
origin: "resident" | "repo";
exports: readonly string[];
};

View file

@ -459,6 +459,32 @@ main {
overflow: hidden;
background: rgba(5, 17, 31, 0.76);
}
.knowledge-workspace.complete {
grid-template-columns: 260px minmax(0, 1fr) 232px;
height: min(720px, calc(100vh - 175px));
min-height: 560px;
}
.knowledge-workspace.complete.inspector-closed {
grid-template-columns: 260px minmax(0, 1fr);
}
.knowledge-browser {
min-height: 0;
overflow: auto;
}
.browser-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 2px 13px;
}
.browser-title strong {
font-size: 12px;
color: #cad8e6;
}
.browser-title span {
font-size: 10px;
color: #6f879e;
}
.knowledge-workspace > aside {
border-right: 1px solid var(--line);
padding: 13px;
@ -532,6 +558,197 @@ main {
font-family: "SFMono-Regular", Menlo, monospace;
font-size: 14px;
}
.document-workspace {
min-height: 0;
}
.document-scroll {
min-height: 0;
overflow: auto;
padding: 42px clamp(28px, 5vw, 70px) 70px;
}
.reader-heading {
padding-bottom: 25px;
margin-bottom: 25px;
border-bottom: 1px solid var(--line);
}
.reader-heading h1 {
font-size: 34px;
margin: 0 0 10px;
letter-spacing: -0.025em;
}
.reader-heading p {
color: #8097ad;
font-size: 12px;
margin: 0;
}
.meta-tags {
display: flex;
gap: 7px;
margin-top: 14px;
}
.meta-tags span {
padding: 4px 8px;
border-radius: 7px;
color: #b8d8ef;
background: rgba(71, 139, 188, 0.14);
font-size: 10px;
}
.markdown-document {
color: #cbd8e6;
font-size: 15px;
line-height: 1.9;
overflow-wrap: anywhere;
}
.markdown-document h1,
.markdown-document h2,
.markdown-document h3,
.markdown-document h4 {
color: #f0f5fb;
line-height: 1.35;
scroll-margin-top: 24px;
}
.markdown-document h1 {
font-size: 28px;
margin: 1.4em 0 0.6em;
}
.markdown-document h2 {
font-size: 22px;
margin: 1.7em 0 0.55em;
}
.markdown-document h3 {
font-size: 18px;
margin: 1.5em 0 0.5em;
}
.markdown-document p {
margin: 0 0 1.1em;
}
.markdown-document a,
.markdown-document .wiki-link {
color: #84c9f3;
}
.markdown-document .wiki-link {
display: inline;
border: 0;
padding: 0 2px;
background: transparent;
text-decoration: underline;
text-underline-offset: 3px;
}
.markdown-document blockquote.callout {
margin: 1.3em 0;
padding: 14px 17px;
border-left: 3px solid #74bee9;
border-radius: 0 10px 10px 0;
background: rgba(72, 137, 181, 0.1);
}
.markdown-document blockquote.callout-amber {
border-left-color: var(--gold);
background: rgba(205, 153, 64, 0.1);
}
.markdown-document blockquote.callout-rose {
border-left-color: #df8991;
background: rgba(190, 83, 94, 0.1);
}
.markdown-document blockquote.callout-mint {
border-left-color: var(--green);
background: rgba(81, 170, 119, 0.1);
}
.markdown-document pre {
overflow: auto;
padding: 16px;
border: 1px solid var(--line);
border-radius: 12px;
background: rgba(1, 7, 14, 0.65);
}
.markdown-document code {
font-family: "SFMono-Regular", Menlo, monospace;
font-size: 0.9em;
}
.markdown-document table {
width: 100%;
border-collapse: collapse;
margin: 1.4em 0;
}
.markdown-document th,
.markdown-document td {
padding: 9px 11px;
border-bottom: 1px solid var(--line);
text-align: left;
}
.markdown-document img {
max-width: 100%;
border-radius: 12px;
}
.heading-flash {
animation: headingFlash 1.2s ease;
}
@keyframes headingFlash {
0% {
color: var(--gold);
text-shadow: 0 0 18px rgba(240, 197, 107, 0.4);
}
}
.document-inspector {
min-height: 0;
overflow: auto;
border-left: 1px solid var(--line);
border-right: 0 !important;
padding: 0 !important;
}
.document-inspector > header {
padding: 18px 16px;
border-bottom: 1px solid var(--line);
}
.document-inspector > header strong,
.document-inspector > header span {
display: block;
}
.document-inspector > header strong {
font-size: 13px;
}
.document-inspector > header span {
color: #72899f;
font-size: 10px;
margin-top: 4px;
}
.outline-list {
padding: 10px 8px 18px;
border-bottom: 1px solid var(--line);
}
.outline-list button {
display: block;
width: 100%;
border: 0;
background: transparent;
text-align: left;
color: #9fb2c5;
font-size: 11px;
line-height: 1.45;
padding-block: 7px;
}
.outline-list p {
color: #72899f;
font-size: 11px;
padding: 8px;
}
.document-evidence {
padding: 14px 16px;
margin: 0;
}
.document-evidence div {
margin-bottom: 13px;
}
.document-evidence dt {
color: #6f879e;
font-size: 10px;
}
.document-evidence dd {
color: #bdcddd;
font-size: 11px;
line-height: 1.5;
margin: 4px 0 0;
word-break: break-all;
}
.split-list {
display: grid;
grid-template-columns: 1fr 1fr;
@ -848,6 +1065,13 @@ main {
.knowledge-workspace {
grid-template-columns: 210px 1fr;
}
.knowledge-workspace.complete,
.knowledge-workspace.complete.inspector-closed {
grid-template-columns: 210px minmax(0, 1fr);
}
.document-inspector {
display: none;
}
.split-list {
grid-template-columns: 1fr;
}

View file

@ -10,6 +10,8 @@
"canonical_tcs": "product-source/hololake-clean-desktop/language/HOLOLAKE-CLEAN-V1-FOUNDATION.tcs",
"old_product_role": "READ_ONLY_DONOR_NO_WHOLE_MERGE",
"first_preinstalled_module": "HLP-MOD-KB-0001",
"knowledge_module_state": "LOCAL_COMPLETE_HUMAN_VISUAL_RENDERING_CREATE_READ_EDIT_IMPORT_EXPORT_TRASH_PRIVATE_GIT",
"knowledge_human_rendering_source": "product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx",
"typed_language_sources": ["USER_MESSAGE", "PERSONA_RESPONSE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"],
"public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json",
"public_update_release_state": "SERVER_HTTP_204_NO_PUBLIC_RELEASE",