@dsh-libing/knowledge-base 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +80 -0
  3. package/cordis.patch.yml +3 -0
  4. package/icon.svg +6 -0
  5. package/lib/chunks.d.ts +8 -0
  6. package/lib/chunks.js +197 -0
  7. package/lib/client-types/client/BrowsePanel.d.ts +13 -0
  8. package/lib/client-types/client/Icon.d.ts +4 -0
  9. package/lib/client-types/client/ImageReview.d.ts +20 -0
  10. package/lib/client-types/client/ImportPanel.d.ts +19 -0
  11. package/lib/client-types/client/JobsPanel.d.ts +6 -0
  12. package/lib/client-types/client/KnowledgePage.d.ts +7 -0
  13. package/lib/client-types/client/Metadata.d.ts +10 -0
  14. package/lib/client-types/client/QualityReport.d.ts +6 -0
  15. package/lib/client-types/client/SettingsPanel.d.ts +6 -0
  16. package/lib/client-types/client/SourceText.d.ts +3 -0
  17. package/lib/client-types/client/UsageGuide.d.ts +8 -0
  18. package/lib/client-types/client/api.d.ts +953 -0
  19. package/lib/client-types/client/common.d.ts +11 -0
  20. package/lib/client-types/client/index.d.ts +14 -0
  21. package/lib/client-types/client/locales.d.ts +284 -0
  22. package/lib/client-types/client/searchCommand.d.ts +2 -0
  23. package/lib/client-types/client/useKnowledge.d.ts +251 -0
  24. package/lib/client-types/contract.d.ts +1197 -0
  25. package/lib/client-types/data.d.ts +1972 -0
  26. package/lib/client-types/settings.d.ts +33 -0
  27. package/lib/client-types/shared.d.ts +95 -0
  28. package/lib/client.js +305 -0
  29. package/lib/config.d.ts +13 -0
  30. package/lib/config.js +13 -0
  31. package/lib/contract.d.ts +1197 -0
  32. package/lib/contract.js +18 -0
  33. package/lib/data.d.ts +1972 -0
  34. package/lib/data.js +80 -0
  35. package/lib/document/convert.d.ts +5 -0
  36. package/lib/document/convert.js +325 -0
  37. package/lib/document/pdf-data.d.ts +99 -0
  38. package/lib/document/pdf-data.js +9 -0
  39. package/lib/document/pdf-layout.d.ts +24 -0
  40. package/lib/document/pdf-layout.js +234 -0
  41. package/lib/document/pdf-session.d.ts +39 -0
  42. package/lib/document/pdf-session.js +127 -0
  43. package/lib/document/pdf-vision.d.ts +26 -0
  44. package/lib/document/pdf-vision.js +115 -0
  45. package/lib/document/pdf-worker.d.ts +1 -0
  46. package/lib/document/pdf-worker.js +210 -0
  47. package/lib/document/pdf.d.ts +3 -0
  48. package/lib/document/pdf.js +218 -0
  49. package/lib/document/zip.d.ts +3 -0
  50. package/lib/document/zip.js +59 -0
  51. package/lib/figures/process.d.ts +187 -0
  52. package/lib/figures/process.js +159 -0
  53. package/lib/figures/prompts.d.ts +7 -0
  54. package/lib/figures/prompts.js +27 -0
  55. package/lib/index.d.ts +8 -0
  56. package/lib/index.js +42 -0
  57. package/lib/indexes.d.ts +38 -0
  58. package/lib/indexes.js +101 -0
  59. package/lib/jobs.d.ts +2806 -0
  60. package/lib/jobs.js +435 -0
  61. package/lib/models.d.ts +24 -0
  62. package/lib/models.js +120 -0
  63. package/lib/retrieval.d.ts +209 -0
  64. package/lib/retrieval.js +173 -0
  65. package/lib/routes.d.ts +3 -0
  66. package/lib/routes.js +152 -0
  67. package/lib/runtime.d.ts +48 -0
  68. package/lib/runtime.js +41 -0
  69. package/lib/settings.d.ts +33 -0
  70. package/lib/settings.js +19 -0
  71. package/lib/shared.d.ts +95 -0
  72. package/lib/shared.js +17 -0
  73. package/lib/skills.d.ts +3 -0
  74. package/lib/skills.js +21 -0
  75. package/lib/store.d.ts +409 -0
  76. package/lib/store.js +328 -0
  77. package/lib/tools.d.ts +3 -0
  78. package/lib/tools.js +72 -0
  79. package/lib/util.d.ts +25 -0
  80. package/lib/util.js +89 -0
  81. package/package.json +125 -0
  82. package/skills/libing-kb-build/SKILL.md +10 -0
  83. package/skills/libing-kb-search/SKILL.md +10 -0
  84. package/skills/libing-knowledge-base/SKILL.md +15 -0
  85. package/skills/libing-knowledge-base/references/build.md +23 -0
  86. package/skills/libing-knowledge-base/references/search.md +21 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - 图片复核改用宽幅双栏与窄屏上下布局,普通文字编辑描述和标识,按需展开类型及技术字段;原图可放大查看,补充各项操作影响,保持标题与底部按钮可见。
