@p-dsh-market/conversation-knowledge-map 0.2.0 → 0.2.2
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 +3 -1
- package/lib/client.js +37 -18
- package/lib/generation-orchestrator.js +8 -5
- package/lib/index.js +6 -2
- package/lib/session-source.js +3 -2
- package/lib/workspace-storage.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,9 +11,11 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
|
|
|
11
11
|
|
|
12
12
|
配置面板会列出运行时可用的 Provider / Model,并默认带入 DSH 默认模型;本次选择会绑定到确认令牌、Agent 调用和 `manifest.json`,不会因为默认模型变化而被静默替换。提交任务后配置对话框立即关闭,失败原因会显示在知识视图页的任务条中。
|
|
13
13
|
|
|
14
|
+
“提取范围”同时作用于思维导图和知识图谱:默认的“完整对话正文”读取用户问题与助手回答;“仅助手回答正文”排除用户消息,并在摘要阶段进一步忽略检查步骤、搜索/工具过程、重试过程和状态播报。两种模式都始终过滤 `reasoning` / `thinking` 块、`<think>` 文本、工具结果和流式过程块。提取范围会绑定到确认令牌并保存到 `manifest.json`,生成后的来源栏会显示本次使用的范围。
|
|
15
|
+
|
|
14
16
|
摘要阶段按对话并行处理,并发上限固定为 3;每个对话按约 5000 字、优先在完整段落边界分段,只有单段本身超长时才按句末或硬边界拆分。同一对话的分段保持顺序处理。思维导图使用合并后的会话摘要;知识图谱直接使用保留来源的分段摘要,避免长对话在二次压缩后丢失实体。知识图谱按证据分段多次生成,最多 2 批并行;单批限制在 10–18 个实体、最多 24 条关系,若达到输出 Token 上限则只降低并重试当前批次。所有成功批次由 Host 按实体类型与名称、关系起点/终点/类型确定性去重并合并来源,最终结果再根据已读取文本量和有效分段数裁剪为 30–100 个实体、最多 200 条关系。单个批次连续失败只跳过该批,不会推翻其他成功批次。结构化输出失败时会完全重置并最多重试 3 次;单个对话摘要连续失败后只跳过该对话,不阻断其他对话和最终报告。“同时生成”模式下,一个最终视图失败也不会阻断另一个视图保存。进度区域和最终知识视图都会按时间线显示读取、摘要、具体失败原因、重试、跳过、合并、生成及保存过程;知识图谱时间线还会显示批次数、模型返回数量和合并去重后的保留数量。模型可以保留 thinking,但结果提取只读取最终文本并过滤 reasoning 内容;摘要和每次图谱调用分别预留 12000 与 24000 个输出 Token。最终模型若返回未选择的 Session 引用,会过滤该引用;严格模式下无有效来源的内容项会被跳过,最终页面列出实际总结的对话、失败对话和过滤数量。
|
|
15
17
|
|
|
16
|
-
生成过程时间线支持折叠和展开:进行中的任务默认展开,已完成结果中的历史时间线默认折叠。仅生成一种视图时会保留工作区中已有的另一种视图,不再用 `null` 覆盖其存储。思维导图使用递归树形布局并绘制父子连接线;严格来源过滤后少于 2
|
|
18
|
+
生成过程时间线支持折叠和展开:进行中的任务默认展开,已完成结果中的历史时间线默认折叠。仅生成一种视图时会保留工作区中已有的另一种视图,不再用 `null` 覆盖其存储。思维导图使用递归树形布局并绘制父子连接线;严格来源过滤后少于 2 个有效节点的退化结果不会再作为成功视图保存,而会进入重试。知识图谱按关联度使用中心节点与自动扩展的多层环形布局,节点卡片内显示类型和最多两行名称,关系以带方向箭头的弱化曲线呈现,并通过描边区分推测或冲突内容;图谱置于固定且裁剪溢出内容的视口中,首次打开自动居中,可在空白区域通过不受边界限制的位移自由拖动,图谱能够部分或完全移出可视框。画布另提供 50%–200% 缩放、独立的 80%–220% 节点间距以及选中实体的一跳局部视图。
|
|
17
19
|
|
|
18
20
|
节点“继续对话”只形成一个可编辑的后续问题。确认导航后,插件使用公开的 Session 导航入口;如果当前 Runtime 没有向该槽位暴露草稿镜像,则提供“打开并复制问题”的安全降级,不自动发送消息。
|
|
19
21
|
|
package/lib/client.js
CHANGED
|
@@ -278,7 +278,7 @@ window.__ModuleLoader__.load({
|
|
|
278
278
|
if (!sources.length) return null
|
|
279
279
|
return React.createElement('section', { className: 'ckm-source-summary', 'aria-label': '本次总结来源' }, [
|
|
280
280
|
React.createElement('div', { key: 'head', className: 'ckm-source-summary-head' }, [
|
|
281
|
-
React.createElement('strong', { key: 'title' }, '已总结 ' + sources.length + ' 个对话'),
|
|
281
|
+
React.createElement('strong', { key: 'title' }, '已总结 ' + sources.length + ' 个对话' + (manifest.sourceMode === 'answer-only' ? ' · 仅助手回答正文' : ' · 完整对话正文')),
|
|
282
282
|
warnings.skippedItems || warnings.skippedRefs || failedSources.length ? React.createElement('span', { key: 'warning', className: 'ckm-source-warning' }, '失败对话 ' + failedSources.length + ' 个 · 已过滤 ' + (warnings.skippedRefs || 0) + ' 个无效引用、跳过 ' + (warnings.skippedItems || 0) + ' 个内容项') : null
|
|
283
283
|
]),
|
|
284
284
|
React.createElement('div', { key: 'list', className: 'ckm-source-list' }, sources.map(function (source) {
|
|
@@ -538,20 +538,26 @@ window.__ModuleLoader__.load({
|
|
|
538
538
|
positions.forEach(function (item) { byId[item.entity.id] = item })
|
|
539
539
|
var viewBox = ['0', '0', layout.width, layout.height].join(' ')
|
|
540
540
|
var centerSignature = [layout.width, layout.height, zoom, spacing, localOnly ? selected && selected.id : 'all'].join(':')
|
|
541
|
-
function
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
function setStagePan(stage, x, y) {
|
|
542
|
+
stage.dataset.panX = String(x)
|
|
543
|
+
stage.dataset.panY = String(y)
|
|
544
|
+
stage.style.transform = 'translate(' + x + 'px,' + y + 'px)'
|
|
545
|
+
}
|
|
546
|
+
function centerGraphStage(stage) {
|
|
547
|
+
if (!stage || stage.dataset.centered === centerSignature) return
|
|
548
|
+
stage.dataset.centered = centerSignature
|
|
544
549
|
window.requestAnimationFrame(function () {
|
|
545
|
-
var viewport =
|
|
550
|
+
var viewport = stage.parentElement
|
|
546
551
|
if (!viewport) return
|
|
547
|
-
viewport.
|
|
548
|
-
viewport.scrollTop = Math.max(0, (viewport.scrollHeight - viewport.clientHeight) / 2)
|
|
552
|
+
setStagePan(stage, (viewport.clientWidth - layout.width * zoom) / 2, (viewport.clientHeight - layout.height * zoom) / 2)
|
|
549
553
|
})
|
|
550
554
|
}
|
|
551
555
|
function beginPan(event) {
|
|
552
556
|
if (event.button !== 0 || (event.target.closest && event.target.closest('.ckm-graph-node,.ckm-graph-toolbar,button,input,select'))) return
|
|
553
557
|
var viewport = event.currentTarget
|
|
554
|
-
|
|
558
|
+
var stage = viewport.querySelector('.ckm-graph-stage')
|
|
559
|
+
if (!stage) return
|
|
560
|
+
panRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY, panX: Number(stage.dataset.panX) || 0, panY: Number(stage.dataset.panY) || 0, stage: stage }
|
|
555
561
|
event.currentTarget.setPointerCapture(event.pointerId)
|
|
556
562
|
event.currentTarget.dataset.dragging = 'true'
|
|
557
563
|
event.preventDefault()
|
|
@@ -559,9 +565,7 @@ window.__ModuleLoader__.load({
|
|
|
559
565
|
function movePan(event) {
|
|
560
566
|
var pan = panRef.current
|
|
561
567
|
if (!pan || pan.pointerId !== event.pointerId) return
|
|
562
|
-
|
|
563
|
-
viewport.scrollLeft = pan.left - (event.clientX - pan.x)
|
|
564
|
-
viewport.scrollTop = pan.top - (event.clientY - pan.y)
|
|
568
|
+
setStagePan(pan.stage, pan.panX + event.clientX - pan.x, pan.panY + event.clientY - pan.y)
|
|
565
569
|
}
|
|
566
570
|
function endPan(event) {
|
|
567
571
|
var pan = panRef.current
|
|
@@ -571,7 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
571
575
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId)
|
|
572
576
|
}
|
|
573
577
|
return React.createElement('div', { className: 'ckm-workspace ckm-graph-layout' }, [
|
|
574
|
-
React.createElement('section', { key: 'graph', className: 'ckm-graph-canvas'
|
|
578
|
+
React.createElement('section', { key: 'graph', className: 'ckm-graph-canvas' }, [
|
|
575
579
|
React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [
|
|
576
580
|
React.createElement('input', { key: 'search', value: query, placeholder: '搜索实体…', onChange: function (event) { setQuery(event.target.value) } }),
|
|
577
581
|
React.createElement('select', { key: 'confidence', value: confidence, onChange: function (event) { setConfidence(event.target.value) } }, [React.createElement('option', { key: 'all', value: 'all' }, '全部置信度'), React.createElement('option', { key: 'confirmed', value: 'confirmed' }, '已确认'), React.createElement('option', { key: 'inferred', value: 'inferred' }, '推测'), React.createElement('option', { key: 'conflicted', value: 'conflicted' }, '有冲突')]),
|
|
@@ -587,8 +591,10 @@ window.__ModuleLoader__.load({
|
|
|
587
591
|
Button({ key: 'in', title: '放大', 'aria-label': '放大知识图谱', disabled: zoom >= 2, onClick: function () { setZoom(Math.min(2, Number((zoom + 0.1).toFixed(1)))) } }, '+')
|
|
588
592
|
])
|
|
589
593
|
]),
|
|
590
|
-
React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '
|
|
591
|
-
React.createElement('
|
|
594
|
+
React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '知识图谱位于固定视口内;可按住空白区域自由拖动,移出视口的部分会被裁剪。画布缩放与节点间距可分别调整,选择实体后可查看一跳局部子图。'),
|
|
595
|
+
React.createElement('div', { key: 'viewport', className: 'ckm-graph-viewport', onPointerDown: beginPan, onPointerMove: movePan, onPointerUp: endPan, onPointerCancel: endPan }, [
|
|
596
|
+
React.createElement('div', { key: 'stage', ref: centerGraphStage, className: 'ckm-graph-stage', style: { width: layout.width * zoom + 'px', height: layout.height * zoom + 'px' } }, [
|
|
597
|
+
React.createElement('svg', { key: 'svg', className: 'ckm-graph-svg', viewBox: viewBox, width: layout.width * zoom, height: layout.height * zoom, style: { width: layout.width * zoom + 'px', height: layout.height * zoom + 'px' }, role: 'img', 'aria-label': '知识图谱' }, [
|
|
592
598
|
React.createElement('defs', { key: 'defs' }, React.createElement('marker', { id: 'ckm-arrow', markerWidth: 7, markerHeight: 7, refX: 6, refY: 3.5, orient: 'auto', markerUnits: 'strokeWidth' }, React.createElement('path', { d: 'M0,0 L7,3.5 L0,7 Z', className: 'ckm-arrow-head' }))),
|
|
593
599
|
React.createElement('g', { key: 'edges', className: 'ckm-graph-edges' }, relations.map(function (relation) {
|
|
594
600
|
var from = byId[relation.from]
|
|
@@ -606,6 +612,8 @@ window.__ModuleLoader__.load({
|
|
|
606
612
|
React.createElement('title', { key: 'title' }, item.entity.name + ' · ' + item.entity.confidence)
|
|
607
613
|
])
|
|
608
614
|
})
|
|
615
|
+
])
|
|
616
|
+
])
|
|
609
617
|
])
|
|
610
618
|
]),
|
|
611
619
|
React.createElement('aside', { key: 'detail', className: 'ckm-detail' }, [
|
|
@@ -657,6 +665,9 @@ window.__ModuleLoader__.load({
|
|
|
657
665
|
var modePair = React.useState('both')
|
|
658
666
|
var mode = modePair[0]
|
|
659
667
|
var setMode = modePair[1]
|
|
668
|
+
var sourceModePair = React.useState('conversation')
|
|
669
|
+
var sourceMode = sourceModePair[0]
|
|
670
|
+
var setSourceMode = sourceModePair[1]
|
|
660
671
|
var promptPair = React.useState('')
|
|
661
672
|
var prompt = promptPair[0]
|
|
662
673
|
var setPrompt = promptPair[1]
|
|
@@ -707,11 +718,11 @@ window.__ModuleLoader__.load({
|
|
|
707
718
|
function confirm() {
|
|
708
719
|
if (!selected.length) { setMessage('至少选择一个对话。'); return }
|
|
709
720
|
setBusy(true)
|
|
710
|
-
request('/confirm', { method: 'POST', body: { anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, model: { provider: modelProvider, model: modelId }, expectedRevision: store.state ? store.state.revision : 0 } }).then(function (body) { setConfirmation(body.confirmation); setMessage(''); setBusy(false) }).catch(function (error) { setMessage(error.message || String(error)); setBusy(false) })
|
|
721
|
+
request('/confirm', { method: 'POST', body: { anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, sourceMode: sourceMode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, model: { provider: modelProvider, model: modelId }, expectedRevision: store.state ? store.state.revision : 0 } }).then(function (body) { setConfirmation(body.confirmation); setMessage(''); setBusy(false) }).catch(function (error) { setMessage(error.message || String(error)); setBusy(false) })
|
|
711
722
|
}
|
|
712
723
|
function start() {
|
|
713
724
|
setBusy(true)
|
|
714
|
-
request('/generations', { method: 'POST', body: { token: confirmation.token, anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, model: confirmation.model, expectedRevision: confirmation.revision } }).then(function (body) {
|
|
725
|
+
request('/generations', { method: 'POST', body: { token: confirmation.token, anchorSessionId: store.sessionId, selectedSessionIds: selected, outputMode: mode, sourceMode: confirmation.sourceMode, prompt: prompt, strict: strict, includeSubagents: includeSubagents, model: confirmation.model, expectedRevision: confirmation.revision } }).then(function (body) {
|
|
715
726
|
setConfirmation(null)
|
|
716
727
|
setBusy(false)
|
|
717
728
|
setOverlayOpen(false)
|
|
@@ -722,7 +733,7 @@ window.__ModuleLoader__.load({
|
|
|
722
733
|
if (!store.context || !store.context.ready) return React.createElement('div', { className: 'ckm-modal-backdrop' }, React.createElement('div', { className: 'ckm-modal' }, [React.createElement('h3', { key: 'title' }, '知识视图'), React.createElement('p', { key: 'copy' }, '请先打开一个有明确工作路径的已有对话。'), Button({ key: 'close', className: 'ckm-primary', onClick: function () { setOverlayOpen(false) } }, '关闭')]))
|
|
723
734
|
return React.createElement('div', { className: 'ckm-modal-backdrop' }, React.createElement('div', { className: 'ckm-modal ckm-config-modal', role: 'dialog', 'aria-modal': 'true' }, confirmation ? [
|
|
724
735
|
React.createElement('h3', { key: 'title' }, '确认生成知识视图?'),
|
|
725
|
-
React.createElement('dl', { key: 'summary', className: 'ckm-confirm-summary' }, [React.createElement('dt', { key: 'cwd-label' }, '工作路径'), React.createElement('dd', { key: 'cwd' }, store.context.cwd), React.createElement('dt', { key: 'source-label' }, '来源'), React.createElement('dd', { key: 'sources' }, confirmation.selectedSessions.length + ' 个已选择对话'), React.createElement('dt', { key: 'output-label' }, '生成'), React.createElement('dd', { key: 'output' }, mode === 'both' ? '思维导图 + 知识图谱' : mode), React.createElement('dt', { key: 'model-label' }, '模型'), React.createElement('dd', { key: 'model' }, confirmation.model.provider + ' / ' + confirmation.model.model), React.createElement('dt', { key: 'strict-label' }, '约束'), React.createElement('dd', { key: 'strict' }, strict ? '严格约束已开启' : '普通约束'), React.createElement('dt', { key: 'save-label' }, '保存'), React.createElement('dd', { key: 'save' }, '.g-dsh-market-knowledge' + (confirmation.overwrite ? '(将替换已有结果)' : ''))]),
|
|
736
|
+
React.createElement('dl', { key: 'summary', className: 'ckm-confirm-summary' }, [React.createElement('dt', { key: 'cwd-label' }, '工作路径'), React.createElement('dd', { key: 'cwd' }, store.context.cwd), React.createElement('dt', { key: 'source-label' }, '来源'), React.createElement('dd', { key: 'sources' }, confirmation.selectedSessions.length + ' 个已选择对话'), React.createElement('dt', { key: 'scope-label' }, '提取范围'), React.createElement('dd', { key: 'scope' }, confirmation.sourceMode === 'answer-only' ? '仅助手回答正文' : '完整对话正文'), React.createElement('dt', { key: 'output-label' }, '生成'), React.createElement('dd', { key: 'output' }, mode === 'both' ? '思维导图 + 知识图谱' : mode), React.createElement('dt', { key: 'model-label' }, '模型'), React.createElement('dd', { key: 'model' }, confirmation.model.provider + ' / ' + confirmation.model.model), React.createElement('dt', { key: 'strict-label' }, '约束'), React.createElement('dd', { key: 'strict' }, strict ? '严格约束已开启' : '普通约束'), React.createElement('dt', { key: 'save-label' }, '保存'), React.createElement('dd', { key: 'save' }, '.g-dsh-market-knowledge' + (confirmation.overwrite ? '(将替换已有结果)' : ''))]),
|
|
726
737
|
React.createElement('p', { key: 'note', className: 'ckm-warning' }, '确认后才会读取所选对话正文、调用模型并写入工作区;不会自动发送消息。'),
|
|
727
738
|
React.createElement('div', { key: 'actions', className: 'ckm-modal-actions' }, [Button({ key: 'back', className: 'ckm-secondary', onClick: function () { setConfirmation(null) }, disabled: busy }, '返回修改'), Button({ key: 'ok', className: 'ckm-primary', onClick: start, disabled: busy }, busy ? '生成中…' : '确认并生成')])
|
|
728
739
|
] : [
|
|
@@ -745,6 +756,14 @@ window.__ModuleLoader__.load({
|
|
|
745
756
|
])
|
|
746
757
|
]),
|
|
747
758
|
React.createElement('label', { key: 'mode', className: 'ckm-field' }, ['生成内容', React.createElement('select', { key: 'select', value: mode, onChange: function (event) { setMode(event.target.value) } }, [React.createElement('option', { key: 'both', value: 'both' }, '思维导图 + 知识图谱'), React.createElement('option', { key: 'mind', value: 'mind-map' }, '仅思维导图'), React.createElement('option', { key: 'graph', value: 'knowledge-graph' }, '仅知识图谱')])]),
|
|
759
|
+
React.createElement('label', { key: 'source-mode', className: 'ckm-field' }, [
|
|
760
|
+
'提取范围(同时作用于思维导图和知识图谱)',
|
|
761
|
+
React.createElement('select', { key: 'select', value: sourceMode, onChange: function (event) { setSourceMode(event.target.value) } }, [
|
|
762
|
+
React.createElement('option', { key: 'conversation', value: 'conversation' }, '完整对话正文(用户问题 + 助手回答)'),
|
|
763
|
+
React.createElement('option', { key: 'answer-only', value: 'answer-only' }, '仅助手回答正文')
|
|
764
|
+
]),
|
|
765
|
+
React.createElement('span', { key: 'hint', className: 'ckm-panel-hint' }, '两种模式都会排除 reasoning / thinking、工具结果和流式过程块。')
|
|
766
|
+
]),
|
|
748
767
|
React.createElement('label', { key: 'model', className: 'ckm-field' }, [
|
|
749
768
|
'生成模型',
|
|
750
769
|
React.createElement('span', { key: 'hint', className: 'ckm-panel-hint' }, '默认带入 DSH 默认模型;可以为本次知识视图单独选择 Provider / Model。'),
|
|
@@ -784,7 +803,7 @@ window.__ModuleLoader__.load({
|
|
|
784
803
|
'.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)}' +
|
|
785
804
|
'@media(max-width:800px){.ckm-workspace,.ckm-graph-layout{display:flex;flex-direction:column}.ckm-detail{border-top:1px solid var(--dsw-alias-border-l2);border-left:0}.ckm-page-header{align-items:flex-start;flex-direction:column}.ckm-page-actions{width:100%;flex-wrap:wrap}}'
|
|
786
805
|
|
|
787
|
-
CSS += '.ckm-graph-
|
|
806
|
+
CSS += '.ckm-graph-layout{height:100%;min-height:0;overflow:hidden;align-self:stretch}.ckm-graph-canvas{display:flex;flex-direction:column;height:100%;max-height:100%;box-sizing:border-box;overflow:hidden}.ckm-graph-toolbar{position:relative;z-index:2;padding-bottom:4px;background:var(--dsw-alias-bg-base);cursor:default}.ckm-graph-zoom{margin-left:0}.ckm-graph-zoom:last-child{margin-left:auto}.ckm-graph-viewport{position:relative;flex:1;min-height:360px;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:radial-gradient(circle at 50% 48%,var(--dsw-alias-state-business-tertiary),var(--dsw-alias-bg-layer-1) 58%);cursor:grab;touch-action:none}.ckm-graph-viewport[data-dragging="true"]{cursor:grabbing;user-select:none}.ckm-graph-stage{position:absolute;left:0;top:0;will-change:transform}.ckm-graph-svg{display:block;width:auto;max-width:none;height:auto;min-width:620px;min-height:620px;margin:0;border:0;border-radius:0;background:transparent}@media(max-width:800px){.ckm-graph-layout{height:auto;overflow:visible}.ckm-graph-canvas{height:auto;min-height:420px}.ckm-graph-viewport{flex:none;height:60vh}}'
|
|
788
807
|
|
|
789
808
|
function apply(ctx) {
|
|
790
809
|
var sessions = ctx.sessions || (ctx.get && ctx.get('sessions'))
|
|
@@ -533,9 +533,10 @@ function outputPrompt(kind, summaries, prompt, strict, options = {}) {
|
|
|
533
533
|
].join('\n\n')
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
-
function summaryPrompt(source, chunk, strict) {
|
|
536
|
+
function summaryPrompt(source, chunk, strict, sourceMode = 'conversation') {
|
|
537
537
|
return [
|
|
538
538
|
'请把一段 DSH 对话整理成带来源的结构化摘要。不要复述完整聊天,不要添加对话中没有的事实。',
|
|
539
|
+
sourceMode === 'answer-only' ? '本次选择“仅助手回答正文”:只保留回答中的事实、结论、决策、方案、风险、问题和行动项;忽略检查步骤、搜索过程、工具调用过程、重试过程、状态播报以及 thinking/reasoning。' : '本次使用用户问题与助手回答正文共同形成摘要;thinking/reasoning、工具结果和流式过程块不在输入中。',
|
|
539
540
|
'只输出 JSON:{"summary":"完整阶段性说明","keyPoints":["..."],"sourceRefs":[{"sessionId":"...","eventSeqs":[1]}]}。',
|
|
540
541
|
'summary 控制在 1200 字以内;keyPoints 最多 16 条,每条不超过 160 字。keyPoints 要优先保留具名的人物/组织、模块、接口、数据对象、工具、外部系统、需求、约束、决策、风险、问题、行动项以及它们之间的关系,不要只写总括性主题。不要增加其他字段。字符串中的双引号必须正确转义,确保整个输出可以被 JSON.parse 直接解析。',
|
|
541
542
|
strict ? '严格模式:每个关键点都要能回指给定事件。' : '允许标记尚未确认的冲突,但不能编造事件序号。',
|
|
@@ -799,7 +800,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
799
800
|
}
|
|
800
801
|
this.tasks.set(task.id, task)
|
|
801
802
|
this.busyByWorkspace.set(key, task.id)
|
|
802
|
-
logMessage(this.logger, 'info', 'generation start id=%s cwd=%s outputMode=%s selectedSessions=%d strict=%s model=%s', logId(task.id), shortText(request.cwd, 180), request.outputMode, Array.isArray(request.selectedSessionIds) ? request.selectedSessionIds.length : 0, request.strict === true, modelLabel(request.model))
|
|
803
|
+
logMessage(this.logger, 'info', 'generation start id=%s cwd=%s outputMode=%s sourceMode=%s selectedSessions=%d strict=%s model=%s', logId(task.id), shortText(request.cwd, 180), request.outputMode, request.sourceMode || 'conversation', Array.isArray(request.selectedSessionIds) ? request.selectedSessionIds.length : 0, request.strict === true, modelLabel(request.model))
|
|
803
804
|
this.update(task, 'created', { request: { ...request, prompt: shortText(request.prompt, 500) } })
|
|
804
805
|
task.promise = this.run(task, request).finally(() => {
|
|
805
806
|
if (this.busyByWorkspace.get(key) === task.id) this.busyByWorkspace.delete(key)
|
|
@@ -821,13 +822,14 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
821
822
|
|
|
822
823
|
async run(task, request) {
|
|
823
824
|
try {
|
|
824
|
-
this.recordTimeline(task, 'start', `开始处理 ${request.selectedSessionIds.length}
|
|
825
|
+
this.recordTimeline(task, 'start', `开始处理 ${request.selectedSessionIds.length} 个已选对话(${request.sourceMode === 'answer-only' ? '仅助手回答正文' : '完整对话正文'})。`)
|
|
825
826
|
this.update(task, 'reading-sources', { progress: { percent: 5, current: 0, total: request.selectedSessionIds.length, label: '读取对话记录' } })
|
|
826
827
|
const sources = await this.sourceReader({ sessionQuery: this.sessionQuery, sessions: this.sessions }, {
|
|
827
828
|
cwd: request.cwd,
|
|
828
829
|
sessionIds: request.selectedSessionIds,
|
|
829
830
|
fallbackSessionId: request.anchorSessionId,
|
|
830
|
-
includeSubagents: request.includeSubagents === true
|
|
831
|
+
includeSubagents: request.includeSubagents === true,
|
|
832
|
+
sourceMode: request.sourceMode || 'conversation'
|
|
831
833
|
})
|
|
832
834
|
const loadedSessionIds = [...new Set(sources.map((source) => source.sessionId))]
|
|
833
835
|
this.assertNotCancelled(task)
|
|
@@ -848,7 +850,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
848
850
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
|
|
849
851
|
const chunk = chunks[chunkIndex]
|
|
850
852
|
this.assertNotCancelled(task)
|
|
851
|
-
const basePrompt = summaryPrompt(source, chunk, request.strict === true)
|
|
853
|
+
const basePrompt = summaryPrompt(source, chunk, request.strict === true, request.sourceMode || 'conversation')
|
|
852
854
|
const generateSummary = async (summaryInput) => this.runModel({
|
|
853
855
|
kind: 'summary', prompt: summaryInput, cwd: request.cwd, strict: request.strict === true,
|
|
854
856
|
selectedSessionIds: loadedSessionIds, model: request.model, signal: task.controller.signal
|
|
@@ -1024,6 +1026,7 @@ export class KnowledgeGenerationOrchestrator {
|
|
|
1024
1026
|
generationTimeline: task.timeline,
|
|
1025
1027
|
prompt: request.prompt,
|
|
1026
1028
|
strict: request.strict,
|
|
1029
|
+
sourceMode: request.sourceMode || 'conversation',
|
|
1027
1030
|
outputMode: request.outputMode,
|
|
1028
1031
|
model: request.model,
|
|
1029
1032
|
mindMap,
|
package/lib/index.js
CHANGED
|
@@ -66,10 +66,12 @@ function normalizeGenerationInput(body = {}) {
|
|
|
66
66
|
const prompt = String(body.prompt || '').trim()
|
|
67
67
|
if (prompt.length > 4000) throw new Error('额外 Prompt 不能超过 4000 个字符。')
|
|
68
68
|
const expectedRevision = Number(body.expectedRevision)
|
|
69
|
+
const sourceMode = body.sourceMode === 'answer-only' ? 'answer-only' : 'conversation'
|
|
69
70
|
return {
|
|
70
71
|
anchorSessionId: safeId(body.anchorSessionId, '锚点 Session ID'),
|
|
71
72
|
selectedSessionIds,
|
|
72
73
|
outputMode: normalizeOutputMode(body.outputMode),
|
|
74
|
+
sourceMode,
|
|
73
75
|
prompt,
|
|
74
76
|
strict: body.strict !== false,
|
|
75
77
|
includeSubagents: body.includeSubagents === true,
|
|
@@ -84,6 +86,7 @@ function canonicalPayload(value) {
|
|
|
84
86
|
cwd: normalizeWorkspacePath(value.cwd),
|
|
85
87
|
selectedSessionIds: [...value.selectedSessionIds].map(String).sort(),
|
|
86
88
|
outputMode: value.outputMode,
|
|
89
|
+
sourceMode: value.sourceMode,
|
|
87
90
|
prompt: value.prompt,
|
|
88
91
|
strict: value.strict === true,
|
|
89
92
|
includeSubagents: value.includeSubagents === true,
|
|
@@ -184,7 +187,7 @@ export function createHost(options = {}) {
|
|
|
184
187
|
const payload = { ...input, model, anchorSessionId: context.sessionId, cwd: context.cwd, expectedRevision: state.revision }
|
|
185
188
|
const token = randomUUID()
|
|
186
189
|
confirmations.set(token, { payload, expiresAt: Date.now() + CONFIRMATION_TTL, used: false })
|
|
187
|
-
logMessage(logger, 'info', 'generation confirmed cwd=%s anchorSession=%s selectedSessions=%d outputMode=%s strict=%s model=%s expectedRevision=%d', shortText(context.cwd, 180), shortText(context.sessionId, 96), input.selectedSessionIds.length, input.outputMode, input.strict, `${model.provider}/${model.model}`, state.revision)
|
|
190
|
+
logMessage(logger, 'info', 'generation confirmed cwd=%s anchorSession=%s selectedSessions=%d outputMode=%s sourceMode=%s strict=%s model=%s expectedRevision=%d', shortText(context.cwd, 180), shortText(context.sessionId, 96), input.selectedSessionIds.length, input.outputMode, input.sourceMode, input.strict, `${model.provider}/${model.model}`, state.revision)
|
|
188
191
|
return {
|
|
189
192
|
token,
|
|
190
193
|
expiresAt: Date.now() + CONFIRMATION_TTL,
|
|
@@ -192,6 +195,7 @@ export function createHost(options = {}) {
|
|
|
192
195
|
context,
|
|
193
196
|
selectedSessions: available.filter((item) => input.selectedSessionIds.includes(item.id)),
|
|
194
197
|
outputMode: input.outputMode,
|
|
198
|
+
sourceMode: input.sourceMode,
|
|
195
199
|
strict: input.strict,
|
|
196
200
|
model,
|
|
197
201
|
promptSummary: shortText(input.prompt, 300),
|
|
@@ -202,7 +206,7 @@ export function createHost(options = {}) {
|
|
|
202
206
|
function consumeConfirmation(token, body) {
|
|
203
207
|
const value = confirmations.get(String(token || ''))
|
|
204
208
|
if (!value || value.used || value.expiresAt < Date.now()) throw new Error('生成确认已过期,请返回配置重新确认。')
|
|
205
|
-
const supplied = normalizeGenerationInput({ ...body, anchorSessionId: body.anchorSessionId || value.payload.anchorSessionId, selectedSessionIds: body.selectedSessionIds || value.payload.selectedSessionIds, outputMode: body.outputMode || value.payload.outputMode, prompt: body.prompt ?? value.payload.prompt, strict: body.strict ?? value.payload.strict, includeSubagents: body.includeSubagents ?? value.payload.includeSubagents, model: body.model ?? value.payload.model, expectedRevision: body.expectedRevision ?? value.payload.expectedRevision })
|
|
209
|
+
const supplied = normalizeGenerationInput({ ...body, anchorSessionId: body.anchorSessionId || value.payload.anchorSessionId, selectedSessionIds: body.selectedSessionIds || value.payload.selectedSessionIds, outputMode: body.outputMode || value.payload.outputMode, sourceMode: body.sourceMode ?? value.payload.sourceMode, prompt: body.prompt ?? value.payload.prompt, strict: body.strict ?? value.payload.strict, includeSubagents: body.includeSubagents ?? value.payload.includeSubagents, model: body.model ?? value.payload.model, expectedRevision: body.expectedRevision ?? value.payload.expectedRevision })
|
|
206
210
|
const expected = canonicalPayload(value.payload)
|
|
207
211
|
const actual = canonicalPayload({ ...supplied, cwd: value.payload.cwd })
|
|
208
212
|
if (expected !== actual) throw new Error('确认内容已变化,请返回配置重新确认。')
|
package/lib/session-source.js
CHANGED
|
@@ -116,7 +116,7 @@ export function surfaceEventView(event, { fullSession = false } = {}) {
|
|
|
116
116
|
const role = eventRole(String(event?.type || ''))
|
|
117
117
|
const data = event?.data?.message || event?.data || {}
|
|
118
118
|
if (fullSession && role === 'user' && data?.source?.kind !== 'user') return null
|
|
119
|
-
const text = shortText(eventMessageText(event), 12000)
|
|
119
|
+
const text = shortText(eventMessageText(event).replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, ''), 12000)
|
|
120
120
|
if (!role || !text) return null
|
|
121
121
|
return {
|
|
122
122
|
seq: Number.isInteger(event?.seq) ? event.seq : 0,
|
|
@@ -127,7 +127,7 @@ export function surfaceEventView(event, { fullSession = false } = {}) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, sessionIds, fallbackSessionId = '', includeSubagents = false }) {
|
|
130
|
+
export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, sessionIds, fallbackSessionId = '', includeSubagents = false, sourceMode = 'conversation' }) {
|
|
131
131
|
const selected = [...new Set((sessionIds || []).map((id) => String(id || '').trim()).filter(Boolean))]
|
|
132
132
|
if (selected.length === 0) throw new Error('至少选择一个对话。')
|
|
133
133
|
const normalizedCwd = normalizeWorkspacePath(cwd)
|
|
@@ -172,6 +172,7 @@ export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, se
|
|
|
172
172
|
throw new Error(`读取对话“${id}”完整记录失败:${errorMessage(error)}`)
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
if (sourceMode === 'answer-only') events = events.filter((event) => event.role === 'assistant')
|
|
175
176
|
if (surfaceError && events.length === 0) throw new Error(`读取对话“${id}”失败:${errorMessage(surfaceError)}`)
|
|
176
177
|
return {
|
|
177
178
|
sessionId: id,
|
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, sourceSessions, failedSources, sourceWarnings, generationTimeline, prompt, strict, outputMode, model, mindMap, knowledgeGraph }) {
|
|
154
|
+
async saveBundle({ cwd, expectedRevision = 0, generationId, sourceSessionIds, sourceSessions, failedSources, sourceWarnings, generationTimeline, prompt, strict, sourceMode, 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()
|
|
@@ -187,6 +187,7 @@ export class WorkspaceStorage {
|
|
|
187
187
|
})) : [],
|
|
188
188
|
promptSummary: shortText(prompt, 500),
|
|
189
189
|
strict: strict === true,
|
|
190
|
+
sourceMode: sourceMode === 'answer-only' ? 'answer-only' : 'conversation',
|
|
190
191
|
outputMode: String(outputMode || 'both'),
|
|
191
192
|
model: model ? { provider: String(model.provider || ''), model: String(model.model || '') } : null,
|
|
192
193
|
generatedAt: this.now()
|