@p-dsh-market/conversation-knowledge-map 0.1.12 → 0.1.13

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 CHANGED
@@ -11,7 +11,9 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
11
11
 
12
12
  配置面板会列出运行时可用的 Provider / Model,并默认带入 DSH 默认模型;本次选择会绑定到确认令牌、Agent 调用和 `manifest.json`,不会因为默认模型变化而被静默替换。提交任务后配置对话框立即关闭,失败原因会显示在知识视图页的任务条中。
13
13
 
14
- 摘要阶段按对话并行处理,并发上限固定为 3;同一对话的超长分段保持顺序处理,随后先合并为一份对话摘要,再统一生成思维导图或知识图谱。结构化输出失败时会完全重置并最多重试 3 次;单个对话连续失败后只跳过该对话,不阻断其他对话和最终报告。“同时生成”模式下,一个最终视图失败也不会阻断另一个视图保存。进度区域和最终知识视图都会按时间线显示读取、摘要、重试、跳过、合并、生成及保存过程。结构化生成关闭推理输出以减少等待和 Token 消耗;知识图谱最多生成 20 个实体、30 条关系。最终模型若返回未选择的 Session 引用,会过滤该引用;严格模式下无有效来源的内容项会被跳过,最终页面列出实际总结的对话、失败对话和过滤数量。
14
+ 摘要阶段按对话并行处理,并发上限固定为 3;每个对话按约 5000 字、优先在完整段落边界分段,只有单段本身超长时才按句末或硬边界拆分。同一对话的分段保持顺序处理,随后先合并为一份对话摘要,再统一生成思维导图或知识图谱。结构化输出失败时会完全重置并最多重试 3 次;单个对话连续失败后只跳过该对话,不阻断其他对话和最终报告。“同时生成”模式下,一个最终视图失败也不会阻断另一个视图保存。进度区域和最终知识视图都会按时间线显示读取、摘要、具体失败原因、重试、跳过、合并、生成及保存过程。模型可以保留 thinking,但结果提取只读取最终文本并过滤 reasoning 内容;摘要和最终视图分别预留 12000 与 24000 个输出 Token。知识图谱最多生成 20 个实体、30 条关系。最终模型若返回未选择的 Session 引用,会过滤该引用;严格模式下无有效来源的内容项会被跳过,最终页面列出实际总结的对话、失败对话和过滤数量。
15
+
16
+ 生成过程时间线支持折叠和展开:进行中的任务默认展开,已完成结果中的历史时间线默认折叠。思维导图使用递归树形布局并绘制父子连接线;知识图谱提供 50%–200% 缩放和一键恢复 100%,缩放不会影响实体筛选、点击和详情查看。
15
17
 
16
18
  节点“继续对话”只形成一个可编辑的后续问题。确认导航后,插件使用公开的 Session 导航入口;如果当前 Runtime 没有向该槽位暴露草稿镜像,则提供“打开并复制问题”的安全降级,不自动发送消息。
17
19
 
