dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.15

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 (266) hide show
  1. package/README.md +89 -56
  2. package/cordis.patch.yml +36 -129
  3. package/eval/benchmark-extraction-format.mjs +20 -0
  4. package/lib/client.js +3262 -4142
  5. package/lib/doc-page.js +1 -1
  6. package/lib/pages.js +1147 -364
  7. package/lib/types/axis-registry.d.ts +8 -1
  8. package/lib/types/axis-registry.d.ts.map +1 -1
  9. package/lib/types/axis-registry.js +62 -7
  10. package/lib/types/axis-registry.js.map +1 -1
  11. package/lib/types/convert/host.d.ts.map +1 -1
  12. package/lib/types/convert/host.js +3 -0
  13. package/lib/types/convert/host.js.map +1 -1
  14. package/lib/types/convert/ingest.d.ts +9 -0
  15. package/lib/types/convert/ingest.d.ts.map +1 -1
  16. package/lib/types/convert/ingest.js +68 -9
  17. package/lib/types/convert/ingest.js.map +1 -1
  18. package/lib/types/diagnosis/cards.d.ts +7 -3
  19. package/lib/types/diagnosis/cards.d.ts.map +1 -1
  20. package/lib/types/diagnosis/cards.js +13 -8
  21. package/lib/types/diagnosis/cards.js.map +1 -1
  22. package/lib/types/diagnosis/run.d.ts +4 -13
  23. package/lib/types/diagnosis/run.d.ts.map +1 -1
  24. package/lib/types/diagnosis/run.js +26 -16
  25. package/lib/types/diagnosis/run.js.map +1 -1
  26. package/lib/types/diagnosis/schema.js +2 -2
  27. package/lib/types/diagnosis/schema.js.map +1 -1
  28. package/lib/types/diagnosis/seed-source.d.ts +1 -1
  29. package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
  30. package/lib/types/diagnosis/seed-source.js +5 -4
  31. package/lib/types/diagnosis/seed-source.js.map +1 -1
  32. package/lib/types/diagnosis/seed.d.ts +4 -4
  33. package/lib/types/diagnosis/seed.d.ts.map +1 -1
  34. package/lib/types/diagnosis/seed.js +6 -8
  35. package/lib/types/diagnosis/seed.js.map +1 -1
  36. package/lib/types/diagnosis/store.d.ts +18 -17
  37. package/lib/types/diagnosis/store.d.ts.map +1 -1
  38. package/lib/types/diagnosis/store.js +94 -66
  39. package/lib/types/diagnosis/store.js.map +1 -1
  40. package/lib/types/diagnosis/types.d.ts +10 -16
  41. package/lib/types/diagnosis/types.d.ts.map +1 -1
  42. package/lib/types/diagnosis/types.js.map +1 -1
  43. package/lib/types/environment/dsh-python.d.ts +77 -0
  44. package/lib/types/environment/dsh-python.d.ts.map +1 -0
  45. package/lib/types/environment/dsh-python.js +139 -0
  46. package/lib/types/environment/dsh-python.js.map +1 -0
  47. package/lib/types/environment/install.d.ts +9 -1
  48. package/lib/types/environment/install.d.ts.map +1 -1
  49. package/lib/types/environment/install.js +149 -24
  50. package/lib/types/environment/install.js.map +1 -1
  51. package/lib/types/environment/model-catalog.d.ts +34 -2
  52. package/lib/types/environment/model-catalog.d.ts.map +1 -1
  53. package/lib/types/environment/model-catalog.js +40 -2
  54. package/lib/types/environment/model-catalog.js.map +1 -1
  55. package/lib/types/environment/service.d.ts +13 -3
  56. package/lib/types/environment/service.d.ts.map +1 -1
  57. package/lib/types/environment/service.js +127 -23
  58. package/lib/types/environment/service.js.map +1 -1
  59. package/lib/types/environment/types.d.ts +9 -0
  60. package/lib/types/environment/types.d.ts.map +1 -1
  61. package/lib/types/events.d.ts +2 -0
  62. package/lib/types/events.d.ts.map +1 -1
  63. package/lib/types/events.js +13 -0
  64. package/lib/types/events.js.map +1 -1
  65. package/lib/types/extract-contract.d.ts +1 -1
  66. package/lib/types/extract-contract.d.ts.map +1 -1
  67. package/lib/types/extract-contract.js +34 -31
  68. package/lib/types/extract-contract.js.map +1 -1
  69. package/lib/types/extract-model.d.ts +2 -0
  70. package/lib/types/extract-model.d.ts.map +1 -1
  71. package/lib/types/extract-model.js.map +1 -1
  72. package/lib/types/extract-table-contract.d.ts +3 -0
  73. package/lib/types/extract-table-contract.d.ts.map +1 -0
  74. package/lib/types/extract-table-contract.js +30 -0
  75. package/lib/types/extract-table-contract.js.map +1 -0
  76. package/lib/types/extract-table.d.ts +8 -0
  77. package/lib/types/extract-table.d.ts.map +1 -0
  78. package/lib/types/extract-table.js +151 -0
  79. package/lib/types/extract-table.js.map +1 -0
  80. package/lib/types/extraction-runs.d.ts +30 -6
  81. package/lib/types/extraction-runs.d.ts.map +1 -1
  82. package/lib/types/extraction-runs.js +69 -8
  83. package/lib/types/extraction-runs.js.map +1 -1
  84. package/lib/types/fs-atomic.d.ts +0 -7
  85. package/lib/types/fs-atomic.d.ts.map +1 -1
  86. package/lib/types/fs-atomic.js +17 -2
  87. package/lib/types/fs-atomic.js.map +1 -1
  88. package/lib/types/images.d.ts +1 -1
  89. package/lib/types/images.d.ts.map +1 -1
  90. package/lib/types/images.js +2 -2
  91. package/lib/types/images.js.map +1 -1
  92. package/lib/types/index.d.ts.map +1 -1
  93. package/lib/types/index.js +232 -50
  94. package/lib/types/index.js.map +1 -1
  95. package/lib/types/intent/scope.d.ts.map +1 -1
  96. package/lib/types/intent/scope.js +1 -0
  97. package/lib/types/intent/scope.js.map +1 -1
  98. package/lib/types/intent/tools.d.ts +19 -0
  99. package/lib/types/intent/tools.d.ts.map +1 -1
  100. package/lib/types/intent/tools.js +37 -1
  101. package/lib/types/intent/tools.js.map +1 -1
  102. package/lib/types/knowledge-bases.d.ts.map +1 -1
  103. package/lib/types/knowledge-bases.js +5 -16
  104. package/lib/types/knowledge-bases.js.map +1 -1
  105. package/lib/types/knowledge.d.ts +3 -0
  106. package/lib/types/knowledge.d.ts.map +1 -1
  107. package/lib/types/knowledge.js +21 -5
  108. package/lib/types/knowledge.js.map +1 -1
  109. package/lib/types/materialize-snapshot.d.ts +49 -0
  110. package/lib/types/materialize-snapshot.d.ts.map +1 -0
  111. package/lib/types/materialize-snapshot.js +2 -0
  112. package/lib/types/materialize-snapshot.js.map +1 -0
  113. package/lib/types/model-text.d.ts +1 -1
  114. package/lib/types/model-text.d.ts.map +1 -1
  115. package/lib/types/model-text.js +5 -4
  116. package/lib/types/model-text.js.map +1 -1
  117. package/lib/types/oplog.d.ts.map +1 -1
  118. package/lib/types/oplog.js +7 -0
  119. package/lib/types/oplog.js.map +1 -1
  120. package/lib/types/pipeline/engine.d.ts +60 -0
  121. package/lib/types/pipeline/engine.d.ts.map +1 -0
  122. package/lib/types/pipeline/engine.js +338 -0
  123. package/lib/types/pipeline/engine.js.map +1 -0
  124. package/lib/types/pipeline/store.d.ts +73 -0
  125. package/lib/types/pipeline/store.d.ts.map +1 -0
  126. package/lib/types/pipeline/store.js +143 -0
  127. package/lib/types/pipeline/store.js.map +1 -0
  128. package/lib/types/pipeline/types.d.ts +101 -0
  129. package/lib/types/pipeline/types.d.ts.map +1 -0
  130. package/lib/types/pipeline/types.js +5 -0
  131. package/lib/types/pipeline/types.js.map +1 -0
  132. package/lib/types/present.d.ts +2 -0
  133. package/lib/types/present.d.ts.map +1 -1
  134. package/lib/types/present.js +9 -4
  135. package/lib/types/present.js.map +1 -1
  136. package/lib/types/retention.d.ts +39 -0
  137. package/lib/types/retention.d.ts.map +1 -0
  138. package/lib/types/retention.js +42 -0
  139. package/lib/types/retention.js.map +1 -0
  140. package/lib/types/runtime.d.ts +115 -5
  141. package/lib/types/runtime.d.ts.map +1 -1
  142. package/lib/types/runtime.js +862 -132
  143. package/lib/types/runtime.js.map +1 -1
  144. package/lib/types/source-evidence.d.ts +9 -1
  145. package/lib/types/source-evidence.d.ts.map +1 -1
  146. package/lib/types/source-evidence.js +13 -5
  147. package/lib/types/source-evidence.js.map +1 -1
  148. package/lib/types/store/axis-references.js +3 -3
  149. package/lib/types/store/axis-references.js.map +1 -1
  150. package/lib/types/store/derived.d.ts +1 -1
  151. package/lib/types/store/derived.d.ts.map +1 -1
  152. package/lib/types/store/derived.js +41 -7
  153. package/lib/types/store/derived.js.map +1 -1
  154. package/lib/types/store/knowledge-db.d.ts +18 -0
  155. package/lib/types/store/knowledge-db.d.ts.map +1 -1
  156. package/lib/types/store/knowledge-db.js +111 -6
  157. package/lib/types/store/knowledge-db.js.map +1 -1
  158. package/lib/types/store/migrate.js +1 -1
  159. package/lib/types/store/migrate.js.map +1 -1
  160. package/lib/types/trajectory-types.d.ts +172 -0
  161. package/lib/types/trajectory-types.d.ts.map +1 -0
  162. package/lib/types/trajectory-types.js +36 -0
  163. package/lib/types/trajectory-types.js.map +1 -0
  164. package/lib/types/types.d.ts +3 -4
  165. package/lib/types/types.d.ts.map +1 -1
  166. package/lib/types/types.js.map +1 -1
  167. package/lib/types/wiki-finalize.d.ts +16 -0
  168. package/lib/types/wiki-finalize.d.ts.map +1 -1
  169. package/lib/types/wiki-finalize.js +26 -0
  170. package/lib/types/wiki-finalize.js.map +1 -1
  171. package/lib/types/wiki-materialize.d.ts.map +1 -1
  172. package/lib/types/wiki-materialize.js +3 -15
  173. package/lib/types/wiki-materialize.js.map +1 -1
  174. package/lib/types/wiki-trial-types.d.ts +3 -0
  175. package/lib/types/wiki-trial-types.d.ts.map +1 -1
  176. package/lib/types/wiki-trial.d.ts +2 -0
  177. package/lib/types/wiki-trial.d.ts.map +1 -1
  178. package/lib/types/wiki-trial.js +31 -7
  179. package/lib/types/wiki-trial.js.map +1 -1
  180. package/package.json +6 -3
  181. package/preset/tita-data-agent/agent.cordis.yml +36 -129
  182. package/src/axis-registry.ts +57 -9
  183. package/src/client/AttributionEventGuide.tsx +10 -9
  184. package/src/client/CompileDialogs.tsx +10 -2
  185. package/src/client/EnvironmentSetup.tsx +76 -16
  186. package/src/client/ExpansionGraph.tsx +13 -7
  187. package/src/client/ExtractionTasksPage.tsx +543 -156
  188. package/src/client/ImportFilesDialog.tsx +2 -13
  189. package/src/client/IntentTab.tsx +3 -1
  190. package/src/client/KnowledgeBaseTab.tsx +139 -122
  191. package/src/client/KnowledgeLayerSection.tsx +19 -2
  192. package/src/client/KnowledgePanel.tsx +17 -3
  193. package/src/client/ListPager.tsx +15 -5
  194. package/src/client/MaterializeTrajectoryView.tsx +124 -0
  195. package/src/client/NodeSettings.tsx +90 -25
  196. package/src/client/NormalizeTrajectoryView.tsx +136 -0
  197. package/src/client/PipelineNotice.tsx +26 -0
  198. package/src/client/RecallPanel.tsx +55 -25
  199. package/src/client/TabGuide.tsx +4 -4
  200. package/src/client/TrialReportView.tsx +61 -16
  201. package/src/client/TypeDefinitionDrawer.tsx +33 -9
  202. package/src/client/WikiCardPreview.tsx +43 -10
  203. package/src/client/WikiFlowPanel.tsx +4 -4
  204. package/src/client/architecture.tsx +1 -1
  205. package/src/client/attribution/AttributionTab.tsx +37 -36
  206. package/src/client/attribution/CaseView.tsx +3 -1
  207. package/src/client/attribution/EventView.tsx +14 -4
  208. package/src/client/attribution/RunView.tsx +2 -2
  209. package/src/client/attribution/ViewSwitch.tsx +20 -5
  210. package/src/client/attribution/graph-options.ts +38 -31
  211. package/src/client/attribution/model.ts +16 -9
  212. package/src/client/graph/palette.ts +165 -0
  213. package/src/client/pages.css +224 -28
  214. package/src/client/pages.tsx +33 -11
  215. package/src/client/styles.css +61 -6
  216. package/src/client/wiki-trial.ts +30 -1
  217. package/src/convert/host.ts +3 -0
  218. package/src/convert/ingest.ts +73 -9
  219. package/src/diagnosis/cards.ts +13 -8
  220. package/src/diagnosis/run.ts +27 -17
  221. package/src/diagnosis/schema.ts +2 -2
  222. package/src/diagnosis/seed-source.ts +5 -4
  223. package/src/diagnosis/seed.ts +6 -8
  224. package/src/diagnosis/store.ts +99 -69
  225. package/src/diagnosis/types.ts +10 -16
  226. package/src/environment/dsh-python.ts +156 -0
  227. package/src/environment/install.ts +134 -19
  228. package/src/environment/model-catalog.ts +45 -3
  229. package/src/environment/service.ts +111 -23
  230. package/src/environment/types.ts +6 -0
  231. package/src/events.ts +12 -0
  232. package/src/extract-contract.ts +6 -3
  233. package/src/extract-model.ts +2 -0
  234. package/src/extract-table-contract.ts +29 -0
  235. package/src/extract-table.ts +126 -0
  236. package/src/extraction-runs.ts +70 -11
  237. package/src/fs-atomic.ts +19 -2
  238. package/src/images.ts +2 -2
  239. package/src/index.ts +207 -72
  240. package/src/intent/scope.ts +1 -0
  241. package/src/intent/tools.ts +38 -1
  242. package/src/knowledge-bases.ts +4 -12
  243. package/src/knowledge.ts +24 -4
  244. package/src/materialize-snapshot.ts +46 -0
  245. package/src/model-text.ts +5 -3
  246. package/src/oplog.ts +7 -0
  247. package/src/pipeline/engine.ts +312 -0
  248. package/src/pipeline/store.ts +147 -0
  249. package/src/pipeline/types.ts +92 -0
  250. package/src/present.ts +10 -4
  251. package/src/retention.ts +76 -0
  252. package/src/runtime.ts +853 -131
  253. package/src/source-evidence.ts +12 -5
  254. package/src/store/axis-references.ts +3 -3
  255. package/src/store/derived.ts +40 -7
  256. package/src/store/knowledge-db.ts +111 -5
  257. package/src/store/migrate.ts +1 -1
  258. package/src/trajectory-types.ts +142 -0
  259. package/src/types.ts +3 -4
  260. package/src/wiki-finalize.ts +28 -0
  261. package/src/wiki-materialize.ts +3 -14
  262. package/src/wiki-trial-types.ts +3 -0
  263. package/src/wiki-trial.ts +26 -7
  264. package/src/client/convert/ConvertPanel.tsx +0 -583
  265. package/src/client/convert/styles.css +0 -138
  266. package/src/client/convert/styles.ts +0 -49
