@p-dsh-market/conversation-knowledge-map 0.1.7 → 0.1.9
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/lib/client.js +11 -3
- package/lib/generation-orchestrator.js +72 -32
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -268,9 +268,17 @@ window.__ModuleLoader__.load({
|
|
|
268
268
|
|
|
269
269
|
function GenerationStrip(props) {
|
|
270
270
|
var generation = props.store.generation
|
|
271
|
+
var progress = generation.progress || { percent: 0, current: 0, total: 0, label: '' }
|
|
272
|
+
var percent = Math.max(0, Math.min(100, Number(progress.percent) || 0))
|
|
271
273
|
return React.createElement('div', { className: 'ckm-generation-strip', 'data-status': generation.status }, [
|
|
272
|
-
React.createElement('
|
|
273
|
-
|
|
274
|
+
React.createElement('div', { key: 'body', className: 'ckm-generation-body' }, [
|
|
275
|
+
React.createElement('div', { key: 'status-row', className: 'ckm-generation-status-row' }, [
|
|
276
|
+
React.createElement('strong', { key: 'status' }, generation.message || generation.status),
|
|
277
|
+
React.createElement('span', { key: 'detail' }, progress.total ? (progress.current + '/' + progress.total + ' 个对话 · ' + progress.label) : progress.label)
|
|
278
|
+
]),
|
|
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 + '%' } }))
|
|
280
|
+
]),
|
|
281
|
+
generation.error ? React.createElement('span', { key: 'error', className: 'ckm-generation-error' }, generation.error) : null,
|
|
274
282
|
['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) }) } }, '取消')
|
|
275
283
|
])
|
|
276
284
|
}
|
|
@@ -563,7 +571,7 @@ window.__ModuleLoader__.load({
|
|
|
563
571
|
'.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}' +
|
|
564
572
|
'.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}' +
|
|
565
573
|
'.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}' +
|
|
566
|
-
'.ckm-generation-strip{display:flex;align-items:center;gap:12px;padding:
|
|
574
|
+
'.ckm-generation-strip{display:flex;align-items:center;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-generation-error{color:#ff9898;max-width:38%;overflow-wrap:anywhere}.ckm-generation-strip .ckm-danger{margin-left:auto}' +
|
|
567
575
|
'.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}' +
|
|
568
576
|
'.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}' +
|
|
569
577
|
'.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)}' +
|
|
@@ -68,7 +68,16 @@ function textFromContent(value) {
|
|
|
68
68
|
return ''
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function
|
|
71
|
+
function expectedObject(value, kind = '') {
|
|
72
|
+
if (!kind) return true
|
|
73
|
+
if (kind === 'summary') return typeof value?.summary === 'string' || typeof value?.narrative === 'string' || typeof value?.text === 'string'
|
|
74
|
+
if (kind === 'mind-map') return Array.isArray(value?.nodes)
|
|
75
|
+
if (kind === 'knowledge-graph') return Array.isArray(value?.entities) && Array.isArray(value?.relations)
|
|
76
|
+
if (kind === 'follow-up') return typeof value?.question === 'string'
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findJsonObject(text, kind = '') {
|
|
72
81
|
const candidates = []
|
|
73
82
|
const fenced = String(text || '').match(/```(?:json)?\s*([\s\S]*?)```/i)
|
|
74
83
|
if (fenced) candidates.push(fenced[1])
|
|
@@ -98,7 +107,7 @@ function findJsonObject(text) {
|
|
|
98
107
|
const fragment = candidate.slice(start, index + 1)
|
|
99
108
|
try {
|
|
100
109
|
const parsed = JSON.parse(fragment)
|
|
101
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed
|
|
110
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && expectedObject(parsed, kind)) return parsed
|
|
102
111
|
} catch {
|
|
103
112
|
// Try the next opening brace in case prose contained an example.
|
|
104
113
|
}
|
|
@@ -111,11 +120,11 @@ function findJsonObject(text) {
|
|
|
111
120
|
return null
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
export function parseStructuredOutput(value) {
|
|
123
|
+
export function parseStructuredOutput(value, kind = '') {
|
|
115
124
|
const object = asObject(value)
|
|
116
|
-
if (object) return object
|
|
125
|
+
if (object && expectedObject(object, kind)) return object
|
|
117
126
|
const text = textFromContent(value).replace(/^\uFEFF/, '').trim()
|
|
118
|
-
const parsed = findJsonObject(text)
|
|
127
|
+
const parsed = findJsonObject(text, kind)
|
|
119
128
|
if (parsed) return parsed
|
|
120
129
|
if (!text) throw new Error('模型没有返回 JSON 对象。')
|
|
121
130
|
throw new Error('模型返回了文本,但其中没有可解析的 JSON 对象。')
|
|
@@ -178,6 +187,7 @@ function outputPrompt(kind, summaries, prompt, strict) {
|
|
|
178
187
|
if (kind === 'mind-map') {
|
|
179
188
|
return [
|
|
180
189
|
'请根据以下多个对话摘要生成阶段性思维导图。节点不是关键词,narrative 必须是至少一段完整说明,包含背景、当前认识和下一步/未决点。',
|
|
190
|
+
'最多生成 12 个节点;每个 narrative 控制在 300 字以内。不要输出 schema 之外的字段。确保最终内容是可被 JSON.parse 直接解析的完整 JSON。',
|
|
181
191
|
'JSON 形状:{"rootId":"...","nodes":[{"id":"...","parentId":null,"type":"theme|stage|question|decision|solution|risk|conclusion","title":"...","narrative":"...","primarySourceSessionId":"...","sourceRefs":[{"sessionId":"...","eventSeqs":[1]}],"openQuestions":[]}]}。',
|
|
182
192
|
...rules,
|
|
183
193
|
`额外要求:${shortText(prompt, 2000) || '没有额外要求。'}`,
|
|
@@ -197,6 +207,7 @@ function summaryPrompt(source, chunk, strict) {
|
|
|
197
207
|
return [
|
|
198
208
|
'请把一段 DSH 对话整理成带来源的结构化摘要。不要复述完整聊天,不要添加对话中没有的事实。',
|
|
199
209
|
'只输出 JSON:{"summary":"完整阶段性说明","keyPoints":["..."],"sourceRefs":[{"sessionId":"...","eventSeqs":[1]}]}。',
|
|
210
|
+
'summary 控制在 1200 字以内;keyPoints 最多 8 条,每条不超过 160 字。不要增加其他字段。字符串中的双引号必须正确转义,确保整个输出可以被 JSON.parse 直接解析。',
|
|
200
211
|
strict ? '严格模式:每个关键点都要能回指给定事件。' : '允许标记尚未确认的冲突,但不能编造事件序号。',
|
|
201
212
|
`Session:${source.sessionId}`,
|
|
202
213
|
`标题:${source.title}`,
|
|
@@ -397,6 +408,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
397
408
|
createdAt: task.createdAt,
|
|
398
409
|
updatedAt: task.updatedAt,
|
|
399
410
|
revision: task.revision || 0,
|
|
411
|
+
progress: task.progress ? clone(task.progress) : null,
|
|
400
412
|
result: task.result ? clone(task.result) : null,
|
|
401
413
|
events: task.events.slice(-40)
|
|
402
414
|
}
|
|
@@ -437,6 +449,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
437
449
|
message: phaseMessage('created'),
|
|
438
450
|
error: '',
|
|
439
451
|
result: null,
|
|
452
|
+
progress: { percent: 0, current: 0, total: Array.isArray(request.selectedSessionIds) ? request.selectedSessionIds.length : 0, label: '准备生成' },
|
|
440
453
|
revision: 0,
|
|
441
454
|
createdAt: this.now(),
|
|
442
455
|
updatedAt: this.now(),
|
|
@@ -470,7 +483,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
470
483
|
|
|
471
484
|
async run(task, request) {
|
|
472
485
|
try {
|
|
473
|
-
this.update(task, 'reading-sources')
|
|
486
|
+
this.update(task, 'reading-sources', { progress: { percent: 5, current: 0, total: request.selectedSessionIds.length, label: '读取对话记录' } })
|
|
474
487
|
const sources = await this.sourceReader({ sessionQuery: this.sessionQuery, sessions: this.sessions }, {
|
|
475
488
|
cwd: request.cwd,
|
|
476
489
|
sessionIds: request.selectedSessionIds,
|
|
@@ -480,41 +493,68 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
480
493
|
const sourceSessionIds = [...new Set(sources.map((source) => source.sessionId))]
|
|
481
494
|
this.assertNotCancelled(task)
|
|
482
495
|
logMessage(this.logger, 'info', 'generation sources loaded id=%s sources=%d events=%d', logId(task.id), sources.length, sources.reduce((total, source) => total + (Array.isArray(source.events) ? source.events.length : 0), 0))
|
|
483
|
-
this.update(task, 'summarizing', { sourceCount: sources.length })
|
|
496
|
+
this.update(task, 'summarizing', { sourceCount: sources.length, progress: { percent: 10, current: 0, total: sources.length, label: '准备整理对话' } })
|
|
484
497
|
const summaries = []
|
|
485
|
-
for (
|
|
498
|
+
for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
|
|
499
|
+
const source = sources[sourceIndex]
|
|
500
|
+
this.update(task, 'summarizing', {
|
|
501
|
+
sourceCount: sources.length,
|
|
502
|
+
progress: { percent: 10 + Math.floor(60 * sourceIndex / sources.length), current: sourceIndex, total: sources.length, label: source.title }
|
|
503
|
+
})
|
|
486
504
|
for (const chunk of chunkSourceText(source)) {
|
|
487
505
|
this.assertNotCancelled(task)
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
prompt:
|
|
491
|
-
|
|
492
|
-
strict: request.strict === true,
|
|
493
|
-
selectedSessionIds: sourceSessionIds,
|
|
494
|
-
model: request.model,
|
|
495
|
-
signal: task.controller.signal
|
|
506
|
+
const prompt = summaryPrompt(source, chunk, request.strict === true)
|
|
507
|
+
const generateSummary = async (summaryInput) => this.runModel({
|
|
508
|
+
kind: 'summary', prompt: summaryInput, cwd: request.cwd, strict: request.strict === true,
|
|
509
|
+
selectedSessionIds: sourceSessionIds, model: request.model, signal: task.controller.signal
|
|
496
510
|
})
|
|
497
|
-
|
|
511
|
+
try {
|
|
512
|
+
const value = await generateSummary(prompt)
|
|
513
|
+
summaries.push(normalizeSummary(value, source, chunk))
|
|
514
|
+
} catch (error) {
|
|
515
|
+
this.assertNotCancelled(task)
|
|
516
|
+
logMessage(this.logger, 'warn', 'summary first attempt invalid id=%s session=%s error=%s', logId(task.id), logId(source.sessionId), errorMessage(error))
|
|
517
|
+
this.update(task, 'summarizing', {
|
|
518
|
+
message: `对话 ${sourceIndex + 1}/${sources.length} 摘要结构无效,正在自动修复…`,
|
|
519
|
+
progress: { percent: 10 + Math.floor(60 * sourceIndex / sources.length), current: sourceIndex, total: sources.length, label: `自动修复:${source.title}` }
|
|
520
|
+
})
|
|
521
|
+
const retryPrompt = `${prompt}\n\n上一次输出未通过校验:${shortText(errorMessage(error), 500)}。请从头重新整理,缩短内容,只输出严格合法的顶层摘要 JSON。`
|
|
522
|
+
const retryValue = await generateSummary(retryPrompt)
|
|
523
|
+
summaries.push(normalizeSummary(retryValue, source, chunk))
|
|
524
|
+
}
|
|
498
525
|
}
|
|
526
|
+
this.update(task, 'summarizing', {
|
|
527
|
+
sourceCount: sources.length,
|
|
528
|
+
progress: { percent: 10 + Math.floor(60 * (sourceIndex + 1) / sources.length), current: sourceIndex + 1, total: sources.length, label: source.title }
|
|
529
|
+
})
|
|
499
530
|
}
|
|
500
531
|
this.assertNotCancelled(task)
|
|
501
532
|
let mindMap = null
|
|
502
533
|
let knowledgeGraph = null
|
|
503
534
|
if (request.outputMode === 'mind-map' || request.outputMode === 'both') {
|
|
504
|
-
this.update(task, 'building-mind-map')
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
strict: request.strict === true,
|
|
510
|
-
selectedSessionIds: sourceSessionIds,
|
|
511
|
-
model: request.model,
|
|
512
|
-
signal: task.controller.signal
|
|
535
|
+
this.update(task, 'building-mind-map', { progress: { percent: 78, current: sources.length, total: sources.length, label: '生成思维导图' } })
|
|
536
|
+
const mindMapPrompt = outputPrompt('mind-map', summaries, request.prompt, request.strict === true)
|
|
537
|
+
const generateMindMap = async (prompt) => this.runModel({
|
|
538
|
+
kind: 'mind-map', prompt, cwd: request.cwd, strict: request.strict === true,
|
|
539
|
+
selectedSessionIds: sourceSessionIds, model: request.model, signal: task.controller.signal
|
|
513
540
|
})
|
|
514
|
-
|
|
541
|
+
try {
|
|
542
|
+
const value = await generateMindMap(mindMapPrompt)
|
|
543
|
+
mindMap = validateMindMap(value, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
544
|
+
} catch (error) {
|
|
545
|
+
this.assertNotCancelled(task)
|
|
546
|
+
logMessage(this.logger, 'warn', 'mind map first attempt invalid id=%s error=%s', logId(task.id), errorMessage(error))
|
|
547
|
+
this.update(task, 'building-mind-map', {
|
|
548
|
+
message: '首次结构无效,正在自动修复思维导图…',
|
|
549
|
+
progress: { percent: 82, current: sources.length, total: sources.length, label: '自动修复结构' }
|
|
550
|
+
})
|
|
551
|
+
const retryPrompt = `${mindMapPrompt}\n\n上一次输出未通过校验:${shortText(errorMessage(error), 500)}。请从头重新生成,不要复用上一次文本;只输出完整、严格合法的 JSON。`
|
|
552
|
+
const retryValue = await generateMindMap(retryPrompt)
|
|
553
|
+
mindMap = validateMindMap(retryValue, { selectedSessionIds: sourceSessionIds, strict: request.strict === true })
|
|
554
|
+
}
|
|
515
555
|
}
|
|
516
556
|
if (request.outputMode === 'knowledge-graph' || request.outputMode === 'both') {
|
|
517
|
-
this.update(task, 'building-knowledge-graph')
|
|
557
|
+
this.update(task, 'building-knowledge-graph', { progress: { percent: 86, current: sources.length, total: sources.length, label: '生成知识图谱' } })
|
|
518
558
|
const value = await this.runModel({
|
|
519
559
|
kind: 'knowledge-graph',
|
|
520
560
|
prompt: outputPrompt('knowledge-graph', summaries, request.prompt, request.strict === true),
|
|
@@ -528,8 +568,8 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
528
568
|
}
|
|
529
569
|
assertOutputSourceRefs({ mindMap, knowledgeGraph }, sources)
|
|
530
570
|
this.assertNotCancelled(task)
|
|
531
|
-
this.update(task, 'validating')
|
|
532
|
-
this.update(task, 'saving')
|
|
571
|
+
this.update(task, 'validating', { progress: { percent: 92, current: sources.length, total: sources.length, label: '校验结构和来源' } })
|
|
572
|
+
this.update(task, 'saving', { progress: { percent: 97, current: sources.length, total: sources.length, label: '保存知识视图' } })
|
|
533
573
|
const saved = await this.storage.saveBundle({
|
|
534
574
|
cwd: request.cwd,
|
|
535
575
|
expectedRevision: request.expectedRevision,
|
|
@@ -545,7 +585,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
545
585
|
task.revision = saved.revision
|
|
546
586
|
task.result = { revision: saved.revision, manifest: saved.manifest, mindMap, knowledgeGraph }
|
|
547
587
|
logMessage(this.logger, 'info', 'generation completed id=%s revision=%d elapsedMs=%d', logId(task.id), saved.revision, this.now() - task.createdAt)
|
|
548
|
-
this.update(task, 'completed', { revision: saved.revision, result: task.result })
|
|
588
|
+
this.update(task, 'completed', { revision: saved.revision, result: task.result, progress: { percent: 100, current: sources.length, total: sources.length, label: '生成完成' } })
|
|
549
589
|
} catch (error) {
|
|
550
590
|
if (task.controller.signal.aborted || /取消|cancel/i.test(errorMessage(error))) {
|
|
551
591
|
this.update(task, 'cancelled', { error: '' })
|
|
@@ -560,7 +600,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
560
600
|
async runModel(input) {
|
|
561
601
|
const parseModelOutput = (value, source, diagnostics = {}) => {
|
|
562
602
|
try {
|
|
563
|
-
const result = parseStructuredOutput(value)
|
|
603
|
+
const result = parseStructuredOutput(value, input.kind)
|
|
564
604
|
logMessage(this.logger, 'info', 'agent output parsed kind=%s source=%s value=%s result=%s', input.kind, source, diagnosticSummary(value), diagnosticSummary(result))
|
|
565
605
|
return result
|
|
566
606
|
} catch (error) {
|