@p-dsh-market/conversation-knowledge-map 0.1.12 → 0.1.14
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 +127 -31
- package/lib/generation-orchestrator.js +6 -6
- package/lib/session-source.js +74 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,9 @@ DSH 的“知识视图”插件,把同一工作路径下用户明确选择的
|
|
|
11
11
|
|
|
12
12
|
配置面板会列出运行时可用的 Provider / Model,并默认带入 DSH 默认模型;本次选择会绑定到确认令牌、Agent 调用和 `manifest.json`,不会因为默认模型变化而被静默替换。提交任务后配置对话框立即关闭,失败原因会显示在知识视图页的任务条中。
|
|
13
13
|
|
|
14
|
-
摘要阶段按对话并行处理,并发上限固定为 3
|
|
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('
|
|
298
|
-
|
|
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
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
|
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('
|
|
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 ? [
|
|
@@ -409,6 +424,61 @@ window.__ModuleLoader__.load({
|
|
|
409
424
|
]))
|
|
410
425
|
}
|
|
411
426
|
|
|
427
|
+
function graphLayout(entities, relations) {
|
|
428
|
+
var degree = Object.create(null)
|
|
429
|
+
entities.forEach(function (entity) { degree[entity.id] = 0 })
|
|
430
|
+
relations.forEach(function (relation) {
|
|
431
|
+
if (degree[relation.from] !== undefined) degree[relation.from] += 1
|
|
432
|
+
if (degree[relation.to] !== undefined) degree[relation.to] += 1
|
|
433
|
+
})
|
|
434
|
+
var ordered = entities.slice().sort(function (left, right) {
|
|
435
|
+
return (degree[right.id] || 0) - (degree[left.id] || 0) || String(left.name || '').localeCompare(String(right.name || ''))
|
|
436
|
+
})
|
|
437
|
+
if (!ordered.length) return []
|
|
438
|
+
var positions = [{ entity: ordered[0], x: 450, y: 310, width: 164, height: 68, central: true }]
|
|
439
|
+
var remaining = ordered.slice(1)
|
|
440
|
+
var inner = remaining.slice(0, Math.min(6, remaining.length))
|
|
441
|
+
var outer = remaining.slice(inner.length)
|
|
442
|
+
function placeRing(items, radiusX, radiusY, phase) {
|
|
443
|
+
items.forEach(function (entity, index) {
|
|
444
|
+
var angle = phase + index / Math.max(1, items.length) * Math.PI * 2
|
|
445
|
+
positions.push({ entity: entity, x: 450 + Math.cos(angle) * radiusX, y: 310 + Math.sin(angle) * radiusY, width: 138, height: 60, central: false })
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
placeRing(inner, 205, 145, -Math.PI / 2)
|
|
449
|
+
placeRing(outer, 350, 255, -Math.PI / 2 + Math.PI / Math.max(2, outer.length))
|
|
450
|
+
return positions
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function graphLabelLines(value) {
|
|
454
|
+
var text = String(value || '').replace(/\s+/g, ' ').trim()
|
|
455
|
+
if (text.length <= 16) return [text]
|
|
456
|
+
var probe = text.slice(0, 17)
|
|
457
|
+
var split = Math.max(probe.lastIndexOf(' '), probe.lastIndexOf('_'), probe.lastIndexOf('/'), probe.lastIndexOf('-'))
|
|
458
|
+
if (split < 7) split = 15
|
|
459
|
+
var first = text.slice(0, split).trim()
|
|
460
|
+
var rest = text.slice(split).replace(/^[_/\-\s]+/, '').trim()
|
|
461
|
+
var second = rest.length > 16 ? rest.slice(0, 15) + '…' : rest
|
|
462
|
+
return [first, second].filter(Boolean)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function graphEdgePath(from, to, relationId) {
|
|
466
|
+
var dx = to.x - from.x
|
|
467
|
+
var dy = to.y - from.y
|
|
468
|
+
var distance = Math.max(1, Math.sqrt(dx * dx + dy * dy))
|
|
469
|
+
var fromScale = Math.min((from.width / 2) / Math.max(1, Math.abs(dx)), (from.height / 2) / Math.max(1, Math.abs(dy)))
|
|
470
|
+
var toScale = Math.min((to.width / 2) / Math.max(1, Math.abs(dx)), (to.height / 2) / Math.max(1, Math.abs(dy)))
|
|
471
|
+
var startX = from.x + dx * fromScale
|
|
472
|
+
var startY = from.y + dy * fromScale
|
|
473
|
+
var endX = to.x - dx * toScale
|
|
474
|
+
var endY = to.y - dy * toScale
|
|
475
|
+
var hash = String(relationId || '').split('').reduce(function (sum, char) { return sum + char.charCodeAt(0) }, 0)
|
|
476
|
+
var bend = (hash % 3 - 1) * 12
|
|
477
|
+
var controlX = (startX + endX) / 2 - dy / distance * bend
|
|
478
|
+
var controlY = (startY + endY) / 2 + dx / distance * bend
|
|
479
|
+
return 'M ' + startX + ' ' + startY + ' Q ' + controlX + ' ' + controlY + ' ' + endX + ' ' + endY
|
|
480
|
+
}
|
|
481
|
+
|
|
412
482
|
function KnowledgeGraphPanel(props) {
|
|
413
483
|
var graph = props.store.state && props.store.state.knowledgeGraph
|
|
414
484
|
var queryPair = React.useState('')
|
|
@@ -420,26 +490,52 @@ window.__ModuleLoader__.load({
|
|
|
420
490
|
var selectedPair = React.useState(null)
|
|
421
491
|
var selected = selectedPair[0]
|
|
422
492
|
var setSelected = selectedPair[1]
|
|
493
|
+
var zoomPair = React.useState(1)
|
|
494
|
+
var zoom = zoomPair[0]
|
|
495
|
+
var setZoom = zoomPair[1]
|
|
423
496
|
if (!graph) return React.createElement(EmptyState, { title: '尚未生成知识图谱', copy: '从标题栏打开知识视图并选择生成知识图谱。' })
|
|
424
497
|
var entities = graph.entities.filter(function (entity) {
|
|
425
498
|
return (confidence === 'all' || entity.confidence === confidence) && (!query.trim() || (entity.name + ' ' + entity.summary).toLowerCase().indexOf(query.trim().toLowerCase()) >= 0)
|
|
426
499
|
})
|
|
427
500
|
var visible = Object.create(null)
|
|
428
501
|
entities.forEach(function (entity) { visible[entity.id] = true })
|
|
429
|
-
var
|
|
430
|
-
|
|
431
|
-
return { entity: entity, x: 400 + Math.cos(angle) * 260, y: 230 + Math.sin(angle) * 160 }
|
|
432
|
-
})
|
|
502
|
+
var relations = graph.relations.filter(function (relation) { return visible[relation.from] && visible[relation.to] })
|
|
503
|
+
var positions = graphLayout(entities, relations)
|
|
433
504
|
var byId = Object.create(null)
|
|
434
505
|
positions.forEach(function (item) { byId[item.entity.id] = item })
|
|
435
|
-
var
|
|
506
|
+
var viewWidth = 900 / zoom
|
|
507
|
+
var viewHeight = 620 / zoom
|
|
508
|
+
var viewBox = [(900 - viewWidth) / 2, (620 - viewHeight) / 2, viewWidth, viewHeight].join(' ')
|
|
436
509
|
return React.createElement('div', { className: 'ckm-workspace ckm-graph-layout' }, [
|
|
437
510
|
React.createElement('section', { key: 'graph', className: 'ckm-graph-canvas' }, [
|
|
438
|
-
React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
511
|
+
React.createElement('div', { key: 'toolbar', className: 'ckm-graph-toolbar' }, [
|
|
512
|
+
React.createElement('input', { key: 'search', value: query, placeholder: '搜索实体…', onChange: function (event) { setQuery(event.target.value) } }),
|
|
513
|
+
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' }, '有冲突')]),
|
|
514
|
+
React.createElement('div', { key: 'zoom', className: 'ckm-graph-zoom', 'aria-label': '知识图谱缩放控制' }, [
|
|
515
|
+
Button({ key: 'out', title: '缩小', 'aria-label': '缩小知识图谱', disabled: zoom <= 0.5, onClick: function () { setZoom(Math.max(0.5, Number((zoom - 0.1).toFixed(1)))) } }, '−'),
|
|
516
|
+
Button({ key: 'reset', title: '重置缩放', className: 'ckm-zoom-value', onClick: function () { setZoom(1) } }, Math.round(zoom * 100) + '%'),
|
|
517
|
+
Button({ key: 'in', title: '放大', 'aria-label': '放大知识图谱', disabled: zoom >= 2, onClick: function () { setZoom(Math.min(2, Number((zoom + 0.1).toFixed(1)))) } }, '+')
|
|
518
|
+
])
|
|
519
|
+
]),
|
|
520
|
+
React.createElement('p', { key: 'static', className: 'ckm-panel-hint' }, '知识图谱是静态结果,可缩放查看;只能通过菜单栏完整重新生成,不支持直接编辑或节点发散。'),
|
|
521
|
+
React.createElement('svg', { key: 'svg', className: 'ckm-graph-svg', viewBox: viewBox, role: 'img', 'aria-label': '知识图谱' }, [
|
|
522
|
+
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' }))),
|
|
523
|
+
React.createElement('g', { key: 'edges', className: 'ckm-graph-edges' }, relations.map(function (relation) {
|
|
524
|
+
var from = byId[relation.from]
|
|
525
|
+
var to = byId[relation.to]
|
|
526
|
+
return React.createElement('path', { key: relation.id, d: graphEdgePath(from, to, relation.id), className: 'ckm-edge', markerEnd: 'url(#ckm-arrow)' }, React.createElement('title', null, relation.type))
|
|
527
|
+
})),
|
|
528
|
+
positions.map(function (item) {
|
|
529
|
+
var lines = graphLabelLines(item.entity.name)
|
|
530
|
+
return React.createElement('g', { key: item.entity.id, className: 'ckm-graph-node', 'data-active': selected && selected.id === item.entity.id, 'data-confidence': item.entity.confidence, 'data-central': item.central, onClick: function () { setSelected(item.entity) } }, [
|
|
531
|
+
React.createElement('rect', { key: 'shape', x: item.x - item.width / 2, y: item.y - item.height / 2, width: item.width, height: item.height, rx: 13 }),
|
|
532
|
+
React.createElement('text', { key: 'type', className: 'ckm-graph-node-type', x: item.x, y: item.y - 12, textAnchor: 'middle' }, String(item.entity.type || 'entity').slice(0, 18)),
|
|
533
|
+
React.createElement('text', { key: 'name', className: 'ckm-graph-node-name', x: item.x, y: lines.length === 1 ? item.y + 10 : item.y + 4, textAnchor: 'middle' }, lines.map(function (line, index) {
|
|
534
|
+
return React.createElement('tspan', { key: index, x: item.x, dy: index === 0 ? 0 : 16 }, line)
|
|
535
|
+
})),
|
|
536
|
+
React.createElement('title', { key: 'title' }, item.entity.name + ' · ' + item.entity.confidence)
|
|
537
|
+
])
|
|
538
|
+
})
|
|
443
539
|
])
|
|
444
540
|
]),
|
|
445
541
|
React.createElement('aside', { key: 'detail', className: 'ckm-detail' }, [
|
|
@@ -612,9 +708,9 @@ window.__ModuleLoader__.load({
|
|
|
612
708
|
'.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
709
|
'.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
710
|
'.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;
|
|
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:
|
|
711
|
+
'.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}' +
|
|
712
|
+
'.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;padding:20px;background:var(--dsw-alias-bg-base)}.ckm-mind-canvas{overflow:auto}.ckm-graph-canvas{overflow-x:hidden;overflow-y:auto}.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}' +
|
|
713
|
+
'.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%;max-width:100%;height:auto;min-height:500px;margin-top:8px;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%);box-sizing:border-box}.ckm-graph-edges{opacity:.62}.ckm-edge{fill:none;stroke:var(--dsw-alias-border-l2);stroke-width:1.4}.ckm-arrow-head{fill:var(--dsw-alias-label-tertiary)}.ckm-graph-node{cursor:pointer}.ckm-graph-node rect{fill:var(--dsw-alias-bg-layer-1);stroke:var(--dsw-alias-state-business-primary);stroke-width:1.5;filter:drop-shadow(0 4px 8px rgba(0,0,0,.14))}.ckm-graph-node[data-central=true] rect{fill:var(--dsw-alias-button-info-fill);stroke:var(--dsw-alias-label-primary);stroke-width:2}.ckm-graph-node[data-confidence="inferred"] rect{stroke:#d69a42}.ckm-graph-node[data-confidence="conflicted"] rect{stroke:#e06c75}.ckm-graph-node[data-active=true] rect{fill:var(--dsw-alias-state-business-tertiary);stroke:var(--dsw-alias-label-primary);stroke-width:2.5}.ckm-graph-node text{pointer-events:none}.ckm-graph-node-type{fill:var(--dsw-alias-label-tertiary);font-size:9px;letter-spacing:.04em;text-transform:uppercase}.ckm-graph-node-name{fill:var(--dsw-alias-label-primary);font-size:11px;font-weight:600}.ckm-graph-node[data-central=true] .ckm-graph-node-type,.ckm-graph-node[data-central=true] .ckm-graph-node-name{fill:var(--dsw-alias-label-primary-foreground)}' +
|
|
618
714
|
'.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
715
|
'@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
716
|
|
|
@@ -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 =
|
|
10
|
-
const VIEW_MAX_TOKENS =
|
|
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',
|
|
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',
|
|
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',
|
|
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,
|
|
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?.({
|
package/lib/session-source.js
CHANGED
|
@@ -206,19 +206,86 @@ export async function readSelectedSurfaces({ sessionQuery, sessions }, { cwd, se
|
|
|
206
206
|
return sources
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
export
|
|
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
|
-
|
|
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 =
|
|
218
|
-
.filter((event) => event.
|
|
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
|
}
|