@p-dsh-market/conversation-knowledge-map 0.1.10 → 0.1.11
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 +1 -1
- package/lib/client.js +39 -2
- package/lib/generation-orchestrator.js +118 -6
- package/lib/workspace-storage.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
|
|
|
11
11
|
|
|
12
12
|
配置面板会列出运行时可用的 Provider / Model,并默认带入 DSH 默认模型;本次选择会绑定到确认令牌、Agent 调用和 `manifest.json`,不会因为默认模型变化而被静默替换。提交任务后配置对话框立即关闭,失败原因会显示在知识视图页的任务条中。
|
|
13
13
|
|
|
14
|
-
摘要阶段按对话并行处理,并发上限固定为 3
|
|
14
|
+
摘要阶段按对话并行处理,并发上限固定为 3;同一对话的超长分段保持顺序处理,随后先合并为一份对话摘要,再统一生成思维导图或知识图谱。进度区域同时显示最近处理动态。结构化生成关闭推理输出以减少等待和 Token 消耗;知识图谱最多生成 20 个实体、30 条关系。最终模型若返回未选择的 Session 引用,会过滤该引用;严格模式下无有效来源的内容项会被跳过,最终页面列出实际总结的对话和过滤数量。
|
|
15
15
|
|
|
16
16
|
节点“继续对话”只形成一个可编辑的后续问题。确认导航后,插件使用公开的 Session 导航入口;如果当前 Runtime 没有向该槽位暴露草稿镜像,则提供“打开并复制问题”的安全降级,不自动发送消息。
|
|
17
17
|
|
package/lib/client.js
CHANGED
|
@@ -260,23 +260,60 @@ window.__ModuleLoader__.load({
|
|
|
260
260
|
]),
|
|
261
261
|
store.generation && store.generation.status !== 'completed' ? React.createElement(GenerationStrip, { key: 'generation', store: store }) : null,
|
|
262
262
|
state && state.compatibility && !state.compatibility.supported ? React.createElement('div', { key: 'compatibility', className: 'ckm-warning ckm-compatibility-warning' }, state.compatibility.message) : null,
|
|
263
|
+
state && state.manifest ? React.createElement(SourceSummary, { key: 'sources', manifest: state.manifest }) : null,
|
|
263
264
|
!state || (!state.mindMap && !state.knowledgeGraph) ? React.createElement(EmptyState, { key: 'empty', title: '尚未生成知识视图', copy: '从标题栏打开“知识视图”,选择同工作路径下的对话并确认生成。' }) : (mode === 'mind-map'
|
|
264
265
|
? React.createElement(MindMapPanel, { key: 'mind', store: store, selectedNodeId: selectedNodeId, setSelectedNodeId: setSelectedNodeId })
|
|
265
266
|
: React.createElement(KnowledgeGraphPanel, { key: 'graph', store: store }))
|
|
266
267
|
])
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
function SourceSummary(props) {
|
|
271
|
+
var manifest = props.manifest || {}
|
|
272
|
+
var sources = Array.isArray(manifest.sourceSessions) && manifest.sourceSessions.length
|
|
273
|
+
? manifest.sourceSessions
|
|
274
|
+
: (manifest.sourceSessionIds || []).map(function (sessionId) { return { sessionId: sessionId, title: '' } })
|
|
275
|
+
var warnings = manifest.sourceWarnings || {}
|
|
276
|
+
if (!sources.length) return null
|
|
277
|
+
return React.createElement('section', { className: 'ckm-source-summary', 'aria-label': '本次总结来源' }, [
|
|
278
|
+
React.createElement('div', { key: 'head', className: 'ckm-source-summary-head' }, [
|
|
279
|
+
React.createElement('strong', { key: 'title' }, '已总结 ' + sources.length + ' 个对话'),
|
|
280
|
+
warnings.skippedItems || warnings.skippedRefs ? React.createElement('span', { key: 'warning', className: 'ckm-source-warning' }, '已过滤 ' + (warnings.skippedRefs || 0) + ' 个无效引用、跳过 ' + (warnings.skippedItems || 0) + ' 个内容项') : null
|
|
281
|
+
]),
|
|
282
|
+
React.createElement('div', { key: 'list', className: 'ckm-source-list' }, sources.map(function (source) {
|
|
283
|
+
var sessionId = String(source.sessionId || '')
|
|
284
|
+
var label = source.title ? source.title + ' · ' + sessionId.slice(0, 16) + '…' : sessionId
|
|
285
|
+
return React.createElement('span', { key: sessionId, className: 'ckm-source-chip', title: sessionId }, label)
|
|
286
|
+
}))
|
|
287
|
+
])
|
|
288
|
+
}
|
|
289
|
+
|
|
269
290
|
function GenerationStrip(props) {
|
|
270
291
|
var generation = props.store.generation
|
|
271
292
|
var progress = generation.progress || { percent: 0, current: 0, total: 0, label: '' }
|
|
272
293
|
var percent = Math.max(0, Math.min(100, Number(progress.percent) || 0))
|
|
294
|
+
var seen = Object.create(null)
|
|
295
|
+
var activities = []
|
|
296
|
+
;(generation.events || []).slice().reverse().some(function (event) {
|
|
297
|
+
var label = event && event.progress && event.progress.label
|
|
298
|
+
if (!label || seen[label]) return false
|
|
299
|
+
seen[label] = true
|
|
300
|
+
activities.push({ label: label, status: event.status })
|
|
301
|
+
return activities.length >= 6
|
|
302
|
+
})
|
|
303
|
+
activities.reverse()
|
|
273
304
|
return React.createElement('div', { className: 'ckm-generation-strip', 'data-status': generation.status }, [
|
|
274
305
|
React.createElement('div', { key: 'body', className: 'ckm-generation-body' }, [
|
|
275
306
|
React.createElement('div', { key: 'status-row', className: 'ckm-generation-status-row' }, [
|
|
276
307
|
React.createElement('strong', { key: 'status' }, generation.message || generation.status),
|
|
277
308
|
React.createElement('span', { key: 'detail' }, progress.total ? (progress.current + '/' + progress.total + ' 个对话 · ' + progress.label) : progress.label)
|
|
278
309
|
]),
|
|
279
|
-
React.createElement('div', { key: 'progress', className: 'ckm-progress-track', role: 'progressbar', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': percent, 'aria-label': '知识视图生成进度' }, React.createElement('span', { className: 'ckm-progress-value', style: { width: percent + '%' } }))
|
|
310
|
+
React.createElement('div', { key: 'progress', className: 'ckm-progress-track', role: 'progressbar', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': percent, 'aria-label': '知识视图生成进度' }, React.createElement('span', { className: 'ckm-progress-value', style: { width: percent + '%' } })),
|
|
311
|
+
activities.length ? React.createElement('div', { key: 'activity', className: 'ckm-progress-activity' }, [
|
|
312
|
+
React.createElement('strong', { key: 'title' }, '最近处理动态'),
|
|
313
|
+
React.createElement('ul', { key: 'list' }, activities.map(function (activity, index) {
|
|
314
|
+
return React.createElement('li', { key: activity.status + '-' + activity.label + '-' + index, 'data-status': activity.status }, activity.label)
|
|
315
|
+
}))
|
|
316
|
+
]) : null
|
|
280
317
|
]),
|
|
281
318
|
generation.error ? React.createElement('span', { key: 'error', className: 'ckm-generation-error' }, generation.error) : null,
|
|
282
319
|
['failed', 'cancelled'].includes(generation.status) ? null : Button({ key: 'cancel', className: 'ckm-danger', onClick: function () { request('/generations/' + encodeURIComponent(generation.id) + '/cancel', { method: 'POST' }).then(function (body) { applyGeneration(props.store, body.generation) }).catch(function (error) { props.store.error = error.message; notifyStore(props.store) }) } }, '取消')
|
|
@@ -571,7 +608,7 @@ window.__ModuleLoader__.load({
|
|
|
571
608
|
'.ckm-page-header h2{margin:0;font-size:16px}.ckm-page-header p{margin:3px 0 0;color:var(--dsw-alias-label-secondary);font-size:11px;word-break:break-all}' +
|
|
572
609
|
'.ckm-page-actions,.ckm-modal-actions{display:flex;align-items:center;gap:8px}.ckm-tab,.ckm-secondary,.ckm-primary,.ckm-danger,.ckm-header-action{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 11px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;font-size:12px}.ckm-tab[data-active=true],.ckm-primary{border-color:var(--dsw-alias-button-info-fill);background:var(--dsw-alias-button-info-fill);color:var(--dsw-alias-label-primary-foreground)}.ckm-tab:hover,.ckm-secondary:hover,.ckm-header-action:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-primary:hover{background:var(--dsw-alias-button-info-hover)}.ckm-danger{border-color:#d36b6b;color:#ffb7b7}.ckm-danger:hover{background:#d94a4a22}.ckm-page button:disabled,.ckm-modal button:disabled{opacity:.5;cursor:not-allowed}' +
|
|
573
610
|
'.ckm-empty{display:flex;flex:1;min-height:260px;flex-direction:column;align-items:center;justify-content:center;padding:28px;text-align:center}.ckm-empty-icon{display:grid;place-items:center;width:44px;height:44px;margin-bottom:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;color:var(--dsw-alias-state-business-primary);font-size:24px}.ckm-empty h2{margin:0 0 8px;font-size:17px}.ckm-empty p{max-width:520px;margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7}' +
|
|
574
|
-
'.ckm-generation-strip{display:flex;align-items:
|
|
611
|
+
'.ckm-generation-strip{display:flex;align-items:flex-start;gap:12px;padding:10px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-state-business-tertiary);font-size:12px}.ckm-generation-body{display:grid;gap:7px;flex:1;min-width:0}.ckm-generation-status-row{display:flex;justify-content:space-between;gap:12px}.ckm-generation-status-row span{color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ckm-progress-track{height:6px;overflow:hidden;border-radius:999px;background:var(--dsw-alias-bg-layer-1)}.ckm-progress-value{display:block;height:100%;border-radius:inherit;background:var(--dsw-alias-state-business-primary);transition:width .25s ease}.ckm-progress-activity{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;color:var(--dsw-alias-label-secondary)}.ckm-progress-activity ul{display:flex;flex-wrap:wrap;gap:5px 12px;margin:0;padding:0;list-style:none}.ckm-progress-activity li:before{content:"·";margin-right:5px;color:var(--dsw-alias-state-business-primary)}.ckm-generation-error{color:#ff9898;max-width:38%;overflow-wrap:anywhere}.ckm-generation-strip .ckm-danger{margin-left:auto}.ckm-source-summary{display:grid;gap:8px;padding:10px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)}.ckm-source-summary-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px}.ckm-source-warning{color:#d69a42}.ckm-source-list{display:flex;flex-wrap:wrap;gap:6px}.ckm-source-chip{max-width:360px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-secondary);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
|
|
575
612
|
'.ckm-workspace{display:grid;grid-template-columns:minmax(0,1fr) 330px;min-height:0;flex:1}.ckm-mind-canvas,.ckm-graph-canvas{min-width:0;min-height:0;overflow:auto;padding:20px;background:var(--dsw-alias-bg-base)}.ckm-detail{min-width:0;min-height:0;overflow:auto;padding:20px;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)}.ckm-panel-hint{margin:0 0 14px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.6}.ckm-tree{display:flex;flex-direction:column;gap:8px;max-width:900px;margin:0 auto}.ckm-tree-node{display:flex;flex-direction:column;align-items:flex-start;gap:5px;width:calc(100% - 0px);padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer}.ckm-tree-node[data-active=true]{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}.ckm-tree-node:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-tree-node strong{font-size:13px}.ckm-tree-node span:last-child{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.65}.ckm-node-type{display:inline-block;color:var(--dsw-alias-state-business-primary);font-size:10px;letter-spacing:.04em;text-transform:uppercase}.ckm-detail-head{display:flex;flex-direction:column;gap:5px}.ckm-detail h3{margin:0;font-size:16px;line-height:1.45}.ckm-narrative{font-size:13px;line-height:1.8}.ckm-source-box{margin:16px 0;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-base);font-size:11px;line-height:1.6}.ckm-source-box strong{display:block;margin-bottom:5px}.ckm-source-box ul{margin:0;padding-left:18px;color:var(--dsw-alias-label-secondary)}.ckm-detail-empty{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.7}' +
|
|
576
613
|
'.ckm-graph-layout{grid-template-columns:minmax(0,1fr) 330px}.ckm-graph-toolbar{display:flex;gap:8px}.ckm-model-selectors{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:8px}.ckm-graph-toolbar input,.ckm-graph-toolbar select,.ckm-field textarea,.ckm-field select,.ckm-field input,.ckm-modal textarea,.ckm-modal select{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 9px;background:var(--dsw-specific-input-major);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}.ckm-graph-toolbar input{flex:1}.ckm-graph-svg{display:block;width:100%;min-height:420px;margin-top:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.ckm-edge{stroke:var(--dsw-alias-border-l2);stroke-width:1.5}.ckm-graph-node{cursor:pointer}.ckm-graph-node circle{fill:var(--dsw-alias-state-business-tertiary);stroke:var(--dsw-alias-state-business-primary);stroke-width:1.5}.ckm-graph-node[data-active=true] circle{fill:var(--dsw-alias-button-info-fill);stroke:var(--dsw-alias-label-primary)}.ckm-graph-node text{fill:var(--dsw-alias-label-primary);font-size:11px}' +
|
|
577
614
|
'.ckm-modal-backdrop{position:fixed;inset:0;z-index:120;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(0,0,0,.46)}.ckm-modal{width:min(680px,calc(100vw - 40px));max-height:min(760px,calc(100vh - 40px));overflow:auto;padding:22px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;box-shadow:0 18px 56px #0008}.ckm-modal h3{margin:0;font-size:17px}.ckm-modal p{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.65}.ckm-modal-head{display:flex;align-items:center;justify-content:space-between}.ckm-icon-close{border:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:20px;cursor:pointer}.ckm-workspace-label{padding:9px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);word-break:break-all}.ckm-field{display:flex;flex-direction:column;gap:7px;margin:14px 0;color:var(--dsw-alias-label-secondary);font-size:12px}.ckm-field textarea{resize:vertical}.ckm-session-list{display:flex;max-height:220px;flex-direction:column;gap:5px;overflow:auto}.ckm-session-option{display:flex;align-items:flex-start;gap:8px;padding:8px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-bg-layer-1);cursor:pointer}.ckm-session-option:hover{border-color:var(--dsw-alias-border-l2)}.ckm-session-option input,.ckm-inline-field input{margin-top:3px}.ckm-session-option span{display:flex;flex-direction:column;gap:3px}.ckm-session-option small{color:var(--dsw-alias-label-secondary);font-size:10px}.ckm-inline-field{display:flex;align-items:flex-start;gap:7px;margin:10px 0;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.5}.ckm-confirm-summary{display:grid;grid-template-columns:100px 1fr;gap:7px 12px;margin:18px 0;font-size:12px}.ckm-confirm-summary dt{color:var(--dsw-alias-label-secondary)}.ckm-confirm-summary dd{margin:0;word-break:break-all}.ckm-warning{padding:10px;border-radius:8px;background:#d29c2518;color:var(--dsw-alias-label-secondary)}.ckm-error{color:#ff9898!important}.ckm-modal-actions{justify-content:flex-end;margin-top:18px}.ckm-header-pending{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}' +
|
|
@@ -198,6 +198,94 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
198
198
|
return results
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
function filterSourceRefs(refs, sources) {
|
|
202
|
+
const allowed = new Map(sources.map((source) => [source.sessionId, new Set((source.events || []).flatMap((event) => [event.seq, ...(event.sourceEventSeqs || [])]))]))
|
|
203
|
+
let skipped = 0
|
|
204
|
+
const filtered = []
|
|
205
|
+
for (const ref of Array.isArray(refs) ? refs : []) {
|
|
206
|
+
const sessionId = String(ref?.sessionId || '')
|
|
207
|
+
const allowedSeqs = allowed.get(sessionId)
|
|
208
|
+
if (!allowedSeqs) {
|
|
209
|
+
skipped += 1
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
const eventSeqs = [...new Set((Array.isArray(ref?.eventSeqs) ? ref.eventSeqs : []).filter((seq) => Number.isInteger(seq) && allowedSeqs.has(seq)))]
|
|
213
|
+
if (!eventSeqs.length) {
|
|
214
|
+
skipped += 1
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
filtered.push({ sessionId, eventSeqs })
|
|
218
|
+
}
|
|
219
|
+
return { refs: filtered.slice(0, 12), skipped }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function sanitizeMindMapSources(value, sources, strict) {
|
|
223
|
+
const rawNodes = Array.isArray(value?.nodes) ? value.nodes : []
|
|
224
|
+
const candidates = rawNodes.map((raw, index) => ({ ...raw, id: String(raw?.id || `mind-node-${index + 1}`) }))
|
|
225
|
+
const originalById = new Map(candidates.map((node) => [node.id, node]))
|
|
226
|
+
let skippedRefs = 0
|
|
227
|
+
let skippedItems = 0
|
|
228
|
+
let nodes = candidates.map((node) => {
|
|
229
|
+
const filtered = filterSourceRefs(node.sourceRefs, sources)
|
|
230
|
+
skippedRefs += filtered.skipped
|
|
231
|
+
const primary = filtered.refs.some((ref) => ref.sessionId === node.primarySourceSessionId)
|
|
232
|
+
? node.primarySourceSessionId
|
|
233
|
+
: (filtered.refs[0]?.sessionId || '')
|
|
234
|
+
return { ...node, sourceRefs: filtered.refs, primarySourceSessionId: primary }
|
|
235
|
+
})
|
|
236
|
+
if (strict) {
|
|
237
|
+
const before = nodes.length
|
|
238
|
+
nodes = nodes.filter((node) => node.sourceRefs.length)
|
|
239
|
+
skippedItems += before - nodes.length
|
|
240
|
+
}
|
|
241
|
+
if (!nodes.length) return { value: { ...value, nodes }, skippedRefs, skippedItems }
|
|
242
|
+
const surviving = new Set(nodes.map((node) => node.id))
|
|
243
|
+
let rootId = surviving.has(String(value?.rootId || '')) ? String(value.rootId) : nodes[0].id
|
|
244
|
+
const nearestParent = (node) => {
|
|
245
|
+
let parentId = node.parentId === null || node.parentId === undefined ? '' : String(node.parentId)
|
|
246
|
+
const visited = new Set([node.id])
|
|
247
|
+
while (parentId && !surviving.has(parentId) && !visited.has(parentId)) {
|
|
248
|
+
visited.add(parentId)
|
|
249
|
+
const parent = originalById.get(parentId)
|
|
250
|
+
parentId = parent?.parentId === null || parent?.parentId === undefined ? '' : String(parent.parentId)
|
|
251
|
+
}
|
|
252
|
+
return surviving.has(parentId) ? parentId : null
|
|
253
|
+
}
|
|
254
|
+
nodes = nodes.map((node) => {
|
|
255
|
+
if (node.id === rootId) return { ...node, parentId: null }
|
|
256
|
+
const parentId = nearestParent(node)
|
|
257
|
+
return { ...node, parentId: parentId && parentId !== node.id ? parentId : rootId }
|
|
258
|
+
})
|
|
259
|
+
return { value: { ...value, rootId, nodes }, skippedRefs, skippedItems }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function sanitizeKnowledgeGraphSources(value, sources, strict) {
|
|
263
|
+
let skippedRefs = 0
|
|
264
|
+
let skippedItems = 0
|
|
265
|
+
let entities = (Array.isArray(value?.entities) ? value.entities : []).map((entity) => {
|
|
266
|
+
const filtered = filterSourceRefs(entity?.sourceRefs, sources)
|
|
267
|
+
skippedRefs += filtered.skipped
|
|
268
|
+
return { ...entity, sourceRefs: filtered.refs }
|
|
269
|
+
})
|
|
270
|
+
if (strict) {
|
|
271
|
+
const before = entities.length
|
|
272
|
+
entities = entities.filter((entity) => entity.sourceRefs.length)
|
|
273
|
+
skippedItems += before - entities.length
|
|
274
|
+
}
|
|
275
|
+
const entityIds = new Set(entities.map((entity) => String(entity?.id || '')))
|
|
276
|
+
let relations = []
|
|
277
|
+
for (const relation of Array.isArray(value?.relations) ? value.relations : []) {
|
|
278
|
+
const filtered = filterSourceRefs(relation?.evidence, sources)
|
|
279
|
+
skippedRefs += filtered.skipped
|
|
280
|
+
if (!entityIds.has(String(relation?.from || '')) || !entityIds.has(String(relation?.to || '')) || (strict && !filtered.refs.length)) {
|
|
281
|
+
skippedItems += 1
|
|
282
|
+
continue
|
|
283
|
+
}
|
|
284
|
+
relations.push({ ...relation, evidence: filtered.refs })
|
|
285
|
+
}
|
|
286
|
+
return { value: { ...value, entities, relations }, skippedRefs, skippedItems }
|
|
287
|
+
}
|
|
288
|
+
|
|
201
289
|
function assertOutputSourceRefs(value, sources) {
|
|
202
290
|
const allowed = new Map(sources.map((source) => [source.sessionId, new Set((source.events || []).flatMap((event) => [event.seq, ...(event.sourceEventSeqs || [])]))]))
|
|
203
291
|
const check = (ref, label) => {
|
|
@@ -562,13 +650,14 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
562
650
|
completedSources += 1
|
|
563
651
|
this.update(task, 'summarizing', {
|
|
564
652
|
sourceCount: sources.length,
|
|
565
|
-
progress: { percent: 10 + Math.floor(60 * completedSources / sources.length), current: completedSources, total: sources.length, label: source.title }
|
|
653
|
+
progress: { percent: 10 + Math.floor(60 * completedSources / sources.length), current: completedSources, total: sources.length, label: `已完成摘要:${source.title}` }
|
|
566
654
|
})
|
|
567
655
|
return mergeSourceSummaries(source, chunkSummaries)
|
|
568
656
|
})
|
|
569
657
|
this.assertNotCancelled(task)
|
|
570
658
|
let mindMap = null
|
|
571
659
|
let knowledgeGraph = null
|
|
660
|
+
const sourceWarnings = { skippedRefs: 0, skippedItems: 0 }
|
|
572
661
|
if (request.outputMode === 'mind-map' || request.outputMode === 'both') {
|
|
573
662
|
this.update(task, 'building-mind-map', { progress: { percent: 78, current: sources.length, total: sources.length, label: '生成思维导图' } })
|
|
574
663
|
const mindMapPrompt = outputPrompt('mind-map', summaries, request.prompt, request.strict === true)
|
|
@@ -578,7 +667,10 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
578
667
|
})
|
|
579
668
|
try {
|
|
580
669
|
const value = await generateMindMap(mindMapPrompt)
|
|
581
|
-
|
|
670
|
+
const sanitized = sanitizeMindMapSources(value, sources, request.strict === true)
|
|
671
|
+
mindMap = validateMindMap(sanitized.value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
672
|
+
sourceWarnings.skippedRefs += sanitized.skippedRefs
|
|
673
|
+
sourceWarnings.skippedItems += sanitized.skippedItems
|
|
582
674
|
} catch (error) {
|
|
583
675
|
this.assertNotCancelled(task)
|
|
584
676
|
logMessage(this.logger, 'warn', 'mind map first attempt invalid id=%s error=%s', logId(task.id), errorMessage(error))
|
|
@@ -588,7 +680,10 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
588
680
|
})
|
|
589
681
|
const retryPrompt = `${mindMapPrompt}\n\n上一次输出未通过校验:${shortText(errorMessage(error), 500)}。请从头重新生成,不要复用上一次文本;只输出完整、严格合法的 JSON。`
|
|
590
682
|
const retryValue = await generateMindMap(retryPrompt)
|
|
591
|
-
|
|
683
|
+
const sanitized = sanitizeMindMapSources(retryValue, sources, request.strict === true)
|
|
684
|
+
mindMap = validateMindMap(sanitized.value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
685
|
+
sourceWarnings.skippedRefs += sanitized.skippedRefs
|
|
686
|
+
sourceWarnings.skippedItems += sanitized.skippedItems
|
|
592
687
|
}
|
|
593
688
|
}
|
|
594
689
|
if (request.outputMode === 'knowledge-graph' || request.outputMode === 'both') {
|
|
@@ -602,7 +697,17 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
602
697
|
model: request.model,
|
|
603
698
|
signal: task.controller.signal
|
|
604
699
|
})
|
|
605
|
-
|
|
700
|
+
const sanitized = sanitizeKnowledgeGraphSources(value, sources, request.strict === true)
|
|
701
|
+
sourceWarnings.skippedRefs += sanitized.skippedRefs
|
|
702
|
+
sourceWarnings.skippedItems += sanitized.skippedItems
|
|
703
|
+
knowledgeGraph = validateKnowledgeGraph(sanitized.value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
704
|
+
}
|
|
705
|
+
if (sourceWarnings.skippedRefs || sourceWarnings.skippedItems) {
|
|
706
|
+
logMessage(this.logger, 'warn', 'generation filtered invalid sources id=%s skippedRefs=%d skippedItems=%d', logId(task.id), sourceWarnings.skippedRefs, sourceWarnings.skippedItems)
|
|
707
|
+
this.update(task, 'validating', {
|
|
708
|
+
message: `已跳过 ${sourceWarnings.skippedRefs} 个无效来源引用、${sourceWarnings.skippedItems} 个无有效来源的内容项。`,
|
|
709
|
+
progress: { percent: 90, current: sources.length, total: sources.length, label: '过滤无效来源' }
|
|
710
|
+
})
|
|
606
711
|
}
|
|
607
712
|
assertOutputSourceRefs({ mindMap, knowledgeGraph }, sources)
|
|
608
713
|
this.assertNotCancelled(task)
|
|
@@ -613,6 +718,8 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
613
718
|
expectedRevision: request.expectedRevision,
|
|
614
719
|
generationId: task.id,
|
|
615
720
|
sourceSessionIds,
|
|
721
|
+
sourceSessions: sources.map((source) => ({ sessionId: source.sessionId, title: source.title })),
|
|
722
|
+
sourceWarnings,
|
|
616
723
|
prompt: request.prompt,
|
|
617
724
|
strict: request.strict,
|
|
618
725
|
outputMode: request.outputMode,
|
|
@@ -621,9 +728,14 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
621
728
|
knowledgeGraph
|
|
622
729
|
})
|
|
623
730
|
task.revision = saved.revision
|
|
624
|
-
task.result = { revision: saved.revision, manifest: saved.manifest, mindMap, knowledgeGraph }
|
|
731
|
+
task.result = { revision: saved.revision, manifest: saved.manifest, mindMap, knowledgeGraph, sourceSessions: saved.manifest.sourceSessions, sourceWarnings }
|
|
625
732
|
logMessage(this.logger, 'info', 'generation completed id=%s revision=%d elapsedMs=%d', logId(task.id), saved.revision, this.now() - task.createdAt)
|
|
626
|
-
this.update(task, 'completed', {
|
|
733
|
+
this.update(task, 'completed', {
|
|
734
|
+
message: `知识视图生成完成,已总结 ${sources.length} 个对话${sourceWarnings.skippedItems ? `,跳过 ${sourceWarnings.skippedItems} 个无有效来源的内容项` : ''}。`,
|
|
735
|
+
revision: saved.revision,
|
|
736
|
+
result: task.result,
|
|
737
|
+
progress: { percent: 100, current: sources.length, total: sources.length, label: '合并并生成最终报告完成' }
|
|
738
|
+
})
|
|
627
739
|
} catch (error) {
|
|
628
740
|
if (task.controller.signal.aborted || /取消|cancel/i.test(errorMessage(error))) {
|
|
629
741
|
this.update(task, 'cancelled', { error: '' })
|
package/lib/workspace-storage.js
CHANGED
|
@@ -151,7 +151,7 @@ export class WorkspaceStorage {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
async saveBundle({ cwd, expectedRevision = 0, generationId, sourceSessionIds, prompt, strict, outputMode, model, mindMap, knowledgeGraph }) {
|
|
154
|
+
async saveBundle({ cwd, expectedRevision = 0, generationId, sourceSessionIds, sourceSessions, sourceWarnings, prompt, strict, outputMode, model, mindMap, knowledgeGraph }) {
|
|
155
155
|
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
156
156
|
const key = workspaceKey(normalizedCwd)
|
|
157
157
|
const previous = this.locks.get(key) || Promise.resolve()
|
|
@@ -166,6 +166,14 @@ export class WorkspaceStorage {
|
|
|
166
166
|
revision,
|
|
167
167
|
cwd: normalizedCwd,
|
|
168
168
|
sourceSessionIds: [...new Set((sourceSessionIds || []).map(String))],
|
|
169
|
+
sourceSessions: Array.isArray(sourceSessions) ? sourceSessions.map((source) => ({
|
|
170
|
+
sessionId: String(source?.sessionId || ''),
|
|
171
|
+
title: shortText(source?.title, 200)
|
|
172
|
+
})).filter((source) => source.sessionId) : [],
|
|
173
|
+
sourceWarnings: {
|
|
174
|
+
skippedRefs: Math.max(0, Number(sourceWarnings?.skippedRefs) || 0),
|
|
175
|
+
skippedItems: Math.max(0, Number(sourceWarnings?.skippedItems) || 0)
|
|
176
|
+
},
|
|
169
177
|
promptSummary: shortText(prompt, 500),
|
|
170
178
|
strict: strict === true,
|
|
171
179
|
outputMode: String(outputMode || 'both'),
|