6
+
7
+ - 详情及任务字段提供含义说明,最近操作一条一行、无空白行;长质量报告使用独立滚动区、13px 文字和问题分类,保留完整明细。
8
+ - 待复核任务增加明确的草稿发布与缺口确认入口,区分提交和完成;页头使用说明提供建库流程、按库名的检索指令、普通 query 示例和六个工具的用途及问法。
9
+
10
+ - 首页打开及弹窗操作按钮统一为官方 32px 控件;新建与回收确认弹窗使用 Portal 独立样式、上下排列标签及铺满内容区的官方 Input,输入框自动获得焦点。
11
+
12
+ - 侧边栏复用包内透明线条 logo 与宿主尺寸,采用随主题变化的单色;工作台采用官方插件页的 960px 内容区、标题、留白、32px 页头按钮和简洁知识库列表。
13
+
14
+ - 从旧 knowledge-base 迁移到 strict TypeScript 和 DSH rc.2 服务,拆分版本存储、Word/PDF 转换、模型预算、持久任务、原文检索与客户端工作台。
15
+ - 提供六个 libing_kb 工具、三个内置技能,以及建库、上传/范围预检、任务/草稿、固定版本阅读、图片复核和回收管理页面。
16
+ - 使用独立数据根、原件/片段完整性检查、可取消 PDF worker、持久检查点及显式预算/缺口确认;卸载等待工作退出后释放数据根。
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @dsh-libing/knowledge-base
2
+
3
+ 适配 DSH `0.2.0-rc.2` 的 TypeScript 知识库插件。将 Word/PDF 转为分章节的原文片段,保留表格、图片、来源和不可变版本;在 DSH 侧边栏「知识库」管理导入、构建任务、检索、图片复核与回收站。
4
+
5
+ ## 安装与开始
6
+
7
+ ```sh
8
+ dsh plugin --profile web add --save-exact @dsh-libing/knowledge-base
9
+ dsh web
10
+ ```
11
+
12
+ 尚未发布时,在仓库执行 `pnpm dev:market --port 18786`,从 LiBing 市场安装本地构建。页面先新建知识库,再上传 `.docx` 或 `.pdf` 开始构建。`.doc` 需额外配置本机 LibreOffice 可执行文件的绝对路径。纯文字导入无需模型;图片理解和扫描 PDF 的版面恢复需要在 DSH 配置支持图片的模型,并在导入选项或插件配置中选定。摘要模型未配置时优先使用宿主默认模型;没有模型则使用原文摘要。
13
+
14
+ PDF 可以先预检:只抽样核对页数、书签及 OCR 可能性,不调用模型。页范围按原件实际页序、从 1 开始,包含结束页。相同文档 ID 替换当前内容;改变已有 PDF 的部分导入范围必须明确确认,历史版本仍保留。默认按文件名生成文档 ID,替换其他名称的文档可在高级选项填写原文档 ID。
15
+
16
+ ## 任务与复核
17
+
18
+ 任务、逐页收据、模型调用用量和已完成检查点保存在磁盘。取消后可重试;卸载时取消并等待任务退出,重载后继续队列。模型调用和已报告 tokens 受到预算限制,耗尽后暂停,只有明确提高额度才继续。重试复用可用缓存;供应商未报告的用量不能精确计入 tokens。
19
+
20
+ 图片失败、扫描页无法恢复或文档覆盖缺口默认进入「待复核」,原来的已发布版本继续可用。草稿只允许在管理页面预览,Agent 工具不检索草稿。没有阻塞问题时自动发布,任务显示「已发布」;普通解析提示不一定阻止发布。「待复核」任务提供「查看待发布草稿」与「发布草稿」两个入口,后者须在弹窗中确认接受报告中的覆盖缺口。提交后仍需等待任务变为「已发布」;接受缺口不会补回遗漏的图片、扫描页或复杂对象,正式检索仍提示缺口。人工图片描述、装饰图确认、忽略一次引用、恢复和重新识别都生成新任务与新版本;保存时核对当前版本及旧复核哈希,防止并发覆盖。图片描述是生成内容,应结合原图核对。
21
+
22
+ 章节导航和摘要用于定位,证据来自原文片段。检索按关键词、原始标识符与中文词组匹配,提供固定 revision、原件哈希、文档版本、片段路径及 PDF 页/区域。大段原文使用有来源行号的语义窗口与继续读取位置。复制引用后可长期按同一 revision 读取;当前库更新不会让已经打开的阅读器自动切换。回收知识库后该库不再向 Agent 开放,恢复后版本重新可用。
23
+
24
+ ## 工具与技能
25
+
26
+ | 工具 | 用途 |
27
+ | --- | --- |
28
+ | `libing_kb_build` | 从本机文件或上传收据创建持久构建任务;可显式新建库 |
29
+ | `libing_kb_job` | 查询、取消、重试;明确调整预算或接受缺口 |
30
+ | `libing_kb_catalog` | 列库、章节与分页导航 |
31
+ | `libing_kb_search` | 在指定正式版本检索原文与来源 |
32
+ | `libing_kb_read` | 阅读片段、匹配窗口或接着上次位置读取 |
33
+ | `libing_kb_read_image` | 将该正式版本中的图片作为真实附件交给支持图片的 Agent |
34
+
35
+ 包内提供 `libing-knowledge-base`、`libing-kb-build`、`libing-kb-search` 三个模型/用户均可调用的技能,无需复制到用户技能目录。页头「使用说明」包含建库流程、按知识库名称生成的检索命令,以及 6 个工具的用途和普通问法;未发布库不生成检索命令。知识库 ID 留作内部版本与引用追溯,日常使用只需库名。
36
+
37
+ 将指令粘贴到 **DSH 会话输入框**(不是终端):
38
+
39
+ ```text
40
+ /libing-kb-search "文档中有哪些关键要求?请引用原文来源。" --kb "接口知识库"
41
+ /libing-kb-build "/绝对路径/手册.pdf" --kb "接口知识库" --pages 11-23
42
+ /libing-knowledge-base 请列出可用知识库及文档目录
43
+ ```
44
+
45
+ 问题、库名和路径含空格时加双引号。导入路径是 DSH 所在电脑上的真实路径;也可从页面上传文档。首次建库需明确要求“新建知识库”;`--pages` 指实际 PDF 页序,从 1 开始且包含结束页。工具由 Agent 调用,参数 `kbId` 已同时接受唯一库名与内部 ID,用户不必填写工具 JSON。
46
+
47
+ 普通 query 也可这样说:「请使用知识库‘接口知识库’回答……;只依据已发布原文,注明文档版本、章节和片段,资料不足时明确说明。」常见操作直接说明意图:列出知识库、查找关键要求、补读刚才结果的前后文、核对引用原图,或查看刚才的构建任务。会话需要提供相应技能与 `libing_kb_*` 工具,并配置可用模型;原图核对需要支持图片的模型,技能中心可查看是否已选用技能。
48
+
49
+ 会话检索默认由 Agent 查找已发布版本,再固定同一次回答的引用与续读。页面「浏览与搜索」的关键词检索不调用模型;其中「在所选会话检索」通过官方命令接口发给明确选择的现有会话,指令按库名表达,并附带 `--revision` 保持当前阅读的正式版本,草稿不能发送。更新后如需最新内容,应先选择「打开最新版本」。此前的 JSON 检索指令继续兼容。
50
+
51
+ 工具和技能使用 `libing` 前缀,避免与旧插件同时启用时重名。默认数据根为 `$DSH_HOME/libing/knowledge-base`(未设置 `DSH_HOME` 时位于 `~/.dsh`),不自动移动或改写旧插件数据。迁移既有库建议从保留原件重新导入;不要把未经备份/验证的旧数据目录直接作为新插件的数据根。
52
+
53
+ ## 配置与边界
54
+
55
+ 在官方插件管理中配置,主要字段:
56
+
57
+ | 配置 | 默认值 |
58
+ | --- | --- |
59
+ | `dataRoot` | 独立数据根,必须为绝对路径 |
60
+ | `chunkTargetChars` / `evidenceMaxChars` | 6000 / 12000 字符(含来源和结构信息) |
61
+ | `visionProvider` / `visionModel` | 空;两者成对配置 |
62
+ | `summaryProvider` / `summaryModel` | 空;两者成对配置 |
63
+ | `maxModelCalls` / `maxModelTokens` | 每任务 1000 / 2000000 |
64
+ | `modelTimeoutMs` / `modelAttempts` | 90000 / 3 |
65
+ | `maxInputBytes` / `maxUncompressedBytes` | 64 MiB / 256 MiB |
66
+ | `maxArchiveEntries` | 10000 |
67
+ | `pdfMaxPages` / `pdfMaxPagePixels` / `pdfPageTimeoutMs` | 2000 / 12000000 / 60000 |
68
+ | `libreOfficePath` | 空,只有 `.doc` 需要 |
69
+
70
+ DOCX 压缩包、上传流、图片和 PDF 渲染均有限制;PDF 在可取消的独立 worker 中解析。复杂版面、扫描文字和生成图片描述仍需人工核对;加密 PDF 需先解除密码。切分保留合并单元格、表头及图文组合,不强行截断不可分的超长结构,报告会列出超出目标的片段。
71
+
72
+ 同一个数据根只能运行一个任务服务,避免两个 profile 同时写入。浏览器通过 DSH 官方 Connection 认证,上传后只提交收据,不接收任意本机路径。原件/图片读取核对版本、路径边界和内容哈希。彻底删除仅限回收站中的库,需要完整名称和不可恢复确认;会删除该库原件、历史与关联任务,上传收据和其他库不受影响。
73
+
74
+ 本插件不需要向量数据库、Python、外部 PDF 命令或 CI。实际验证平台为 macOS、Node.js 24、DSH rc.2;Windows、LibreOffice `.doc` 转换与真实供应商识别质量需另行验收。
75
+
76
+ 工作台复用官方插件管理的内容宽度、留白、标题和 32px 操作按钮;新建与回收确认弹窗用官方 Input,在标签下方铺满弹窗内容区。导航与包 logo 共用 `icon.svg`:导航使用随主题变化的单色,市场与官方插件管理保留资源的识别颜色。
77
+
78
+ 首页详情显示带说明的知识库 ID、创建时间和当前正式版本;最近操作一条一行,行间不再留空行。任务中模型调用与 tokens 为累计用量 / 预算上限,页、图片、片段分别表示对应阶段进度;未报告的 tokens 不能精确计入。质量报告区可用滚轮或键盘上下滚动,保留全部问题和技术明细,区分待处理问题与普通提示。
79
+
80
+ 图片复核使用双栏弹窗:左侧原图可在新窗口放大查看,右侧直接编辑描述和图中文字(每行一个标识);窄屏改为上下排列。图片类型、不确定项与完整结构化信息按需展开,识别出的关联和技术字段在保存时保留。重新识别、装饰图确认、忽略及恢复分别提供影响说明;它们直接重建且不保存正在编辑的人工描述,未保存修改会提示确认放弃。弹窗正文可滚动,页头和 32px 底部按钮保持可见。
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: libing-knowledge-base
3
+ name: '@dsh-libing/knowledge-base'
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 16 16" fill="none" stroke="#58766b" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M8.7 14.4H3.65a1.9 1.9 0 0 1-1.9-1.9V3.5a1.9 1.9 0 0 1 1.9-1.9h7.1a1.9 1.9 0 0 1 1.9 1.9v3.1"/>
3
+ <path d="M4.65 5.2h5.1M4.65 8.1h2.3M4.65 11h1.1"/>
4
+ <circle cx="10.85" cy="10.75" r="3.1"/>
5
+ <path d="m13.05 12.95 1.7 1.7"/>
6
+ </svg>
@@ -0,0 +1,8 @@
1
+ import type { Block, Chapter, ContentChunk, Document, DocInfo, Source } from './data.ts';
2
+ import type { Settings } from './config.ts';
3
+ export declare function sourceLocations(blocks: Block[]): Source[];
4
+ /** Split enriched, source-preserving blocks; each fragment retains its original block identity. */
5
+ export declare function createChunks(document: Document, docInfo: DocInfo, config: Settings): {
6
+ nodes: Chapter[];
7
+ chunks: ContentChunk[];
8
+ };
package/lib/chunks.js ADDED
@@ -0,0 +1,197 @@
1
+ import { required } from "./util.js";
2
+ import { posix } from 'node:path';
3
+ import { blockMarkdown } from "./document/convert.js";
4
+ import { figureMarkdown } from "./figures/process.js";
5
+ import { chars, escapeHtml, hash } from "./util.js";
6
+ function tableText(rows) {
7
+ return '<table>\n' + rows.map((row, i) => '<tr>' + row.map(c => `<${i ? 'td' : 'th'}${c.colspan > 1 ? ` colspan="${c.colspan}"` : ''}${c.rowspan > 1 ? ` rowspan="${c.rowspan}"` : ''}>${escapeHtml(c.text).replace(/\n/g, '<br>')}</${i ? 'td' : 'th'}>`).join('') + '</tr>').join('\n') + '\n</table>';
8
+ }
9
+ function splitParagraph(block, target) {
10
+ if (chars(block.text) <= target)
11
+ return [block];
12
+ const sentences = block.text.match(/[^。!?!?\n]+[。!?!?\n]*|[。!?!?\n]+/gu) || [block.text];
13
+ const parts = [];
14
+ let pending = '';
15
+ for (const sentence of sentences) {
16
+ if (pending && chars(pending + sentence) > target) {
17
+ parts.push({ ...block, text: pending, part: parts.length + 1 });
18
+ pending = '';
19
+ }
20
+ pending += sentence;
21
+ }
22
+ if (pending)
23
+ parts.push({ ...block, text: pending, part: parts.length + 1 });
24
+ return parts;
25
+ }
26
+ function splitTable(block, target) {
27
+ if (chars(block.markdown ?? '') <= target || block.rows.length < 3)
28
+ return [block];
29
+ const header = required(block.rows[0], '表头');
30
+ // A spanning header cannot safely be repeated or detached from its rows.
31
+ if (header.some(c => c.rowspan > 1))
32
+ return [{ ...block, oversizedReason: '表头合并行不能安全重复' }];
33
+ const groups = [];
34
+ let start = 1;
35
+ while (start < block.rows.length) {
36
+ let end = start + 1;
37
+ for (let i = start; i < end; i++)
38
+ for (const c of block.rows[i] || [])
39
+ end = Math.min(block.rows.length, Math.max(end, i + c.rowspan));
40
+ groups.push(block.rows.slice(start, end));
41
+ start = end;
42
+ }
43
+ const parts = [];
44
+ let rows = [];
45
+ const push = () => {
46
+ if (rows.length) {
47
+ const actual = [header, ...rows];
48
+ parts.push({ ...block, rows: actual, markdown: tableText(actual), text: actual.map(r => r.map(c => c.text).join(' | ')).join('\n'), part: parts.length + 1 });
49
+ rows = [];
50
+ }
51
+ };
52
+ for (const group of groups) {
53
+ if (rows.length && chars(tableText([header, ...rows, ...group])) > target)
54
+ push();
55
+ rows.push(...group);
56
+ }
57
+ push();
58
+ return parts;
59
+ }
60
+ export function sourceLocations(blocks) {
61
+ const result = [];
62
+ for (const b of blocks)
63
+ for (const location of (b.type === 'table' && b.part && b.rows.flat().some(c => c.source) ? b.rows.flat().flatMap(c => c.source ? [c.source] : []) : b.source?.locations || (b.source ? [b.source] : [])))
64
+ if (location.page) {
65
+ const source = { ...location };
66
+ delete source.locations;
67
+ const old = result.find(s => s.page === source.page && s.method === source.method && s.caption === (b.caption || ''));
68
+ if (old && old.bbox && source.bbox)
69
+ old.bbox = [Math.min(old.bbox[0], source.bbox[0]), Math.min(old.bbox[1], source.bbox[1]), Math.max(old.bbox[2], source.bbox[2]), Math.max(old.bbox[3], source.bbox[3])];
70
+ else if (!old)
71
+ result.push({ ...source, caption: b.caption || '' });
72
+ }
73
+ return result;
74
+ }
75
+ function metadata(chunk) {
76
+ const fields = ['chunkId', 'docId', 'sourceHash', 'sourceVersion', 'headingPath', 'blockRange', 'assetRefs', 'tableRefs', 'relatedChunks', 'reviewStatus', 'oversizedReason', 'sourceFormat', 'pageRange', 'selectedPages'];
77
+ return '---\n' + fields.filter(k => chunk[k] !== undefined).map(k => `${k}: ${JSON.stringify(chunk[k])}`).join('\n') + '\n---\n\n';
78
+ }
79
+ /** Split enriched, source-preserving blocks; each fragment retains its original block identity. */
80
+ export function createChunks(document, docInfo, config) {
81
+ const nodes = [{ nodeId: 'root', title: docInfo.title, level: 0, parentId: null, headingPath: [], children: [], blocks: [] }], stack = [nodes[0]];
82
+ const ignored = new Set(document.blocks.filter(b => b.type === 'figure' && b.figure?.status === 'ignored').map(b => b.occurrenceId));
83
+ let number = 0;
84
+ for (const block of document.blocks) {
85
+ // Exclude occurrences, not asset IDs: an identical image can be kept elsewhere.
86
+ if (ignored.has(block.occurrenceId) || Boolean(block.figureRef && ignored.has(block.figureRef)))
87
+ continue;
88
+ if (block.type === 'heading') {
89
+ while (stack.length > 1 && required(stack.at(-1), '章节').level >= block.level)
90
+ stack.pop();
91
+ const parent = required(stack.at(-1), '父章节'), node = { nodeId: `s-${String(++number).padStart(4, '0')}`, title: block.text, level: block.level, parentId: parent.nodeId, headingPath: [...parent.headingPath, block.text], children: [], blocks: [], anchor: block.anchor };
92
+ parent.children.push(node.nodeId);
93
+ nodes.push(node);
94
+ stack.push(node);
95
+ }
96
+ else
97
+ required(stack.at(-1), '章节').blocks.push(block);
98
+ }
99
+ const chunks = [];
100
+ for (const node of nodes) {
101
+ const units = [];
102
+ for (let i = 0; i < node.blocks.length; i++) {
103
+ const block = required(node.blocks[i], '正文块');
104
+ const budget = Math.max(128, config.chunkTargetChars - chars(metadata({ docId: docInfo.docId, headingPath: node.headingPath })) - 400);
105
+ const parts = block.type === 'table' ? splitTable(block, budget) : ['paragraph', 'list', 'note'].includes(block.type) ? splitParagraph(block, budget) : [block];
106
+ for (const part of parts) {
107
+ const unit = { blocks: [part] };
108
+ if (part.type === 'figure' && node.blocks[i + 1]?.figureRef === part.occurrenceId) {
109
+ unit.blocks.push(required(node.blocks[++i], '关联图注'));
110
+ }
111
+ if (part.type === 'table' && /^(?:注[::]?|Note\b)/i.test(node.blocks[i + 1]?.text || ''))
112
+ unit.blocks.push(required(node.blocks[i + 1], '表注'));
113
+ units.push(unit);
114
+ }
115
+ if (block.type === 'table' && /^(?:注[::]?|Note\b)/i.test(node.blocks[i + 1]?.text || ''))
116
+ i++;
117
+ }
118
+ const heading = `# ${docInfo.title}\n\n${node.headingPath.length ? '## ' + node.headingPath.join(' / ') + '\n\n' : ''}`;
119
+ let group = [];
120
+ const ancestors = [];
121
+ let ancestor = node;
122
+ while (ancestor.parentId) {
123
+ ancestor = required(nodes.find(n => n.nodeId === ancestor.parentId), '父章节');
124
+ if (ancestor.nodeId !== 'root')
125
+ ancestors.unshift(ancestor.nodeId);
126
+ }
127
+ const dir = posix.join('wiki/documents', docInfo.docId, ...ancestors, node.nodeId === 'root' ? 'intro' : node.nodeId);
128
+ const sourcePrefix = posix.relative(dir, posix.join('sources', docInfo.docId));
129
+ const render = (unit) => unit.blocks.map(b => blockMarkdown(b, document.assets, sourcePrefix) + (b.type === 'figure' ? '\n\n' + figureMarkdown(b) : '')).join('\n\n');
130
+ const make = () => {
131
+ if (!group.length)
132
+ return;
133
+ const chunkId = `c-${docInfo.docId}-${String(chunks.length + 1).padStart(5, '0')}`;
134
+ const blocks = group.flatMap(u => u.blocks), body = heading + group.map(render).join('\n\n') + '\n';
135
+ const figureResults = blocks.filter(b => b.type === 'figure').map(b => required(b.figure, '图片处理结果'));
136
+ const value = { content: '', fullChars: 0, bodyChars: 0, contentHash: '', summary: '', keywords: [], chunkId, docId: docInfo.docId, sourceHash: docInfo.sourceHash, sourceVersion: docInfo.sourceVersion || '', title: node.title, nodeId: node.nodeId, headingPath: node.headingPath, blockRange: [required(blocks[0], '起始块').blockId, required(blocks.at(-1), '终止块').blockId], assetRefs: [...new Set(blocks.flatMap(b => [b.assetId, b.sourcePreviewAssetId, ...b.sourcePreviewAssetIds || []].filter((x) => typeof x === 'string' && Boolean(x))))], tableRefs: [...new Set(blocks.filter(b => b.type === 'table').map(b => b.blockId))], relatedChunks: [], reviewStatus: blocks.some(b => b.needsReview) || figureResults.some(f => !f || ['failed', 'skipped'].includes(f.status) || f.needsReview && !f.humanReviewed) ? 'needs-review' : figureResults.some(f => !f.humanReviewed) ? 'generated' : 'source', path: posix.join(dir, chunkId + '.md'), body, segments: group.map(u => ({ text: render(u), blockIds: u.blocks.map(b => b.blockId), type: required(u.blocks[0], '语义块').type, ...document.sourceFormat === 'pdf' ? { sourceLocations: sourceLocations(u.blocks) } : {} })), anchors: blocks.flatMap(b => b.anchor ? [b.anchor] : []) };
137
+ if (document.sourceFormat === 'pdf') {
138
+ value.sourceFormat = 'pdf';
139
+ value.sourceLocations = sourceLocations(blocks);
140
+ const pages = required(value.sourceLocations).map(s => s.page);
141
+ value.pageRange = [Math.min(...pages), Math.max(...pages)];
142
+ value.selectedPages = document.selectedPages;
143
+ }
144
+ if (chars(body + metadata(value)) > config.chunkTargetChars)
145
+ value.oversizedReason = blocks.find(b => b.oversizedReason)?.oversizedReason || '完整图文、合并表行或单句超过目标,保留语义块';
146
+ chunks.push(value);
147
+ group = [];
148
+ };
149
+ for (const unit of units) {
150
+ if (group.length && chars(heading + [...group, unit].map(render).join('\n\n')) > config.chunkTargetChars - 800)
151
+ make();
152
+ group.push(unit);
153
+ }
154
+ make();
155
+ }
156
+ for (let i = 0; i < chunks.length; i++) {
157
+ const c = required(chunks[i]);
158
+ c.relatedChunks = [chunks[i - 1], chunks[i + 1]].flatMap(x => x ? [x.chunkId] : []);
159
+ c.content = metadata(c) + c.body;
160
+ c.fullChars = chars(c.content);
161
+ c.bodyChars = chars(c.body);
162
+ c.contentHash = hash(c.content);
163
+ }
164
+ const anchorMap = {};
165
+ for (const chunk of chunks)
166
+ for (const anchor of chunk.anchors)
167
+ anchorMap[anchor] = chunk.chunkId;
168
+ for (const node of nodes)
169
+ if (node.anchor)
170
+ anchorMap[node.anchor] = chunks.find(c => c.nodeId === node.nodeId || c.headingPath.join('/').startsWith(node.headingPath.join('/')))?.chunkId;
171
+ for (const chunk of chunks) {
172
+ for (const ref of document.internalRefs || [])
173
+ if (chunk.body.includes(`](#${ref.target})`) && anchorMap[ref.target])
174
+ chunk.relatedChunks.push(required(anchorMap[ref.target]));
175
+ chunk.relatedChunks = [...new Set(chunk.relatedChunks)].filter(x => x !== chunk.chunkId);
176
+ const rewrite = (value) => value.replace(/\]\(#([^)]+)\)/g, (match, anchor) => {
177
+ const target = chunks.find(c => c.chunkId === anchorMap[anchor]);
178
+ return target ? `](${posix.relative(posix.dirname(chunk.path), target.path)})` : match;
179
+ });
180
+ chunk.body = rewrite(chunk.body);
181
+ for (const segment of chunk.segments)
182
+ segment.text = rewrite(segment.text);
183
+ chunk.content = metadata(chunk) + chunk.body;
184
+ chunk.fullChars = chars(chunk.content);
185
+ chunk.contentHash = hash(chunk.content);
186
+ let cursor = chunk.content.indexOf(chunk.body);
187
+ for (const segment of chunk.segments) {
188
+ const start = chunk.content.indexOf(segment.text, cursor);
189
+ if (start < 0)
190
+ throw new Error('片段语义块无法定位');
191
+ segment.startLine = chunk.content.slice(0, start).split('\n').length;
192
+ segment.endLine = segment.startLine + segment.text.split('\n').length - 1;
193
+ cursor = start + segment.text.length;
194
+ }
195
+ }
196
+ return { nodes: nodes.map(({ blocks: _blocks, ...node }) => node), chunks };
197
+ }
@@ -0,0 +1,13 @@
1
+ import type { LibraryView } from '../contract.ts';
2
+ import type { Actions } from './common.ts';
3
+ export interface SearchSession {
4
+ id: string;
5
+ label: string;
6
+ }
7
+ export declare function BrowsePanel({ library, draftId, t, busy, act, dirty, operationError, go, sessions, send }: {
8
+ library: LibraryView;
9
+ draftId?: string | undefined;
10
+ go: (action: () => void) => void;
11
+ sessions: SearchSession[];
12
+ send: (id: string, line: string) => Promise<boolean>;
13
+ } & Actions): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ /** Reuse the package silhouette in the shell's theme-aware navigation color. */
2
+ import { type ReactNode } from 'react';
3
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
+ export declare function KnowledgeIcon({ size }: Pick<PropsRuntime<'sidebar.panellist'>, 'size'>): ReactNode;
@@ -0,0 +1,20 @@
1
+ import type { Figure, FigureData } from '../data.ts';
2
+ import type { ModelDirectory, TreeDocument } from '../contract.ts';
3
+ import type { T } from './common.ts';
4
+ export type ReviewAction = 'save' | 'ignore' | 'decoration' | 'restore' | 'reidentify';
5
+ export declare function ImageReview({ t, doc, figure, imageUrl, body, setBody, vision, setVision, directory, busy, stale, error, close, action }: {
6
+ t: T;
7
+ doc: TreeDocument;
8
+ figure: Figure;
9
+ imageUrl: string;
10
+ body: FigureData;
11
+ setBody: (value: FigureData) => void;
12
+ vision: string;
13
+ setVision: (value: string) => void;
14
+ directory: ModelDirectory | undefined;
15
+ busy: boolean;
16
+ stale: boolean;
17
+ error: string;
18
+ close: () => void;
19
+ action: (kind: ReviewAction) => void;
20
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import type { State, ModelDirectory } from '../contract.ts';
2
+ import type { Actions } from './common.ts';
3
+ export declare function ModelSelect({ value, set, directory, label, image, defaultLabel }: {
4
+ value: string;
5
+ set: (value: string) => void;
6
+ directory: ModelDirectory | undefined;
7
+ label: string;
8
+ image?: boolean;
9
+ defaultLabel?: string;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ export declare function route(value: string): {
12
+ provider: string;
13
+ model: string;
14
+ } | null;
15
+ export declare function ImportPanel({ kbId, state, t, busy, act, dirty, submitted }: {
16
+ kbId: string;
17
+ state: State;
18
+ submitted: () => void;
19
+ } & Actions): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { PublicJob } from '../contract.ts';
2
+ import type { Actions } from './common.ts';
3
+ export declare function JobsPanel({ jobs, t, busy, act, operationError, preview }: {
4
+ jobs: PublicJob[];
5
+ preview: (jobId: string) => void;
6
+ } & Omit<Actions, 'dirty'>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { UseSessions } from '@deepseek-ai/dsh-client-ui-session/client';
3
+ export interface PageProps extends PropsLocale<'libingKnowledgeBase'> {
4
+ useSessions: UseSessions;
5
+ send: (id: string, line: string) => Promise<boolean>;
6
+ }
7
+ export declare function KnowledgePage({ t, useSessions, send }: PageProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface MetadataField {
3
+ label: string;
4
+ hint: string;
5
+ value: ReactNode;
6
+ }
7
+ /** Explain public values without requiring users to interpret an unlabeled JSON dump. */
8
+ export declare function Metadata({ fields }: {
9
+ fields: MetadataField[];
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { PublicJob } from '../contract.ts';
2
+ import type { T } from './common.ts';
3
+ export declare function QualityReport({ report, t }: {
4
+ report: NonNullable<PublicJob['report']>;
5
+ t: T;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { LibraryView } from '../contract.ts';
2
+ import type { Actions } from './common.ts';
3
+ export declare function SettingsPanel({ library, t, busy, act, dirty, remove }: {
4
+ library: LibraryView;
5
+ remove: () => void;
6
+ } & Actions): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export declare function SourceText({ text }: {
2
+ text: string;
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import type { LibraryView } from '../contract.ts';
2
+ import type { T } from './common.ts';
3
+ export declare function UsageGuide({ libraries, preferred, t, close }: {
4
+ libraries: LibraryView[];
5
+ preferred: string;
6
+ t: T;
7
+ close: () => void;
8
+ }): import("react/jsx-runtime").JSX.Element;