@@ -1,583 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useState, type ChangeEvent, type ReactNode } from 'react'
2
- import {
3
- AlertCircle, Check, Copy, Download, ExternalLink, FileText, FileUp, Inbox,
4
- Package, RefreshCw, RotateCcw, ScanEye, X,
5
- } from '@tita-agent/ui/icons'
6
- import type { LucideIcon } from '@tita-agent/ui/icons'
7
- import { Badge, Button, Input, Label, SelectMenu, Switch, Tabs, TabsContent, TabsList, TabsTrigger, cn } from '@tita-agent/ui/components'
8
- import { styles } from './styles.ts'
9
- import '@tita-agent/ui/styles.css'
10
- // The panel ships its own `tdc-*` stylesheet; without this import the class names
11
- // resolve to nothing and the panel renders unstyled.
12
- import './styles.css'
13
-
14
- /**
15
- * The conversion kernel is mounted by the knowledge plugin at `/convert`.
16
- * Frozen contract — see spec/VERSION-MATRIX.md.
17
- */
18
- const BASE = '/convert'
19
-
20
- interface TaskSummary {
21
- id: string
22
- state: 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled'
23
- inputKind: string
24
- title: string
25
- sourceFormat: string
26
- parser?: string
27
- progress: number | null
28
- stage: string
29
- message?: string
30
- error?: { code: string; message: string; suggestion?: string } | null
31
- warnings: Array<{ code: string; message: string }>
32
- assetCount: number
33
- wordCount: number
34
- contentHash?: string
35
- externalId?: string
36
- deliveryDir?: string
37
- }
38
-
39
- interface AdapterCapability {
40
- id: string
41
- description: string
42
- kinds: string[]
43
- extensions: string[]
44
- availability: { available: boolean; reason?: string; suggestion?: string }
45
- }
46
-
47
- interface CapabilityReport {
48
- adapters: AdapterCapability[]
49
- supportedExtensions: string[]
50
- runtime: Record<string, { available: boolean; version?: string; error?: string }>
51
- rootDir: string
52
- outDir: string
53
- knowledgeRoot: string
54
- knowledgeRootSource: string
55
- ingestEnabled: boolean
56
- }
57
-
58
- /** Plugin-level facts served by `/convert/api/plugin` (the kernel cannot know them). */
59
- interface PluginInfo {
60
- rootDir: string
61
- outDir: string
62
- mountPath: string
63
- knowledgeRoot: string
64
- knowledgeRootSource: string
65
- knowledgeBaseUrl: string
66
- ingestEnabled: boolean
67
- }
68
-
69
- interface IngestResult {
70
- taskId: string
71
- sourceId?: string
72
- sourcePath?: string
73
- knowledgeRoot: string
74
- externalId: string
75
- contentHash: string
76
- sourceFormat: string
77
- parser: string
78
- assets: Array<{ rel: string; target: string; bytes: number }>
79
- cardHints: { entityTypes: string[]; sourceRefs: string[]; title: string; summary: string; contentWithImages: string; imageRefs: string[] }
80
- notes: string[]
81
- knowledgeRootSource?: string
82
- }
83
-
84
- interface Envelope<T> { ok?: boolean; data?: T; warnings?: string[]; error?: { code?: string; message?: string; suggestion?: string } }
85
-
86
- async function requestJson<T>(url: string, init?: RequestInit): Promise<Envelope<T>> {
87
- const response = await fetch(url, init)
88
- const text = await response.text()
89
- if (!text.trim()) throw new Error(`服务端返回空响应(HTTP ${response.status})`)
90
- let payload: Envelope<T>
91
- try {
92
- payload = JSON.parse(text) as Envelope<T>
93
- } catch {
94
- throw new Error(`服务端返回了无效响应(HTTP ${response.status})`)
95
- }
96
- if (!response.ok || payload.ok === false) {
97
- const error = new Error([payload.error?.code, payload.error?.message].filter(Boolean).join(' · ') || `请求失败(${response.status})`)
98
- ;(error as Error & { suggestion?: string }).suggestion = payload.error?.suggestion
99
- throw error
100
- }
101
- return payload
102
- }
103
-
104
- function describe(cause: unknown): string {
105
- if (!(cause instanceof Error)) return String(cause)
106
- const suggestion = (cause as Error & { suggestion?: string }).suggestion
107
- return suggestion ? `${cause.message} — 建议:${suggestion}` : cause.message
108
- }
109
-
110
- const STATE_META: Record<TaskSummary['state'], { label: string; tone: 'neutral' | 'success' | 'warning' | 'blue' }> = {
111
- queued: { label: '排队', tone: 'warning' },
112
- running: { label: '进行中', tone: 'blue' },
113
- succeeded: { label: '已交付', tone: 'success' },
114
- failed: { label: '失败', tone: 'neutral' },
115
- cancelled: { label: '已取消', tone: 'neutral' },
116
- }
117
-
118
- const KIND_META: Record<string, { label: string; icon: LucideIcon }> = {
119
- file: { label: '文件', icon: FileText },
120
- text: { label: '文本', icon: FileUp },
121
- }
122
-
123
- function formatBytes(bytes: number | undefined): string {
124
- if (!bytes) return '0 B'
125
- const units = ['B', 'KB', 'MB', 'GB']
126
- let value = bytes
127
- let unit = 0
128
- while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1 }
129
- return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`
130
- }
131
-
132
- export interface ConvertPanelProps {
133
- visible: boolean
134
- /** Settings scope handed in by the client shell; the panel reads live values from the plugin API. */
135
- settings?: { convert?: unknown }
136
- }
137
-
138
- export function ConvertPanel({ visible }: ConvertPanelProps): ReactNode {
139
- const [view, setView] = useState('tasks')
140
- const [tasks, setTasks] = useState<TaskSummary[]>([])
141
- const [capabilities, setCapabilities] = useState<CapabilityReport | null>(null)
142
- const [selected, setSelected] = useState<TaskSummary | null>(null)
143
- const [markdown, setMarkdown] = useState('')
144
- const [ingest, setIngest] = useState<IngestResult | null>(null)
145
- const [error, setError] = useState<string | null>(null)
146
- const [notice, setNotice] = useState<string | null>(null)
147
- const [busy, setBusy] = useState(false)
148
- const [form, setForm] = useState({ kind: 'upload', path: '', text: '', title: '', adapter: '' })
149
- const [uploadFile, setUploadFile] = useState<File | null>(null)
150
- const [ingestForm, setIngestForm] = useState({ title: '', planOnly: false })
151
-
152
- const load = useCallback(async () => {
153
- try {
154
- const [tasksPayload, capabilityPayload, pluginPayload] = await Promise.all([
155
- requestJson<{ tasks: TaskSummary[] }>(`${BASE}/api/tasks`),
156
- requestJson<CapabilityReport>(`${BASE}/api/capabilities`),
157
- requestJson<PluginInfo>(`${BASE}/api/plugin`),
158
- ])
159
- setTasks(tasksPayload.data?.tasks ?? [])
160
- setCapabilities(capabilityPayload.data ? { ...capabilityPayload.data, ...pluginPayload.data } as CapabilityReport : null)
161
- setError(null)
162
- } catch (cause) {
163
- setError(describe(cause))
164
- }
165
- }, [])
166
-
167
- useEffect(() => { void load() }, [load, visible])
168
-
169
- useEffect(() => {
170
- if (!visible) return undefined
171
- const timer = setInterval(() => { void load() }, 4000)
172
- return () => clearInterval(timer)
173
- }, [load, visible])
174
-
175
- const selectedId = selected?.id ?? ''
176
-
177
- const openTask = useCallback(async (task: TaskSummary) => {
178
- setSelected(task)
179
- setIngest(null)
180
- setMarkdown('')
181
- setIngestForm({ title: task.title ?? '', planOnly: false })
182
- try {
183
- const detail = await requestJson<TaskSummary>(`${BASE}/api/tasks/${encodeURIComponent(task.id)}`)
184
- if (detail.data) setSelected(detail.data)
185
- if (task.state === 'succeeded') {
186
- const body = await fetch(`${BASE}/api/tasks/${encodeURIComponent(task.id)}/markdown`)
187
- if (body.ok) setMarkdown(await body.text())
188
- }
189
- } catch (cause) {
190
- setError(describe(cause))
191
- }
192
- }, [])
193
-
194
- const submit = useCallback(async () => {
195
- setBusy(true)
196
- setError(null)
197
- setNotice(null)
198
- try {
199
- const common = {
200
- title: form.title.trim() || undefined,
201
- adapter: form.adapter.trim() || undefined,
202
- }
203
- let payload: Envelope<{ id?: string }>
204
- if (form.kind === 'upload') {
205
- // The kernel owns multipart parsing, staging and cleanup, so the browser
206
- // hands it the bytes directly instead of a path only the server can see.
207
- if (!uploadFile) throw new Error('请先选择一个文件。')
208
- const body = new FormData()
209
- body.append('file', uploadFile, uploadFile.name)
210
- if (common.title) body.append('title', common.title)
211
- if (common.adapter) body.append('adapter', common.adapter)
212
- payload = await requestJson(`${BASE}/api/tasks`, { method: 'POST', body })
213
- } else if (form.kind === 'file') {
214
- if (!form.path.trim()) throw new Error('本机路径转换需要一个绝对路径(供 Agent 或本机脚本使用)。')
215
- payload = await requestJson(`${BASE}/api/tasks`, {
216
- method: 'POST',
217
- headers: { 'content-type': 'application/json' },
218
- body: JSON.stringify({ kind: 'file', local_path: form.path.trim(), ...common }),
219
- })
220
- } else {
221
- payload = await requestJson(`${BASE}/api/tasks`, {
222
- method: 'POST',
223
- headers: { 'content-type': 'application/json' },
224
- body: JSON.stringify({ kind: 'text', text: form.text, ...common }),
225
- })
226
- }
227
- setNotice(`任务已受理:${payload.data?.id ?? ''}`)
228
- setUploadFile(null)
229
- setView('tasks')
230
- await load()
231
- } catch (cause) {
232
- setError(describe(cause))
233
- } finally {
234
- setBusy(false)
235
- }
236
- }, [form, load, uploadFile])
237
-
238
- const act = useCallback(async (taskId: string, action: 'cancel' | 'retry') => {
239
- try {
240
- await requestJson(`${BASE}/api/tasks/${encodeURIComponent(taskId)}/${action}`, { method: 'POST' })
241
- await load()
242
- } catch (cause) {
243
- setError(describe(cause))
244
- }
245
- }, [load])
246
-
247
- const runIngest = useCallback(async () => {
248
- if (!selectedId) return
249
- setBusy(true)
250
- setError(null)
251
- setNotice(null)
252
- try {
253
- const result = await requestJson<IngestResult>(`${BASE}/api/ingest`, {
254
- method: 'POST',
255
- headers: { 'content-type': 'application/json' },
256
- body: JSON.stringify({
257
- task_id: selectedId,
258
- title: ingestForm.title.trim() || undefined,
259
- plan_only: ingestForm.planOnly,
260
- }),
261
- })
262
- setIngest(result.data ?? null)
263
- setNotice(ingestForm.planOnly ? '已完成入库预演(未写入)' : `已写入知识库:${result.data?.sourceId ?? ''}`)
264
- } catch (cause) {
265
- setError(describe(cause))
266
- } finally {
267
- setBusy(false)
268
- }
269
- }, [ingestForm, selectedId])
270
-
271
- const copy = useCallback((value: string) => {
272
- void navigator.clipboard?.writeText(value).then(() => setNotice('已复制到剪贴板')).catch(() => setError('复制失败'))
273
- }, [])
274
-
275
- const cardPrompt = useMemo(() => {
276
- if (!ingest) return ''
277
- const hints = ingest.cardHints
278
- return [
279
- `请为刚录入的来源 ${ingest.sourceId ?? ''} 写一张 wiki 卡片,让图片进入多模态召回:`,
280
- `- 坐标实体类型建议:${hints.entityTypes.join(' / ')}(使用系统固定模板)`,
281
- `- source_refs:${hints.sourceRefs.join(', ')}`,
282
- `- 标题:${hints.title}`,
283
- `- 正文必须使用下面这些图片相对路径(按对应坐标实体目录解析):`,
284
- ...hints.imageRefs.map(ref => ` - ${ref}`),
285
- '调用 propose_wiki_cards 时把图片引用原样写进 content(正文只读,之后改不了,所以一次写对)。',
286
- ].join('\n')
287
- }, [ingest])
288
-
289
- const runningCount = tasks.filter(task => task.state === 'running' || task.state === 'queued').length
290
-
291
- return (
292
- <div className={styles.panel}>
293
- <div className={styles.header}>
294
- <div className={styles.headerCopy}>
295
- <h3 className={styles.headerTitle}>格式转换</h3>
296
- <p className={styles.headerSubtitle}>
297
- PDF / Office / 视频 / 音频 / 图片 → 知识就绪 Markdown + 本地图片
298
- </p>
299
- </div>
300
- <div className={styles.headerActions}>
301
- <a className="tita-uiButton" href={`${BASE}/`} target="_blank" rel="noreferrer" title="打开内核控制台(Vue3)">
302
- <ExternalLink size={14} />
303
- </a>
304
- <Button variant="ghost" size="sm" onClick={() => void load()} title="刷新">
305
- <RefreshCw size={14} />
306
- </Button>
307
- </div>
308
- </div>
309
-
310
- <div className={styles.body}>
311
- {error && <p className={cn(styles.small, styles.error)}><AlertCircle size={12} /> {error}</p>}
312
- {notice && <p className={cn(styles.small, styles.notice)}><Check size={12} /> {notice}</p>}
313
-
314
- <Tabs value={view} onValueChange={setView}>
315
- <TabsList>
316
- <TabsTrigger value="tasks">任务{runningCount > 0 ? ` (${runningCount})` : ''}</TabsTrigger>
317
- <TabsTrigger value="new">新转换</TabsTrigger>
318
- <TabsTrigger value="ingest">知识入库</TabsTrigger>
319
- <TabsTrigger value="caps">能力</TabsTrigger>
320
- </TabsList>
321
-
322
- {/* ---------------------------------------------------------- tasks */}
323
- <TabsContent value="tasks">
324
- {tasks.length === 0 && <p className={styles.empty}>还没有转换任务。</p>}
325
- <div className={styles.taskList}>
326
- {tasks.map(task => {
327
- const meta = STATE_META[task.state]
328
- const kind = KIND_META[task.inputKind] ?? KIND_META.file!
329
- const KindIcon = kind.icon
330
- return (
331
- <div key={task.id} className={cn(styles.task, selectedId === task.id && styles.taskActive)}>
332
- <div className={styles.taskMain}>
333
- <div className={styles.taskTitle}>
334
- <KindIcon size={12} /> {task.title}
335
- </div>
336
- <div className={styles.taskMeta}>
337
- {task.parser ?? task.inputKind} · {task.assetCount} 图 / {task.wordCount} 字 · {task.stage}
338
- {task.message ? ` · ${task.message}` : ''}
339
- </div>
340
- {task.state === 'running' && (
341
- <div className={styles.progress}>
342
- <div className={styles.progressBar} style={{ width: task.progress === null ? '100%' : `${Math.round(task.progress * 100)}%` }} />
343
- </div>
344
- )}
345
- {task.error && (
346
- <div className={cn(styles.taskMeta, styles.error)}>
347
- {task.error.code} · {task.error.message}
348
- {task.error.suggestion ? ` — ${task.error.suggestion}` : ''}
349
- </div>
350
- )}
351
- </div>
352
- <div className={styles.taskSide}>
353
- <Badge tone={meta.tone}>{meta.label}</Badge>
354
- <div className={styles.taskActions}>
355
- <Button variant="ghost" size="sm" title="详情" onClick={() => void openTask(task)}><ScanEye size={13} /></Button>
356
- {task.state === 'running' || task.state === 'queued'
357
- ? <Button variant="ghost" size="sm" title="取消" onClick={() => void act(task.id, 'cancel')}><X size={13} /></Button>
358
- : task.state === 'failed' || task.state === 'cancelled'
359
- // 只有这两个状态还能重试。已交付的任务**输入在交付后就清理了**,
360
- // 给它一枚重试按钮,点了只会得到 TASK_ALREADY_DELIVERED,
361
- // 而修复之前那一次点击还会顺手删掉刚交付的产物。
362
- ? <Button variant="ghost" size="sm" title="重试" onClick={() => void act(task.id, 'retry')}><RotateCcw size={13} /></Button>
363
- : null}
364
- {task.state === 'succeeded' && (
365
- <a className="tita-uiButton" href={`${BASE}/api/tasks/${encodeURIComponent(task.id)}/files/index.md`} target="_blank" rel="noreferrer" title="打开 index.md">
366
- <Download size={13} />
367
- </a>
368
- )}
369
- </div>
370
- </div>
371
- </div>
372
- )
373
- })}
374
- </div>
375
- </TabsContent>
376
-
377
- {/* ------------------------------------------------------------ new */}
378
- <TabsContent value="new">
379
- <div className={styles.field}>
380
- <span className={styles.fieldLabel}>输入类型</span>
381
- <SelectMenu
382
- value={form.kind}
383
- onValueChange={next => setForm({ ...form, kind: next })}
384
- aria-label="输入类型"
385
- options={[
386
- { value: 'upload', label: '上传文件' },
387
- { value: 'file', label: '文档(本机路径)' },
388
- { value: 'text', label: '内联文本' },
389
- ]}
390
- />
391
- </div>
392
-
393
- {form.kind === 'upload' && (
394
- <label className={styles.field}>
395
- <span className={styles.fieldLabel}>文件</span>
396
- <input
397
- className={styles.fileInput}
398
- type="file"
399
- accept={(capabilities?.supportedExtensions ?? ['.docx', '.pdf', '.pptx', '.xlsx', '.csv', '.epub', '.rtf']).join(',')}
400
- onChange={(event: ChangeEvent<HTMLInputElement>) => setUploadFile(event.target.files?.[0] ?? null)}
401
- />
402
- <span className={styles.fieldHint}>
403
- 支持 {(capabilities?.supportedExtensions ?? []).map(item => item.replace('.', '')).join(' ')};文件由内核直接接收,不需要先放到本机某个路径。
404
- </span>
405
- {uploadFile && <span className={styles.fieldHint}>已选择:{uploadFile.name}({formatBytes(uploadFile.size)})</span>}
406
- </label>
407
- )}
408
-
409
- {form.kind === 'file' && (
410
- <label className={styles.field}>
411
- <span className={styles.fieldLabel}>本机绝对路径</span>
412
- <Input value={form.path} spellCheck={false} placeholder="/Users/you/Documents/spec.docx" onChange={(event: ChangeEvent<HTMLInputElement>) => setForm({ ...form, path: event.target.value })} />
413
- <span className={styles.fieldHint}>
414
- 给 Agent 或本机脚本用的入口:文件必须已经在运行 DSH 的这台机器上。
415
- </span>
416
- </label>
417
- )}
418
-
419
- {form.kind === 'text' && (
420
- <label className={styles.field}>
421
- <span className={styles.fieldLabel}>文本</span>
422
- <textarea className={styles.pre} rows={8} value={form.text} onChange={(event: ChangeEvent<HTMLTextAreaElement>) => setForm({ ...form, text: event.target.value })} />
423
- </label>
424
- )}
425
-
426
- <div className={styles.grid2}>
427
- <label className={styles.field}>
428
- <span className={styles.fieldLabel}>标题(可选)</span>
429
- <Input value={form.title} onChange={(event: ChangeEvent<HTMLInputElement>) => setForm({ ...form, title: event.target.value })} />
430
- </label>
431
- </div>
432
-
433
- <div className={styles.field}>
434
- <span className={styles.fieldLabel}>adapter(可选)</span>
435
- <SelectMenu
436
- value={form.adapter}
437
- onValueChange={next => setForm({ ...form, adapter: next })}
438
- aria-label="adapter(可选)"
439
- options={[
440
- { value: '', label: '自动选择' },
441
- ...(capabilities?.adapters ?? []).map(adapter => ({
442
- value: adapter.id,
443
- label: `${adapter.id}${adapter.availability.available ? '' : '(不可用)'}`,
444
- })),
445
- ]}
446
- />
447
- </div>
448
-
449
- <Button variant="default" disabled={busy} onClick={() => void submit()}>
450
- <FileUp size={14} /> {busy ? '提交中…' : '开始转换'}
451
- </Button>
452
- </TabsContent>
453
-
454
- {/* --------------------------------------------------------- ingest */}
455
- <TabsContent value="ingest">
456
- {!selectedId && <p className={styles.empty}>先在「任务」里选择一个已交付的任务。</p>}
457
- {selectedId && (
458
- <>
459
- <dl className={styles.kv}>
460
- <div><dt>任务</dt><dd><span className={styles.code}>{selectedId}</span></dd></div>
461
- <div><dt>标题</dt><dd>{selected?.title}</dd></div>
462
- <div><dt>交付目录</dt><dd><span className={styles.code}>{selected?.deliveryDir}</span></dd></div>
463
- <div><dt>content_hash</dt><dd><span className={styles.code}>{selected?.contentHash}</span></dd></div>
464
- <div><dt>知识库根目录</dt><dd><span className={styles.code}>{capabilities?.knowledgeRoot}</span> <span className={styles.muted}>({capabilities?.knowledgeRootSource})</span></dd></div>
465
- </dl>
466
-
467
- <div className={styles.grid2}>
468
- <label className={styles.field}>
469
- <span className={styles.fieldLabel}>标题覆盖(可选)</span>
470
- <Input value={ingestForm.title} onChange={(event: ChangeEvent<HTMLInputElement>) => setIngestForm({ ...ingestForm, title: event.target.value })} />
471
- </label>
472
- </div>
473
-
474
- <div className={cn(styles.row, styles.section)}>
475
- <Switch checked={ingestForm.planOnly} onCheckedChange={checked => setIngestForm({ ...ingestForm, planOnly: checked })} />
476
- <Label>仅预演(plan_only,不写入)</Label>
477
- </div>
478
-
479
- <Button variant="default" disabled={busy || !capabilities?.ingestEnabled} onClick={() => void runIngest()}>
480
- <Package size={14} /> {ingestForm.planOnly ? '预演入库' : '写入知识库'}
481
- </Button>
482
- {capabilities && !capabilities.ingestEnabled && (
483
- <p className={styles.fieldHint}>插件设置里关闭了「允许一键入库」。</p>
484
- )}
485
-
486
- {ingest && (
487
- <div className={styles.section}>
488
- <h4 className={styles.sectionTitle}>入库结果</h4>
489
- <dl className={styles.kv}>
490
- <div><dt>source_id</dt><dd><span className={styles.code}>{ingest.sourceId ?? '(预演)'}</span></dd></div>
491
- <div><dt>来源路径</dt><dd><span className={styles.code}>{ingest.sourcePath ?? '—'}</span></dd></div>
492
- <div><dt>图片资产</dt><dd>{ingest.assets.length} 个</dd></div>
493
- </dl>
494
-
495
- {ingest.assets.length > 0 && (
496
- <div className={styles.assetGrid}>
497
- {ingest.assets.map(asset => (
498
- <figure key={asset.rel} className={styles.assetCard}>
499
- <img src={`${BASE}/api/tasks/${encodeURIComponent(selectedId)}/files/${asset.rel}`} alt={asset.rel} loading="lazy" />
500
- <figcaption>{formatBytes(asset.bytes)}</figcaption>
501
- </figure>
502
- ))}
503
- </div>
504
- )}
505
-
506
- {ingest.notes.length > 0 && (
507
- <ul className={cn(styles.small, styles.muted)}>
508
- {ingest.notes.map(note => <li key={note}>{note}</li>)}
509
- </ul>
510
- )}
511
-
512
- <h4 className={styles.sectionTitle}>给 Agent 的卡片提示</h4>
513
- <pre className={styles.pre}>{cardPrompt}</pre>
514
- <div className={cn(styles.row, styles.section)}>
515
- <Button variant="ghost" size="sm" onClick={() => copy(cardPrompt)}><Copy size={13} /> 复制</Button>
516
- <Button variant="ghost" size="sm" onClick={() => copy(ingest.cardHints.contentWithImages)}><Copy size={13} /> 复制卡片正文</Button>
517
- </div>
518
- <p className={styles.fieldHint}>
519
- 图片必须在 wiki 卡片里被引用才会进入多模态向量(编译段每个单元只取第一张图),所以这一步不能省。
520
- </p>
521
- </div>
522
- )}
523
- </>
524
- )}
525
- </TabsContent>
526
-
527
- {/* ----------------------------------------------------------- caps */}
528
- <TabsContent value="caps">
529
- {!capabilities && <p className={styles.empty}>能力信息不可用。</p>}
530
- {capabilities && (
531
- <>
532
- <ul className={styles.capList}>
533
- {capabilities.adapters.map(adapter => (
534
- <li key={adapter.id} className={styles.capItem}>
535
- <span className={adapter.availability.available ? styles.dotOn : styles.dotOff} />
536
- <div>
537
- <strong>{adapter.id}</strong>
538
- <div className={styles.taskMeta}>{adapter.description}</div>
539
- {!adapter.availability.available && adapter.availability.reason && (
540
- <div className={cn(styles.taskMeta, styles.error)}>{adapter.availability.reason}</div>
541
- )}
542
- {adapter.availability.suggestion && <div className={styles.taskMeta}>{adapter.availability.suggestion}</div>}
543
- </div>
544
- </li>
545
- ))}
546
- </ul>
547
-
548
- <h4 className={styles.sectionTitle}>支持的输入格式</h4>
549
- <p className={styles.taskMeta}>{(capabilities.supportedExtensions ?? []).join(' ')}</p>
550
-
551
- <h4 className={styles.sectionTitle}>引擎</h4>
552
- <ul className={styles.capList}>
553
- {Object.entries(capabilities.runtime).map(([name, probe]) => (
554
- <li key={name} className={styles.capItem}>
555
- <span className={probe.available ? styles.dotOn : styles.dotOff} />
556
- <div>
557
- <strong>{name}</strong>
558
- <div className={styles.taskMeta}>{probe.available ? probe.version : probe.error}</div>
559
- </div>
560
- </li>
561
- ))}
562
- </ul>
563
-
564
- <h4 className={styles.sectionTitle}>路径</h4>
565
- <dl className={styles.kv}>
566
- <div><dt>交付根目录</dt><dd><span className={styles.code}>{capabilities.outDir}</span></dd></div>
567
- <div><dt>知识库根目录</dt><dd><span className={styles.code}>{capabilities.knowledgeRoot}</span></dd></div>
568
- </dl>
569
- </>
570
- )}
571
- </TabsContent>
572
- </Tabs>
573
-
574
- {selected && markdown && (
575
- <div className={styles.section}>
576
- <h4 className={styles.sectionTitle}><Inbox size={12} /> 交付预览 · {selected.title}</h4>
577
- <pre className={styles.pre}>{markdown.slice(0, 6000)}</pre>
578
- </div>
579
- )}
580
- </div>
581
- </div>
582
- )
583
- }