package/lib/client.js CHANGED
@@ -261,7 +261,7 @@ window.__ModuleLoader__.load({
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
263
  state && state.manifest ? React.createElement(SourceSummary, { key: 'sources', manifest: state.manifest }) : null,
264
- state && state.manifest && state.manifest.generationTimeline ? React.createElement(GenerationTimeline, { key: 'timeline', items: state.manifest.generationTimeline }) : null,
264
+ state && state.manifest && state.manifest.generationTimeline ? React.createElement(GenerationTimeline, { key: 'timeline', items: state.manifest.generationTimeline, defaultCollapsed: true }) : null,
265
265
  !state || (!state.mindMap && !state.knowledgeGraph) ? React.createElement(EmptyState, { key: 'empty', title: '尚未生成知识视图', copy: '从标题栏打开“知识视图”,选择同工作路径下的对话并确认生成。' }) : (mode === 'mind-map'
266
266
  ? React.createElement(MindMapPanel, { key: 'mind', store: store, selectedNodeId: selectedNodeId, setSelectedNodeId: setSelectedNodeId })
267
267
  : React.createElement(KnowledgeGraphPanel, { key: 'graph', store: store }))
@@ -292,10 +292,16 @@ window.__ModuleLoader__.load({
292
292
 
293
293
  function GenerationTimeline(props) {
294
294
  var items = (props.items || []).slice(-80)
295
+ var collapsedPair = React.useState(props.defaultCollapsed === true)
296
+ var collapsed = collapsedPair[0]
297
+ var setCollapsed = collapsedPair[1]
295
298
  if (!items.length) return null
296
299
  return React.createElement('section', { className: 'ckm-timeline', 'aria-label': '生成过程时间线' }, [
297
- React.createElement('strong', { key: 'title', className: 'ckm-timeline-title' }, '生成过程时间线'),
298
- React.createElement('ol', { key: 'items' }, items.map(function (item, index) {
300
+ React.createElement('div', { key: 'head', className: 'ckm-timeline-head' }, [
301
+ React.createElement('strong', { key: 'title', className: 'ckm-timeline-title' }, '生成过程时间线 · ' + items.length + ' 条'),
302
+ Button({ key: 'toggle', className: 'ckm-timeline-toggle', 'aria-expanded': !collapsed, onClick: function () { setCollapsed(!collapsed) } }, collapsed ? '展开' : '折叠')
303
+ ]),
304
+ collapsed ? null : React.createElement('ol', { key: 'items' }, items.map(function (item, index) {
299
305
  var time = ''
300
306
  try { time = new Date(Number(item.at) || 0).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) } catch (_) {}
301
307
  return React.createElement('li', { key: String(item.id || index), 'data-type': item.type || 'info' }, [
@@ -324,24 +330,39 @@ window.__ModuleLoader__.load({
324
330
  ])
325
331
  }
326
332
 
327
- function mindNodes(map) {
333
+ function mindTree(map) {
328
334
  var nodes = (map && map.nodes) || []
329
335
  var byParent = Object.create(null)
330
336
  nodes.forEach(function (node) { var key = node.parentId || ''; (byParent[key] || (byParent[key] = [])).push(node) })
331
- var result = []
332
- function visit(node, depth) {
333
- result.push({ node: node, depth: depth })
334
- ;(byParent[node.id] || []).forEach(function (child) { visit(child, depth + 1) })
337
+ function branch(node, ancestors) {
338
+ var seen = Object.assign({}, ancestors || {})
339
+ if (seen[node.id]) return { node: node, children: [] }
340
+ seen[node.id] = true
341
+ return { node: node, children: (byParent[node.id] || []).map(function (child) { return branch(child, seen) }) }
335
342
  }
336
343
  var root = nodes.filter(function (node) { return node.id === map.rootId })[0]
337
- if (root) visit(root, 0)
338
- return result
344
+ return root ? branch(root, {}) : null
345
+ }
346
+
347
+ function MindTreeBranch(props) {
348
+ var branch = props.branch
349
+ var node = branch.node
350
+ return React.createElement('li', { className: 'ckm-tree-branch' }, [
351
+ Button({ key: 'node', className: 'ckm-tree-node', 'data-active': node.id === props.selectedNodeId, onClick: function () { props.setSelectedNodeId(node.id) } }, [
352
+ React.createElement('span', { key: 'type', className: 'ckm-node-type' }, node.type),
353
+ React.createElement('strong', { key: 'title' }, node.title),
354
+ React.createElement('span', { key: 'narrative' }, node.narrative)
355
+ ]),
356
+ branch.children.length ? React.createElement('ul', { key: 'children', className: 'ckm-tree-children' }, branch.children.map(function (child) {
357
+ return React.createElement(MindTreeBranch, { key: child.node.id, branch: child, selectedNodeId: props.selectedNodeId, setSelectedNodeId: props.setSelectedNodeId })
358
+ })) : null
359
+ ])
339
360
  }
340
361
 
341
362
  function MindMapPanel(props) {
342
363
  var map = props.store.state && props.store.state.mindMap
343
364
  var selected = map && map.nodes.filter(function (node) { return node.id === props.selectedNodeId })[0]
344
- var rows = map ? mindNodes(map) : []
365
+ var tree = map ? mindTree(map) : null
345
366
  var followPair = React.useState(null)
346
367
  var followUp = followPair[0]
347
368
  var setFollowUp = followPair[1]
@@ -359,13 +380,7 @@ window.__ModuleLoader__.load({
359
380
  return React.createElement('div', { className: 'ckm-workspace' }, [
360
381
  React.createElement('section', { key: 'canvas', className: 'ckm-mind-canvas' }, [
361
382
  React.createElement('div', { key: 'hint', className: 'ckm-panel-hint' }, '节点代表阶段性认知;点击后可形成问题,但不会直接修改脑图。'),
362
- React.createElement('div', { key: 'tree', className: 'ckm-tree' }, rows.map(function (row) {
363
- return Button({ key: row.node.id, className: 'ckm-tree-node', 'data-active': row.node.id === props.selectedNodeId, style: { marginLeft: (row.depth * 24) + 'px' }, onClick: function () { props.setSelectedNodeId(row.node.id) } }, [
364
- React.createElement('span', { key: 'type', className: 'ckm-node-type' }, row.node.type),
365
- React.createElement('strong', { key: 'title' }, row.node.title),
366
- React.createElement('span', { key: 'narrative' }, row.node.narrative)
367
- ])
368
- }))
383
+ tree ? React.createElement('ul', { key: 'tree', className: 'ckm-tree ckm-tree-root' }, React.createElement(MindTreeBranch, { branch: tree, selectedNodeId: props.selectedNodeId, setSelectedNodeId: props.setSelectedNodeId })) : null
369
384
  ]),
370
385
  React.createElement('aside', { key: 'detail', className: 'ckm-detail' }, [
371
386
  selected ? [
@@ -420,6 +435,9 @@ window.__ModuleLoader__.load({
420
435
  var selectedPair = React.useState(null)
421
436
  var selected = selectedPair[0]
422
437
  var setSelected = selectedPair[1]
438
+ var zoomPair = React.useState(1)
439
+ var zoom = zoomPair[0]
440
+ var setZoom = zoomPair[1]
423
441
  if (!graph) return React.createElement(EmptyState, { title: '尚未生成知识图谱', copy: '从标题栏打开知识视图并选择生成知识图谱。' })
424
442
  var entities = graph.entities.filter(function (entity) {
425
443
  return (confidence === 'all' || entity.confidence === confidence) && (!query.trim() || (entity.name + ' ' + entity.summary).toLowerCase().indexOf(query.trim().toLowerCase()) >= 0)
@@ -433,11 +451,22 @@ window.__ModuleLoader__.load({
433
451
  var byId = Object.create(null)
434
452
  positions.forEach(function (item) { byId[item.entity.id] = item })
435
453
  var relations = graph.relations.filter(function (relation) { return visible[relation.from] && visible[relation.to] })
454
+ var viewWidth = 800 / zoom
455
+ var viewHeight = 460 / zoom
456
+ var viewBox = [(800 - viewWidth) / 2, (460 - viewHeight) / 2, viewWidth, viewHeight].join(' ')
436
457
  return React.createElement('div', { className: 'ckm-workspace ckm-graph-layout' }, [
437
458
  React.createElement('section', { key: 'graph', className: 'ckm-graph-canvas' }, [
438
- React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [React.createElement('input', { key: 'search', value: query, placeholder: '搜索实体…', onChange: function (event) { setQuery(event.target.value) } }), 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' }, '有冲突')])]),
439
- React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '知识图谱是静态结果,只能通过菜单栏完整重新生成;画布不支持编辑或节点发散。'),
440
- React.createElement('svg', { key: 'svg', className: 'ckm-graph-svg', viewBox: '0 0 800 460', role: 'img', 'aria-label': '知识图谱' }, [
459
+ React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [
460
+ React.createElement('input', { key: 'search', value: query, placeholder: '搜索实体…', onChange: function (event) { setQuery(event.target.value) } }),
461
+ 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' }, '有冲突')]),
462
+ React.createElement('div', { key: 'zoom', className: 'ckm-graph-zoom', 'aria-label': '知识图谱缩放控制' }, [
463
+ Button({ key: 'out', title: '缩小', 'aria-label': '缩小知识图谱', disabled: zoom <= 0.5, onClick: function () { setZoom(Math.max(0.5, Number((zoom - 0.1).toFixed(1)))) } }, '−'),
464
+ Button({ key: 'reset', title: '重置缩放', className: 'ckm-zoom-value', onClick: function () { setZoom(1) } }, Math.round(zoom * 100) + '%'),
465
+ Button({ key: 'in', title: '放大', 'aria-label': '放大知识图谱', disabled: zoom >= 2, onClick: function () { setZoom(Math.min(2, Number((zoom + 0.1).toFixed(1)))) } }, '+')
466
+ ])
467
+ ]),
468
+ React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '知识图谱是静态结果,可缩放查看;只能通过菜单栏完整重新生成,不支持直接编辑或节点发散。'),
469
+ React.createElement('svg', { key: 'svg', className: 'ckm-graph-svg', viewBox: viewBox, role: 'img', 'aria-label': '知识图谱' }, [
441
470
  relations.map(function (relation) { var from = byId[relation.from]; var to = byId[relation.to]; return React.createElement('line', { key: relation.id, x1: from.x, y1: from.y, x2: to.x, y2: to.y, className: 'ckm-edge' }) }),
442
471
  positions.map(function (item) { return React.createElement('g', { key: item.entity.id, className: 'ckm-graph-node', 'data-active': selected && selected.id === item.entity.id, onClick: function () { setSelected(item.entity) } }, [React.createElement('circle', { key: 'circle', cx: item.x, cy: item.y, r: 26 }), React.createElement('text', { key: 'text', x: item.x, y: item.y + 44, textAnchor: 'middle' }, item.entity.name.slice(0, 18))]) })
443
472
  ])
@@ -612,9 +641,9 @@ window.__ModuleLoader__.load({
612
641
  '.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}' +
613
642
  '.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}' +
614
643
  '.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}' +
615
- '.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-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,.ckm-source-failed{color:#d69a42}.ckm-source-failed{font-size:11px}.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}.ckm-timeline{display:grid;gap:9px;padding:12px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px}.ckm-generation-strip .ckm-timeline{max-height:220px;overflow:auto;padding:6px 0 0;border:0}.ckm-timeline-title{color:var(--dsw-alias-label-primary)}.ckm-timeline ol{display:grid;gap:0;margin:0;padding:0;list-style:none}.ckm-timeline li{position:relative;display:grid;grid-template-columns:78px 1fr;gap:10px;padding:5px 0 5px 14px;color:var(--dsw-alias-label-secondary)}.ckm-timeline li:before{content:"";position:absolute;left:2px;top:11px;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-business-primary)}.ckm-timeline li:not(:last-child):after{content:"";position:absolute;left:4px;top:17px;bottom:-5px;width:1px;background:var(--dsw-alias-border-l2)}.ckm-timeline li[data-type="retry"]:before,.ckm-timeline li[data-type="skipped"]:before,.ckm-timeline li[data-type="view-failed"]:before{background:#d69a42}.ckm-timeline li[data-type="failed"]:before{background:#ff7070}.ckm-timeline time{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}' +
616
- '.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}' +
617
- '.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}' +
644
+ '.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-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,.ckm-source-failed{color:#d69a42}.ckm-source-failed{font-size:11px}.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}.ckm-timeline{display:grid;gap:9px;padding:12px 20px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:12px}.ckm-generation-strip .ckm-timeline{max-height:220px;overflow:auto;padding:6px 0 0;border:0}.ckm-timeline-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.ckm-timeline-title{color:var(--dsw-alias-label-primary)}.ckm-timeline-toggle{border:0;padding:3px 7px;border-radius:6px;background:transparent;color:var(--dsw-alias-state-business-primary);cursor:pointer;font:inherit;font-size:11px}.ckm-timeline-toggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-timeline ol{display:grid;gap:0;margin:0;padding:0;list-style:none}.ckm-timeline li{position:relative;display:grid;grid-template-columns:78px 1fr;gap:10px;padding:5px 0 5px 14px;color:var(--dsw-alias-label-secondary)}.ckm-timeline li:before{content:"";position:absolute;left:2px;top:11px;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-business-primary)}.ckm-timeline li:not(:last-child):after{content:"";position:absolute;left:4px;top:17px;bottom:-5px;width:1px;background:var(--dsw-alias-border-l2)}.ckm-timeline li[data-type="retry"]:before,.ckm-timeline li[data-type="skipped"]:before,.ckm-timeline li[data-type="view-failed"]:before{background:#d69a42}.ckm-timeline li[data-type="failed"]:before{background:#ff7070}.ckm-timeline time{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}' +
645
+ '.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{max-width:900px;margin:0 auto;padding:0;list-style:none}.ckm-tree-branch{position:relative;padding:6px 0 6px 30px;list-style:none}.ckm-tree-root>.ckm-tree-branch{padding-left:0}.ckm-tree-children{position:relative;margin:8px 0 0 20px;padding:0 0 0 22px;list-style:none}.ckm-tree-children>.ckm-tree-branch:before{content:"";position:absolute;left:-22px;top:-8px;bottom:50%;border-left:1px solid var(--dsw-alias-border-l2)}.ckm-tree-children>.ckm-tree-branch:not(:last-child):before{bottom:-8px}.ckm-tree-children>.ckm-tree-branch:after{content:"";position:absolute;left:-22px;top:50%;width:22px;border-top:1px solid var(--dsw-alias-border-l2)}.ckm-tree-node{display:flex;flex-direction:column;align-items:flex-start;gap:5px;width:min(720px,100%);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}' +
646
+ '.ckm-graph-layout{grid-template-columns:minmax(0,1fr) 330px}.ckm-graph-toolbar{display:flex;align-items:center;flex-wrap:wrap;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;min-width:180px}.ckm-graph-zoom{display:flex;align-items:center;margin-left:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden}.ckm-graph-zoom button{min-width:32px;border:0;border-right:1px solid var(--dsw-alias-border-l2);padding:7px 9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit}.ckm-graph-zoom button:last-child{border-right:0}.ckm-graph-zoom button:hover{background:var(--dsw-alias-interactive-bg-hover)}.ckm-graph-zoom button:disabled{opacity:.4;cursor:not-allowed}.ckm-graph-zoom .ckm-zoom-value{min-width:58px;color:var(--dsw-alias-state-business-primary);font-size:11px}.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}' +
618
647
  '.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)}' +
619
648
  '@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}}'
620
649
 
@@ -6,8 +6,8 @@ import { clone, diagnosticSummary, errorMessage, logMessage, makeUserMessage, sh
6
6
  import { chunkSourceText, readSelectedSurfaces } from './session-source.js'
7
7
 
8
8
  const MAX_SUMMARY_CHARS = 2400
9
- const SUMMARY_MAX_TOKENS = 8000
10
- const VIEW_MAX_TOKENS = 12000
9
+ const SUMMARY_MAX_TOKENS = 12000
10
+ const VIEW_MAX_TOKENS = 24000
11
11
  const SUMMARY_CONCURRENCY = 3
12
12
  const MAX_MODEL_RETRIES = 3
13
13
 
@@ -659,7 +659,7 @@ export class KnowledgeGenerationOrchestrator {
659
659
  finalError = error
660
660
  logMessage(this.logger, 'warn', 'summary attempt invalid id=%s session=%s chunk=%d attempt=%d error=%s', logId(task.id), logId(source.sessionId), chunkIndex + 1, attempt + 1, errorMessage(error))
661
661
  if (attempt < MAX_MODEL_RETRIES) {
662
- this.recordTimeline(task, 'retry', `摘要失败,重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}:${source.title}`, { sessionId: source.sessionId })
662
+ this.recordTimeline(task, 'retry', `摘要失败(${shortText(errorMessage(finalError), 160)}),重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}:${source.title}`, { sessionId: source.sessionId })
663
663
  this.update(task, 'summarizing', {
664
664
  message: `对话 ${sourceIndex + 1}/${sources.length} 摘要无效,正在重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}…`,
665
665
  progress: { percent: 10 + Math.floor(60 * completedSources / sources.length), current: completedSources, total: sources.length, label: `重试 ${attempt + 1}/${MAX_MODEL_RETRIES}:${source.title}` }
@@ -724,7 +724,7 @@ export class KnowledgeGenerationOrchestrator {
724
724
  mindMapError = error
725
725
  logMessage(this.logger, 'warn', 'mind map attempt invalid id=%s attempt=%d error=%s', logId(task.id), attempt + 1, errorMessage(error))
726
726
  if (attempt < MAX_MODEL_RETRIES) {
727
- this.recordTimeline(task, 'retry', `思维导图结构无效,重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
727
+ this.recordTimeline(task, 'retry', `思维导图生成失败(${shortText(errorMessage(mindMapError), 160)}),重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
728
728
  this.update(task, 'building-mind-map', {
729
729
  message: `思维导图结构无效,正在重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}…`,
730
730
  progress: { percent: 82, current: sources.length, total: sources.length, label: `思维导图重试 ${attempt + 1}/${MAX_MODEL_RETRIES}` }
@@ -761,7 +761,7 @@ export class KnowledgeGenerationOrchestrator {
761
761
  graphError = error
762
762
  logMessage(this.logger, 'warn', 'knowledge graph attempt invalid id=%s attempt=%d error=%s', logId(task.id), attempt + 1, errorMessage(error))
763
763
  if (attempt < MAX_MODEL_RETRIES) {
764
- this.recordTimeline(task, 'retry', `知识图谱结构无效,重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
764
+ this.recordTimeline(task, 'retry', `知识图谱生成失败(${shortText(errorMessage(graphError), 160)}),重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}。`)
765
765
  this.update(task, 'building-knowledge-graph', {
766
766
  message: `知识图谱结构无效,正在重置重试 ${attempt + 1}/${MAX_MODEL_RETRIES}…`,
767
767
  progress: { percent: 88, current: sources.length, total: sources.length, label: `知识图谱重试 ${attempt + 1}/${MAX_MODEL_RETRIES}` }
@@ -914,7 +914,7 @@ export class KnowledgeGenerationOrchestrator {
914
914
  handle = await this.agents.create({
915
915
  sessionId,
916
916
  meta: { cwd: input.cwd, origin: 'subagent' },
917
- agentOptions: { provider, model, reasoningEffort: 'off', maxTokens: input.kind === 'summary' ? SUMMARY_MAX_TOKENS : VIEW_MAX_TOKENS },
917
+ agentOptions: { provider, model, maxTokens: input.kind === 'summary' ? SUMMARY_MAX_TOKENS : VIEW_MAX_TOKENS },
918
918
  signal: input.signal,
919
919
  setup: async (agentCtx) => {
920
920
  agentCtx?.systemPrompt?.section?.({
@@ -206,19 +206,86 @@ export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, se
206
206
  return sources
207
207
  }
208
208
 
209
- export function chunkSourceText(source, maxChars = 16000) {
209
+ export const DEFAULT_SOURCE_CHUNK_CHARS = 5000
210
+
211
+ function paragraphRanges(text) {
212
+ const ranges = []
213
+ const separator = /\r?\n[\t ]*\r?\n/g
214
+ let start = 0
215
+ let match
216
+ while ((match = separator.exec(text)) !== null) {
217
+ const end = match.index + match[0].length
218
+ ranges.push({ start, end })
219
+ start = end
220
+ }
221
+ if (start < text.length) ranges.push({ start, end: text.length })
222
+ return ranges
223
+ }
224
+
225
+ function splitOversizedRange(text, range, maxChars) {
226
+ const ranges = []
227
+ let start = range.start
228
+ while (start < range.end) {
229
+ let end = Math.min(range.end, start + maxChars)
230
+ if (end < range.end) {
231
+ const candidate = text.slice(start, end)
232
+ const minimumBoundary = Math.floor(maxChars * 0.6)
233
+ let boundary = Math.max(candidate.lastIndexOf('\n'), candidate.lastIndexOf('。'), candidate.lastIndexOf('!'), candidate.lastIndexOf('?'), candidate.lastIndexOf(';'))
234
+ if (boundary + 1 >= minimumBoundary) end = start + boundary + 1
235
+ }
236
+ ranges.push({ start, end })
237
+ start = end
238
+ }
239
+ return ranges
240
+ }
241
+
242
+ function sourceEventRanges(source, text) {
243
+ const ranges = []
244
+ let cursor = 0
245
+ for (const event of source.events || []) {
246
+ const eventText = String(event?.text || '')
247
+ if (!eventText) continue
248
+ let start = text.indexOf(eventText, cursor)
249
+ if (start < 0) start = text.indexOf(eventText)
250
+ if (start < 0) continue
251
+ const end = start + eventText.length
252
+ ranges.push({ start, end, seq: event.seq })
253
+ cursor = end
254
+ }
255
+ return ranges
256
+ }
257
+
258
+ export function chunkSourceText(source, maxChars = DEFAULT_SOURCE_CHUNK_CHARS) {
210
259
  const text = String(source?.text || '')
211
260
  if (!text) return [{ text: '', sourceRefs: [] }]
261
+ const limit = Math.max(1, Number(maxChars) || DEFAULT_SOURCE_CHUNK_CHARS)
262
+ const units = paragraphRanges(text).flatMap((range) => (
263
+ range.end - range.start > limit ? splitOversizedRange(text, range, limit) : [range]
264
+ ))
265
+ const ranges = []
266
+ let current = null
267
+ for (const unit of units) {
268
+ if (!current) {
269
+ current = { ...unit }
270
+ continue
271
+ }
272
+ if (unit.end - current.start <= limit) {
273
+ current.end = unit.end
274
+ } else {
275
+ ranges.push(current)
276
+ current = { ...unit }
277
+ }
278
+ }
279
+ if (current) ranges.push(current)
280
+
281
+ const eventRanges = sourceEventRanges(source, text)
212
282
  const chunks = []
213
- let start = 0
214
- while (start < text.length) {
215
- const end = Math.min(text.length, start + maxChars)
283
+ for (const { start, end } of ranges) {
216
284
  const chunkText = text.slice(start, end)
217
- const refs = (source.events || [])
218
- .filter((event) => event.text && text.indexOf(event.text, start) >= start && text.indexOf(event.text, start) < end)
285
+ const refs = eventRanges
286
+ .filter((event) => event.start < end && event.end > start)
219
287
  .map((event) => ({ sessionId: source.sessionId, eventSeqs: [event.seq] }))
220
288
  chunks.push({ text: chunkText, sourceRefs: refs })
221
- start = end
222
289
  }
223
290
  return chunks
224
291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-dsh-market/conversation-knowledge-map",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "DSH 多对话思维导图与静态知识图谱",
5
5
  "keywords": [
6
6
  "dsh",