dsh-data-domain-knowledge 0.1.0-alpha.2 → 0.1.0-alpha.4
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.
- package/README.md +147 -19
- package/cordis.patch.yml +25 -10
- package/lib/client.js +6400 -3326
- package/lib/doc-page.js +7761 -0
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +63 -11
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +7 -4
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +52 -14
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/convert/types.d.ts +2 -2
- package/lib/types/convert/types.d.ts.map +1 -1
- package/lib/types/convert/types.js +2 -2
- package/lib/types/convert/types.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +8 -7
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +119 -49
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +57 -0
- package/lib/types/diagnosis/run.d.ts.map +1 -0
- package/lib/types/diagnosis/run.js +245 -0
- package/lib/types/diagnosis/run.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +25 -2
- package/lib/types/diagnosis/schema.d.ts.map +1 -1
- package/lib/types/diagnosis/schema.js +64 -25
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +17 -2
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +19 -2
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +32 -42
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +187 -51
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +95 -25
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +297 -166
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +168 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/document-page.d.ts +39 -0
- package/lib/types/document-page.d.ts.map +1 -0
- package/lib/types/document-page.js +43 -0
- package/lib/types/document-page.js.map +1 -0
- package/lib/types/extract-model.d.ts +67 -27
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js +225 -97
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract.d.ts +16 -0
- package/lib/types/extract.d.ts.map +1 -1
- package/lib/types/extract.js +19 -2
- package/lib/types/extract.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +11 -0
- package/lib/types/fs-atomic.d.ts.map +1 -0
- package/lib/types/fs-atomic.js +41 -0
- package/lib/types/fs-atomic.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +466 -63
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/types.js +1 -1
- package/lib/types/intent/types.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +23 -12
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +37 -44
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/layout.d.ts +3 -3
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +4 -4
- package/lib/types/layout.js.map +1 -1
- package/lib/types/migrate.d.ts +1 -1
- package/lib/types/migrate.js +2 -2
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/model-text.d.ts +99 -0
- package/lib/types/model-text.d.ts.map +1 -0
- package/lib/types/model-text.js +103 -0
- package/lib/types/model-text.js.map +1 -0
- package/lib/types/present.d.ts +239 -0
- package/lib/types/present.d.ts.map +1 -0
- package/lib/types/present.js +255 -0
- package/lib/types/present.js.map +1 -0
- package/lib/types/recall-rank.d.ts +37 -0
- package/lib/types/recall-rank.d.ts.map +1 -1
- package/lib/types/recall-rank.js +41 -1
- package/lib/types/recall-rank.js.map +1 -1
- package/lib/types/runtime.d.ts +564 -31
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +2645 -292
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +4 -2
- package/lib/types/settings-schema.d.ts.map +1 -1
- package/lib/types/settings-schema.js +2 -0
- package/lib/types/settings-schema.js.map +1 -1
- package/lib/types/source-images.d.ts +118 -0
- package/lib/types/source-images.d.ts.map +1 -0
- package/lib/types/source-images.js +288 -0
- package/lib/types/source-images.js.map +1 -0
- package/lib/types/types.d.ts +356 -28
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +112 -18
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +140 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -0
- package/lib/types/wiki-finalize.js +269 -0
- package/lib/types/wiki-finalize.js.map +1 -0
- package/lib/types/wiki-types.d.ts +10 -2
- package/lib/types/wiki-types.d.ts.map +1 -1
- package/lib/types/wiki-types.js +27 -30
- package/lib/types/wiki-types.js.map +1 -1
- package/package.json +4 -3
- package/preset/tita-data-agent/agent.cordis.yml +25 -10
- package/src/client/DirectoryPicker.tsx +12 -12
- package/src/client/DrawerSection.tsx +18 -0
- package/src/client/InfoHint.tsx +21 -8
- package/src/client/IntentTab.tsx +20 -26
- package/src/client/KnowledgeBaseTab.tsx +458 -158
- package/src/client/KnowledgePanel.tsx +44 -10
- package/src/client/KnowledgeStepRail.tsx +3 -0
- package/src/client/KnowledgeToolRow.tsx +331 -0
- package/src/client/NodeSettings.tsx +20 -22
- package/src/client/PreviewSections.tsx +178 -43
- package/src/client/RecallPanel.tsx +418 -127
- package/src/client/TypeDefinitionDrawer.tsx +154 -54
- package/src/client/WikiCardReview.tsx +266 -0
- package/src/client/WikiFlowCard.tsx +57 -95
- package/src/client/WikiTypeEditor.tsx +197 -95
- package/src/client/arch/AgentArchitectureGraph.tsx +30 -14
- package/src/client/arch/NodeLogicPanel.tsx +6 -3
- package/src/client/arch/model.ts +164 -26
- package/src/client/architecture.css +25 -10
- package/src/client/architecture.tsx +66 -41
- package/src/client/asset-images.ts +125 -0
- package/src/client/attribution/AttributionGraph.tsx +481 -34
- package/src/client/attribution/AttributionTab.tsx +501 -106
- package/src/client/attribution/CaseView.tsx +9 -14
- package/src/client/attribution/GraphStage.tsx +25 -18
- package/src/client/attribution/RunView.tsx +27 -9
- package/src/client/attribution/StageChrome.tsx +280 -140
- package/src/client/attribution/graph-options.ts +611 -47
- package/src/client/attribution/model.ts +559 -30
- package/src/client/convert/ConvertPanel.tsx +36 -4
- package/src/client/doc-page.css +134 -0
- package/src/client/doc-page.tsx +242 -0
- package/src/client/index.ts +18 -0
- package/src/client/panel-state.ts +105 -0
- package/src/client/problem-domains.ts +1 -1
- package/src/client/request.ts +36 -0
- package/src/client/styles.css +397 -54
- package/src/client/useFitScale.ts +15 -2
- package/src/client/wiki-type-groups.ts +86 -0
- package/src/convert/host.ts +68 -9
- package/src/convert/ingest.ts +61 -18
- package/src/convert/types.ts +2 -2
- package/src/diagnosis/cards.ts +120 -50
- package/src/diagnosis/run.ts +251 -0
- package/src/diagnosis/schema.ts +67 -26
- package/src/diagnosis/seed-source.ts +29 -4
- package/src/diagnosis/seed.ts +187 -85
- package/src/diagnosis/store.ts +310 -173
- package/src/diagnosis/types.ts +181 -16
- package/src/document-page.ts +51 -0
- package/src/extract-model.ts +254 -99
- package/src/extract.ts +35 -3
- package/src/fs-atomic.ts +41 -0
- package/src/index.ts +499 -62
- package/src/intent/types.ts +1 -1
- package/src/knowledge-bases.ts +50 -49
- package/src/layout.ts +4 -4
- package/src/migrate.ts +2 -2
- package/src/model-text.ts +126 -0
- package/src/present.ts +367 -0
- package/src/recall-rank.ts +57 -2
- package/src/runtime.ts +2802 -293
- package/src/settings-schema.ts +2 -0
- package/src/source-images.ts +326 -0
- package/src/types.ts +446 -45
- package/src/wiki-finalize.ts +310 -0
- package/src/wiki-types.ts +28 -32
|
@@ -16,6 +16,16 @@ import { useEffect, useState, type RefObject } from 'react'
|
|
|
16
16
|
/** 窄栏下的两档。`fit` = 整图缩到容器,`actual` = 1:1(滚动查看)。 */
|
|
17
17
|
export type ZoomMode = 'fit' | 'actual'
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 适应哪一条轴。
|
|
21
|
+
*
|
|
22
|
+
* - `both`(默认):宽高都缩进容器,缩完就是容器的整块面积 —— 知识图谱那一栏用它。
|
|
23
|
+
* - `width`:只按宽度缩,高度允许溢出 —— 概览画布用它。概览是一块**自持视口**的画布
|
|
24
|
+
* (见 `architecture.css` 的 `.tita-archScrollStage`),纵向溢出由空格 + 拖拽平移看,
|
|
25
|
+
* 而不是把整块板缩到读不出字。
|
|
26
|
+
*/
|
|
27
|
+
export type FitAxis = 'both' | 'width'
|
|
28
|
+
|
|
19
29
|
export interface FitScale {
|
|
20
30
|
scale: number
|
|
21
31
|
/** 缩放后的板高;容器高度就用它(`fit` 档下正好没有可滚的余量)。 */
|
|
@@ -31,6 +41,7 @@ export interface FitScale {
|
|
|
31
41
|
* @param enabled 容器不可见时不要量(隐藏容器尺寸为 0,算出来的比例是 0)
|
|
32
42
|
* @param fullscreen 全屏时两个方向都按容器算;窄栏只有宽度可用
|
|
33
43
|
* @param mode 缩放档
|
|
44
|
+
* @param fit 适应哪条轴;`width` 时高度永不算进比例(溢出交由平移)
|
|
34
45
|
*/
|
|
35
46
|
export function useFitScale(
|
|
36
47
|
scroller: RefObject<HTMLDivElement | null>,
|
|
@@ -39,6 +50,7 @@ export function useFitScale(
|
|
|
39
50
|
enabled: boolean,
|
|
40
51
|
fullscreen: boolean,
|
|
41
52
|
mode: ZoomMode,
|
|
53
|
+
fit: FitAxis = 'both',
|
|
42
54
|
): FitScale {
|
|
43
55
|
/**
|
|
44
56
|
* 可视尺寸。用 `offsetWidth/Height` 而不是 `clientWidth/clientHeight`:后者会随
|
|
@@ -60,9 +72,10 @@ export function useFitScale(
|
|
|
60
72
|
}, [enabled, fullscreen])
|
|
61
73
|
|
|
62
74
|
// 窄栏只有宽度是可用的:高度由板高自己决定,拿它去算比例就成了一条回路,
|
|
63
|
-
//
|
|
75
|
+
// 所以那里高度按设计高度算(即「只按宽度适应」)。`fit: 'width'` 把这条规则
|
|
76
|
+
// 从「窄栏的例外」提升成「这一栏的常态」—— 全屏下也不再把高度算进比例。
|
|
64
77
|
const availW = viewport.w || contentW
|
|
65
|
-
const availH = fullscreen ? (viewport.h || contentH) : contentH
|
|
78
|
+
const availH = fit === 'width' ? contentH : fullscreen ? (viewport.h || contentH) : contentH
|
|
66
79
|
const scale = mode === 'actual' ? 1 : Math.min(1, availW / contentW, availH / contentH)
|
|
67
80
|
return { scale, boardH: contentH * scale }
|
|
68
81
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiki 类型列表的**分组与排布计划**(0040 §4.3)。
|
|
3
|
+
*
|
|
4
|
+
* 抽成独立模块的理由是**它出过一次错,而且是静默的**:把「列表怎么摆」直接写在 JSX 的
|
|
5
|
+
* 三元里时,`prefix === ''` 那一支只渲染了 `members[0]` —— 于是六个没有斜杠的通用类型
|
|
6
|
+
* (concept / metric / table / event / playbook / case)在界面上只剩第一个,
|
|
7
|
+
* 另外五个**不见了**,而且没有任何报错。这里把「摆成几行、哪几个进折叠组」
|
|
8
|
+
* 变成一个可以单独断言的纯函数:它没有 React、没有样式、没有 DOM,输入是注册表、
|
|
9
|
+
* 输出是行清单。
|
|
10
|
+
*
|
|
11
|
+
* 三条规则(都是呈现层行为,不改注册表结构、不改 `entity_type`):
|
|
12
|
+
*
|
|
13
|
+
* 1. **没有斜杠的 id 各自平铺成一行。** 它们不构成一组 —— `concept` 与 `case` 之间
|
|
14
|
+
* 除了「都没有命名空间」之外没有任何共同点,把它们折成一组只是把六个类型藏进一个
|
|
15
|
+
* 名字叫空字符串的组里。
|
|
16
|
+
* 2. **同一个前缀的成员聚成一组,组内保持类型定义顺序**,组与组按前缀第一次出现的
|
|
17
|
+
* 位置排。注册表的顺序仍然是唯一真源,这里不重排、不排序。
|
|
18
|
+
* 3. **前缀下只有一个成员时不折叠**(折成只装一项的组,多一次点击、少一次直达),
|
|
19
|
+
* 那个成员直接平铺回一级列表。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface WikiTypeGroup<T> {
|
|
23
|
+
prefix: string
|
|
24
|
+
label: string
|
|
25
|
+
/** 组内保持注册表里的定义顺序。 */
|
|
26
|
+
members: T[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 一行要画什么:一个平铺的类型,还是一个可折叠的组。 */
|
|
30
|
+
export type WikiTypeRow<T> =
|
|
31
|
+
| { kind: 'row'; type: T }
|
|
32
|
+
| { kind: 'group'; prefix: string; label: string; members: T[] }
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 组名默认取前缀本身 —— 只有真正需要翻译的才写进这张表。
|
|
36
|
+
*
|
|
37
|
+
* 写死五个 `diagnosis/*` 的 id 是不行的:自定义类型也允许带斜杠,
|
|
38
|
+
* `diagnosis` 前缀下的类型是可增删的。
|
|
39
|
+
*/
|
|
40
|
+
export const GROUP_LABELS: Record<string, string> = { diagnosis: '归因图谱' }
|
|
41
|
+
|
|
42
|
+
export const idPrefix = (id: string): string => {
|
|
43
|
+
const at = id.indexOf('/')
|
|
44
|
+
return at > 0 ? id.slice(0, at) : ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const groupLabel = (prefix: string): string => GROUP_LABELS[prefix] ?? prefix
|
|
48
|
+
|
|
49
|
+
/** 按 id 前缀把类型折成组。顺序 = 前缀第一次出现的位置。 */
|
|
50
|
+
export function groupWikiTypes<T extends { id: string }>(types: T[]): Array<WikiTypeGroup<T>> {
|
|
51
|
+
const groups: Array<WikiTypeGroup<T>> = []
|
|
52
|
+
const byPrefix = new Map<string, WikiTypeGroup<T>>()
|
|
53
|
+
for (const type of types) {
|
|
54
|
+
const prefix = idPrefix(type.id)
|
|
55
|
+
const existing = byPrefix.get(prefix)
|
|
56
|
+
if (existing) { existing.members.push(type); continue }
|
|
57
|
+
const created: WikiTypeGroup<T> = { prefix, label: groupLabel(prefix), members: [type] }
|
|
58
|
+
byPrefix.set(prefix, created)
|
|
59
|
+
groups.push(created)
|
|
60
|
+
}
|
|
61
|
+
return groups
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 类型注册表 → 界面上的行清单。
|
|
66
|
+
*
|
|
67
|
+
* **不丢任何一个类型**是它的核心承诺:`rows` 里所有 `type` 加起来必须与输入一一对应。
|
|
68
|
+
* 测试钉住了这一条(见 `tests/wiki-type-groups.test.mjs`)—— 上一次出错时
|
|
69
|
+
* 界面少了五个类型,而所有既有测试都是绿的。
|
|
70
|
+
*/
|
|
71
|
+
export function wikiTypeRows<T extends { id: string }>(types: T[]): Array<WikiTypeRow<T>> {
|
|
72
|
+
const rows: Array<WikiTypeRow<T>> = []
|
|
73
|
+
for (const group of groupWikiTypes(types)) {
|
|
74
|
+
// 没有命名空间的照旧平铺 —— **每一个成员各占一行**,不是只画第一个。
|
|
75
|
+
if (group.prefix === '') {
|
|
76
|
+
for (const type of group.members) rows.push({ kind: 'row', type })
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
if (group.members.length === 1) {
|
|
80
|
+
rows.push({ kind: 'row', type: group.members[0]! })
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
rows.push({ kind: 'group', prefix: group.prefix, label: group.label, members: group.members })
|
|
84
|
+
}
|
|
85
|
+
return rows
|
|
86
|
+
}
|
package/src/convert/host.ts
CHANGED
|
@@ -156,7 +156,7 @@ function send(res: ServerResponse, status: number, body: unknown): void {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/** Compact task view for tools — the full ingest payload never enters context. */
|
|
159
|
-
function taskView(task: ConvertTask): Record<string, unknown> {
|
|
159
|
+
function taskView(kernel: ConvertService, task: ConvertTask): Record<string, unknown> {
|
|
160
160
|
const manifest = task.manifest
|
|
161
161
|
return {
|
|
162
162
|
taskId: task.id,
|
|
@@ -171,6 +171,8 @@ function taskView(task: ConvertTask): Record<string, unknown> {
|
|
|
171
171
|
error: task.error ?? null,
|
|
172
172
|
warnings: task.warnings.map(warning => warning.code),
|
|
173
173
|
attempts: task.attempts,
|
|
174
|
+
/** 与服务端 retry() 的守卫同源(`canRetryTask`):Agent 不该去点一枚注定失败的按钮。 */
|
|
175
|
+
canRetry: kernel.canRetry(task).ok,
|
|
174
176
|
deliveryDir: task.deliveryDir,
|
|
175
177
|
assetCount: manifest?.assets.length ?? 0,
|
|
176
178
|
wordCount: manifest?.wordCount ?? 0,
|
|
@@ -307,10 +309,24 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
307
309
|
config = { ...DEFAULT_CONVERT_CONFIG, ...readConfig() }
|
|
308
310
|
void resetService()
|
|
309
311
|
|
|
312
|
+
/**
|
|
313
|
+
* 写操作要用户点头。
|
|
314
|
+
*
|
|
315
|
+
* `convert_task` 是**一个混合工具**:`list` / `get` / `markdown` / `manifest` 只读,
|
|
316
|
+
* `cancel` / `retry` 会改队列。所以它不能整只进 `WRITE_TOOLS`(那会让「看一眼队列」
|
|
317
|
+
* 也要审批),也不能整只留在外面 —— 那样一个自称只读的工具就能取消别人的任务。
|
|
318
|
+
* 判据落在 `action` 上。
|
|
319
|
+
*/
|
|
310
320
|
ctx.on('tools/pre-execute', (exec, next): Promise<PreToolDecision> => {
|
|
311
321
|
if ((WRITE_TOOLS as readonly string[]).includes(exec.name)) {
|
|
312
322
|
return Promise.resolve({ kind: 'ask', reason: '该操作会转换本地内容,并写入 Tita Knowledge Store,需要本轮用户审批。' })
|
|
313
323
|
}
|
|
324
|
+
if (exec.name === 'convert_task') {
|
|
325
|
+
const action = (exec.arguments as { action?: unknown } | undefined)?.action
|
|
326
|
+
if (action === 'cancel' || action === 'retry') {
|
|
327
|
+
return Promise.resolve({ kind: 'ask', reason: `convert_task 的 ${String(action)} 会改动转换队列,需要本轮用户审批。` })
|
|
328
|
+
}
|
|
329
|
+
}
|
|
314
330
|
return next()
|
|
315
331
|
})
|
|
316
332
|
|
|
@@ -346,10 +362,11 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
346
362
|
|
|
347
363
|
ctx.tools.register(tool(
|
|
348
364
|
'convert_source',
|
|
349
|
-
'Convert a document (Word / PowerPoint / Excel / OpenDocument / RTF / EPUB / CSV / text-layer PDF) or inline text into knowledge-ready Markdown + local images. Async kernel task; returns taskId, delivery dir and ingest hints (source_type candidates, external_id, content_hash). Write operation: DSH asks the user once before execution.',
|
|
365
|
+
'Convert a document (Word / PowerPoint / Excel / OpenDocument / RTF / EPUB / CSV / text-layer PDF), a web page / WeChat article (a bare URL, or a saved .html/.htm page), local audio-video-image, or inline text into knowledge-ready Markdown + local images. Async kernel task; returns taskId, delivery dir and ingest hints (source_type candidates, external_id, content_hash). Write operation: DSH asks the user once before execution.',
|
|
350
366
|
{
|
|
351
|
-
kind: { type: 'string', enum: ['file', 'text'], required: true },
|
|
367
|
+
kind: { type: 'string', enum: ['file', 'text', 'url'], required: true },
|
|
352
368
|
path: { type: 'string' },
|
|
369
|
+
url: { type: 'string' },
|
|
353
370
|
text: { type: 'string' },
|
|
354
371
|
title: { type: 'string' },
|
|
355
372
|
source_type: { type: 'string' },
|
|
@@ -373,6 +390,7 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
373
390
|
kind,
|
|
374
391
|
...(candidates.length > 0 ? { sourceTypeCandidates: candidates } : {}),
|
|
375
392
|
...(typeof args.text === 'string' ? { text: args.text } : {}),
|
|
393
|
+
...(typeof args.url === 'string' ? { url: args.url } : {}),
|
|
376
394
|
...(typeof args.title === 'string' && args.title.trim() ? { title: args.title.trim() } : {}),
|
|
377
395
|
...(typeof args.source_type === 'string' && args.source_type.trim() ? { sourceType: args.source_type.trim(), sourceTypeSource: 'agent' as const } : {}),
|
|
378
396
|
...(typeof args.adapter === 'string' && args.adapter.trim() ? { adapter: args.adapter.trim() } : {}),
|
|
@@ -399,10 +417,33 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
399
417
|
if (!request.title) request.title = basename(absolute)
|
|
400
418
|
}
|
|
401
419
|
|
|
420
|
+
/*
|
|
421
|
+
* 链接档**必须**在宿主侧就把地址检查掉。
|
|
422
|
+
*
|
|
423
|
+
* 内核的 `resolveAdapter` 见到 `kind=url` 会直接交给 web 适配器,而适配器只认
|
|
424
|
+
* `request.url` —— 少了这个字段,报出来的是一句和用户输入毫无关系的 `URL_REQUIRED`。
|
|
425
|
+
* 这里挡住并给出「你要给的是哪一种」。
|
|
426
|
+
*/
|
|
427
|
+
if (kind === 'url') {
|
|
428
|
+
const raw = typeof args.url === 'string' ? args.url.trim() : ''
|
|
429
|
+
if (!raw) {
|
|
430
|
+
return failure({ taskId: null }, 'URL_REQUIRED', 'kind=url 必须提供 http(s) 地址 url。', false, '例如 https://mp.weixin.qq.com/s?__biz=...(公众号文章直接粘整条链接即可)。')
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const parsed = new URL(raw)
|
|
434
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
435
|
+
return failure({ taskId: null }, 'URL_INVALID', `只支持 http/https:${parsed.protocol}`, false, '本机文件请用 kind=file + path。')
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
return failure({ taskId: null }, 'URL_INVALID', `不是合法的地址:${raw}`, false, '整条链接一起粘过来,包含 https:// 前缀。')
|
|
439
|
+
}
|
|
440
|
+
request.url = raw
|
|
441
|
+
}
|
|
442
|
+
|
|
402
443
|
const submitted = await kernel.submit(request)
|
|
403
444
|
const shouldWait = args.wait !== false
|
|
404
445
|
const finished = shouldWait ? await waitForTask(kernel, submitted.id, config.toolWaitMs) : submitted
|
|
405
|
-
const view = taskView(finished ?? submitted)
|
|
446
|
+
const view = taskView(kernel, finished ?? submitted)
|
|
406
447
|
return success({
|
|
407
448
|
...view,
|
|
408
449
|
waited: shouldWait,
|
|
@@ -413,7 +454,7 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
413
454
|
|
|
414
455
|
ctx.tools.register(tool(
|
|
415
456
|
'convert_task',
|
|
416
|
-
'Inspect the conversion queue
|
|
457
|
+
'Inspect and steer the conversion queue. list / get / markdown / manifest are read-only; cancel and retry change the queue (both need user approval). retry only works on a failed or cancelled task whose staged input is still in tmp/ — a delivered task cannot be retried, because its input is cleaned up when it is delivered; submit the file again instead.',
|
|
417
458
|
{
|
|
418
459
|
action: { type: 'string', enum: ['list', 'get', 'cancel', 'retry', 'markdown', 'manifest'], required: true },
|
|
419
460
|
task_id: { type: 'string' },
|
|
@@ -425,12 +466,30 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
425
466
|
const action = String(args.action)
|
|
426
467
|
if (action === 'list') {
|
|
427
468
|
const limit = Number.isFinite(args.limit) ? Math.max(1, Math.min(100, Number(args.limit))) : 20
|
|
428
|
-
return success({ tasks: kernel.list().slice(0, limit).map(taskView) })
|
|
469
|
+
return success({ tasks: kernel.list().slice(0, limit).map(task => taskView(kernel, task)) })
|
|
429
470
|
}
|
|
430
471
|
const taskId = typeof args.task_id === 'string' ? args.task_id.trim() : ''
|
|
431
472
|
if (!taskId) return failure({ taskId: null }, 'TASK_ID_REQUIRED', `${action} 需要 task_id。`)
|
|
432
|
-
|
|
433
|
-
|
|
473
|
+
/*
|
|
474
|
+
* `cancel` / `retry` **会改队列**,所以它们不把 `AdapterError` 抛出去让宿主兜,
|
|
475
|
+
* 而是把 code 与 suggestion 如实放进 envelope:Agent 读到的应该是「为什么不能」
|
|
476
|
+
* 与「那该怎么办」(例如:已交付的任务要**重新提交文件**,而不是重试 ——
|
|
477
|
+
* 它的输入在交付后就清理了)。
|
|
478
|
+
*/
|
|
479
|
+
if (action === 'cancel' || action === 'retry') {
|
|
480
|
+
try {
|
|
481
|
+
return success(taskView(kernel, action === 'cancel' ? kernel.cancel(taskId) : await kernel.retry(taskId)))
|
|
482
|
+
} catch (cause) {
|
|
483
|
+
const error = cause as { code?: unknown; message?: unknown; suggestion?: unknown }
|
|
484
|
+
return failure(
|
|
485
|
+
{ taskId },
|
|
486
|
+
typeof error.code === 'string' ? error.code : 'CONVERT_TASK_FAILED',
|
|
487
|
+
typeof error.message === 'string' ? error.message : String(cause),
|
|
488
|
+
false,
|
|
489
|
+
typeof error.suggestion === 'string' ? error.suggestion : undefined,
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
434
493
|
if (action === 'markdown') {
|
|
435
494
|
const file = await kernel.readDeliveryFile(taskId, 'index.md')
|
|
436
495
|
if (!file) return failure({ taskId }, 'ARTIFACT_NOT_FOUND', '该任务没有 index.md(未成功或已被清理)。')
|
|
@@ -443,7 +502,7 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
|
|
|
443
502
|
}
|
|
444
503
|
const task = kernel.get(taskId)
|
|
445
504
|
if (!task) return failure({ taskId }, 'TASK_NOT_FOUND', `任务不存在:${taskId}`)
|
|
446
|
-
return success(taskView(task))
|
|
505
|
+
return success(taskView(kernel, task))
|
|
447
506
|
},
|
|
448
507
|
))
|
|
449
508
|
|
package/src/convert/ingest.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { cp, mkdir, stat } from 'node:fs/promises'
|
|
|
25
25
|
import { basename, join, resolve, sep } from 'node:path'
|
|
26
26
|
import type { ConvertService, IngestPayload } from 'dsh-convert-core'
|
|
27
27
|
import type { LineageContext } from 'dsh-data-contracts/refs'
|
|
28
|
-
import type { SourceType, SourceTypeDef } from '../types.js'
|
|
28
|
+
import type { SourceProvenance, SourceType, SourceTypeDef } from '../types.js'
|
|
29
29
|
import {
|
|
30
30
|
failure,
|
|
31
31
|
KNOWLEDGE_ASSET_DIR,
|
|
@@ -48,6 +48,8 @@ export interface IngestTarget {
|
|
|
48
48
|
metadata?: Record<string, unknown>
|
|
49
49
|
sourceId?: string
|
|
50
50
|
lineage?: LineageContext
|
|
51
|
+
/** OKF v0.2 溯源信息(0048):写成 front matter 的顶层键,不塞进 metadata。 */
|
|
52
|
+
provenance?: SourceProvenance
|
|
51
53
|
}): Promise<{
|
|
52
54
|
ok: boolean
|
|
53
55
|
data: { sourceId: string; path: string }
|
|
@@ -98,7 +100,7 @@ export interface IngestOutcome {
|
|
|
98
100
|
sourceTypeSupplied: boolean
|
|
99
101
|
sourceTypeCandidates: string[]
|
|
100
102
|
assets: PlacedAsset[]
|
|
101
|
-
/** Path to the source file relative to the knowledge root, e.g. `
|
|
103
|
+
/** Path to the source file relative to the knowledge root, e.g. `01_source_raw/x.md`. */
|
|
102
104
|
relativeSourcePath?: string
|
|
103
105
|
cardHints: CardHints
|
|
104
106
|
notes: string[]
|
|
@@ -111,8 +113,9 @@ export interface CardHints {
|
|
|
111
113
|
title: string
|
|
112
114
|
summary: string
|
|
113
115
|
/**
|
|
114
|
-
* Card body with image references
|
|
115
|
-
*
|
|
116
|
+
* Card body with image references re-based to the canonical
|
|
117
|
+
* knowledge-root-relative form (`01_source_raw/_assets/<bucket>/<file>`), so
|
|
118
|
+
* the first image actually reaches the multimodal embedder.
|
|
116
119
|
*/
|
|
117
120
|
contentWithImages: string
|
|
118
121
|
/** The image references as they must appear inside a card body. */
|
|
@@ -137,12 +140,17 @@ function rewriteImageRefs(markdown: string, mapping: Map<string, string>): strin
|
|
|
137
140
|
return result
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
/**
|
|
144
|
+
* 图片引用写成**知识库根相对**的规范形:`01_source_raw/_assets/<bucket>/<file>`。
|
|
145
|
+
*
|
|
146
|
+
* 为什么不写成「相对引用它的那个文件」:同一份资产会被三处引用 —— SourceRaw 在
|
|
147
|
+
* `01_source_raw/<type>/`、卡片在 `02_wiki/<type>/`、还有面板拼出来的 URL。写死
|
|
148
|
+
* 「往上两级」在深度相同的地方凑巧成立,换个深度就错,而且错了不会报错,只会让
|
|
149
|
+
* 向量编码器读到不存在的文件(0047 实机:`FileNotFoundError: …/02_wiki/concept/_assets/…`)。
|
|
150
|
+
* 根相对只有一种解释,运行时按 {@link assetRefCandidates} 解析。
|
|
151
|
+
*/
|
|
152
|
+
function assetImageRef(hash: string, rel: string): string {
|
|
153
|
+
return `${KNOWLEDGE_SOURCE_DIR}/${KNOWLEDGE_ASSET_DIR}/${hash}/${basename(rel)}`
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
export function buildCardHints(input: {
|
|
@@ -152,12 +160,11 @@ export function buildCardHints(input: {
|
|
|
152
160
|
assetMapping: Map<string, string>
|
|
153
161
|
entityType?: string
|
|
154
162
|
}): CardHints {
|
|
155
|
-
const entityType = input.entityType && ENTITY_TYPES.includes(input.entityType) ? input.entityType : 'concept'
|
|
156
163
|
const content = rewriteImageRefs(
|
|
157
164
|
input.payload.content,
|
|
158
|
-
new Map([...input.assetMapping].map(([from, to]) => [from,
|
|
165
|
+
new Map([...input.assetMapping].map(([from, to]) => [from, assetImageRef(input.assetBucket, to)])),
|
|
159
166
|
)
|
|
160
|
-
const imageRefs = input.payload.assets.map(asset =>
|
|
167
|
+
const imageRefs = input.payload.assets.map(asset => assetImageRef(input.assetBucket, asset.rel))
|
|
161
168
|
const summary = input.payload.content
|
|
162
169
|
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
163
170
|
.replace(/[#>*`_\-[\]()]/g, ' ')
|
|
@@ -249,9 +256,17 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
|
|
|
249
256
|
|
|
250
257
|
const sourceContent = rewriteImageRefs(
|
|
251
258
|
payload.content,
|
|
252
|
-
new Map([...assetMapping].map(([from, to]) => [from,
|
|
259
|
+
new Map([...assetMapping].map(([from, to]) => [from, assetImageRef(bucket, to)])),
|
|
253
260
|
)
|
|
254
261
|
|
|
262
|
+
/**
|
|
263
|
+
* `metadata` 只剩**类型专属的扩展键**(0048)。
|
|
264
|
+
*
|
|
265
|
+
* 原先链接、解析器、格式、哈希、资产清单全挤在这一个 JSON 字符串里 —— 人读不了、
|
|
266
|
+
* `grep` 不到,等于没有元数据。OKF v0.2 的那一组(`description` / `resource` /
|
|
267
|
+
* `generated` / `sources[]`)现在走 `provenance`,写成真正的顶层键;这里留下的是
|
|
268
|
+
* OKF 没有对应概念、但下游确实要用的运行信息。
|
|
269
|
+
*/
|
|
255
270
|
const metadata: Record<string, unknown> = {
|
|
256
271
|
...(options.metadata ?? {}),
|
|
257
272
|
source_format: payload.sourceFormat,
|
|
@@ -259,9 +274,10 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
|
|
|
259
274
|
content_hash: payload.contentHash,
|
|
260
275
|
convert_task_id: taskId,
|
|
261
276
|
convert_contract_version: 1,
|
|
262
|
-
...(payload.
|
|
263
|
-
|
|
264
|
-
|
|
277
|
+
...(payload.original
|
|
278
|
+
? { original: { rel: payload.original.rel, bytes: payload.original.bytes, sha256: payload.original.sha256 } }
|
|
279
|
+
: {}),
|
|
280
|
+
assets: placed.map(asset => assetImageRef(bucket, asset.rel)),
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
const sourceType = options.sourceType?.trim() || 'document'
|
|
@@ -273,6 +289,29 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
|
|
|
273
289
|
const title = options.title?.trim() || payload.title
|
|
274
290
|
const externalId = payload.externalId
|
|
275
291
|
|
|
292
|
+
/**
|
|
293
|
+
* OKF v0.2 的溯源信息。
|
|
294
|
+
*
|
|
295
|
+
* `generated.at` 就是**采集时间**:OKF 里没有「抓取时间」这个概念,而「这份文件
|
|
296
|
+
* 什么时候被谁写出来的」正好是它。`sources[]` 记的是这份文件的来源 —— 对网页就是
|
|
297
|
+
* 那个页面本身(地址、标题、作者、发布时间),对上传的文件就是文件名。
|
|
298
|
+
*/
|
|
299
|
+
const provenance: SourceProvenance = {
|
|
300
|
+
...(payload.document?.description ? { description: payload.document.description } : {}),
|
|
301
|
+
...(payload.sourceUrl ? { resource: payload.sourceUrl } : {}),
|
|
302
|
+
generatedBy: `convert/${payload.parser}`,
|
|
303
|
+
generatedAt: new Date().toISOString(),
|
|
304
|
+
sources: [{
|
|
305
|
+
id: 'origin',
|
|
306
|
+
resource: payload.sourceUrl ?? payload.original?.rel ?? payload.sourcePath ?? title,
|
|
307
|
+
title,
|
|
308
|
+
...(payload.document?.siteName ? { author: payload.document.siteName } : {}),
|
|
309
|
+
...(!payload.document?.siteName && payload.document?.author ? { author: payload.document.author } : {}),
|
|
310
|
+
...(payload.document?.publishedAt ? { lastModified: payload.document.publishedAt } : {}),
|
|
311
|
+
}],
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
276
315
|
if (!options.planOnly) {
|
|
277
316
|
const result = await options.runtime.addSource({
|
|
278
317
|
sourceType: sourceType as SourceType,
|
|
@@ -280,6 +319,7 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
|
|
|
280
319
|
content: sourceContent,
|
|
281
320
|
externalId,
|
|
282
321
|
metadata,
|
|
322
|
+
provenance,
|
|
283
323
|
...(options.lineage ? { lineage: options.lineage as LineageContext } : {}),
|
|
284
324
|
})
|
|
285
325
|
|
|
@@ -297,7 +337,10 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
|
|
|
297
337
|
const sourceId = result.data.sourceId
|
|
298
338
|
const sourcePath = result.data.path
|
|
299
339
|
const cardHints = buildCardHints({ payload, sourceId, assetBucket: bucket, assetMapping })
|
|
300
|
-
|
|
340
|
+
// 0046 起编译会把来源里的图**自动**挂进对应卡片正文(按「图前那段话」定位),
|
|
341
|
+
// 所以 cardHints 不再是让图进多模态召回的**唯一**办法,只是「想立刻手工建一张图卡」
|
|
342
|
+
// 时的现成素材。
|
|
343
|
+
notes.push('SourceRaw 已写入;图片已随正文落地,下一次编译会把它们挂到对应卡片上(cardHints 是手工建图卡时的现成素材)。')
|
|
301
344
|
|
|
302
345
|
return success<IngestOutcome>(
|
|
303
346
|
{
|
package/src/convert/types.ts
CHANGED
|
@@ -12,9 +12,9 @@ import { join } from 'node:path'
|
|
|
12
12
|
export const CONVERT_SETTINGS_NAMESPACE = 'tita-data-agent-convert'
|
|
13
13
|
|
|
14
14
|
/** Knowledge store layout the convert seam cooperates with (issue 0013). */
|
|
15
|
-
export const KNOWLEDGE_SOURCE_DIR = '
|
|
15
|
+
export const KNOWLEDGE_SOURCE_DIR = '01_source_raw'
|
|
16
16
|
export const KNOWLEDGE_WIKI_DIR = '02_wiki'
|
|
17
|
-
/** Sub-directory inside `
|
|
17
|
+
/** Sub-directory inside `01_source_raw` that holds converted images. */
|
|
18
18
|
export const KNOWLEDGE_ASSET_DIR = '_assets'
|
|
19
19
|
|
|
20
20
|
export interface ConvertPluginConfig {
|