/* global document */ const stories = [ { id: 'graph', title: '监督者 · 故事图谱', color: 'var(--accent)', cx: 270, cy: 230, r: 172, description: '从工作过程到可追溯的故事' }, { id: 'knowledge', title: '个人知识库', color: 'var(--success)', cx: 625, cy: 260, r: 146, description: '从学习记录到可复用的知识' }, { id: 'memory', title: '记忆架构', color: 'var(--warning)', cx: 451, cy: 463, r: 118, description: '从原始数据到分层理解' } ]; const nodes = [ { id: 'g0', story: 'graph', stage: 0, x: 160, y: 205, label: '观察工作过程', title: '让工作过程可以回看', copy: '监督者按时间记录工作的推进。我们希望在记录之上保留阶段性的理解,帮助用户重新进入一段未完成的工作。', source: '监督者功能讨论', quote: '过一周再回来,我需要知道之前为什么这样做,而不只是看到最后的结果。' }, { id: 'g1', story: 'graph', stage: 1, x: 261, y: 260, label: '阶段总结', title: '把连续记录整理成阶段', copy: '每个阶段保留讨论的问题、形成的判断和仍未解决的事项。阶段节点沿时间连接,组成一条能够继续展开的故事。', source: '阶段总结交互草案', quote: '以一个阶段为单位总结。保留关键决定,也保留没有走通的尝试和原始记录。' }, { id: 'g2', story: 'graph', stage: 2, x: 326, y: 215, label: '保留知识来路', title: '总结之后,仍能找到来路', copy: '故事图谱把阶段总结和原始记录连接起来。跨时间线聚合出的知识,也应保留每条贡献来源,方便核对结论、理解语境。', source: 'Story Graph 方案讨论', quote: '阶段总结与聚合知识应保留来源,支持追溯原始记录。', related: 'k2', reason: '共同要求:聚合知识仍可回到原始记录' }, { id: 'k0', story: 'knowledge', stage: 0, x: 706, y: 213, label: '学习记录', title: '记录学习中的问题', copy: '学习时产生的笔记与问答散落在不同会话中。先保留它们的上下文,再考虑如何归入个人知识库。', source: '学习知识图谱讨论', quote: '学习时的提问和笔记可以放在一起,之后能够继续补充。' }, { id: 'k1', story: 'knowledge', stage: 1, x: 635, y: 304, label: '知识关联', title: '把同一个问题的线索连起来', copy: '不同笔记可能围绕同一个问题。关联需要说明它们共有的主题,以及各自提供了什么补充。', source: '个人知识库设计记录', quote: '关联不只是两段内容相似,还要说明这条联系为什么有用。' }, { id: 'k2', story: 'knowledge', stage: 2, x: 562, y: 240, label: '来源追溯', title: '知识条目携带出处', copy: '知识库中的归纳附带原始笔记和讨论位置。用户可以从结论返回上下文,判断它是否适用于当前问题。', source: '知识条目来源设计', quote: '每条归纳都应该能够展开出处,看到原话和产生结论时的上下文。', related: 'g2', reason: '共同要求:聚合知识仍可回到原始记录' }, { id: 'm0', story: 'memory', stage: 0, x: 380, y: 475, label: '原始数据', title: '从真实记录开始', copy: '原始会话、笔记和执行结果构成记忆的基础。上层归纳需要能指向这些记录。', source: '记忆基础讨论', quote: '先有记录,再有总结;总结不能取代原始记录。' }, { id: 'm1', story: 'memory', stage: 1, x: 465, y: 504, label: 'DIKW 分层', title: '区分记录与理解的层次', copy: '尝试按数据、信息、知识、智慧组织记忆。图谱中的阶段总结与知识聚合,可能成为这些层次之间的连接。', source: 'DIKW 设计草案', quote: '数据、信息、知识、智慧应当分层呈现,让用户知道当前看到的是什么。' }, { id: 'm2', story: 'memory', stage: 2, x: 478, y: 438, label: '可解释的归纳', title: '让每层归纳都能被解释', copy: '从数据到知识的每一步,都应说明归纳依据。阶段总结可以连接过程记录,聚合知识可以连接多个阶段。', source: '记忆分层讨论', quote: '一条知识来自哪些信息、信息又来自哪些数据,需要能够顺着关系找回去。', related: 'g2', reason: '共同要求:上层归纳保留底层依据' } ]; const dates = ['09.01 · 故事起点', '09.07 · 持续探索', '09.14 · 知识汇聚']; let activeStory = null; let selected = 'g2'; let stage = 2; const graph = document.querySelector('#graph'); const detail = document.querySelector('#detail'); function showDetail() { const node = nodes.find(item => item.id === selected); const story = stories.find(item => item.id === node.story); const related = node.related && nodes.find(item => item.id === node.related); detail.innerHTML = `
${story.title} / 阶段 ${node.stage + 1}

${node.title}

${node.copy}

跨时间线关联

${related && related.stage <= stage ? `` : '

此阶段尚未建立跨线关联。

'}

来源记录 · 1 条

${node.source}模拟会话 / 2026.${dates[node.stage].slice(0, 5)} / 消息 #${node.stage + 1}

“${node.quote}”

演示来源,可展开查看模拟原文。
`; } function render() { document.querySelector('#timeline-list').innerHTML = stories.map(story => ``).join(''); document.querySelector('#graph-title').textContent = activeStory ? stories.find(item => item.id === activeStory).title : '全部故事'; document.querySelector('#node-count').textContent = ` / ${(stage + 1) * (activeStory ? 1 : 3)} 个阶段`; document.querySelector('#date-label').textContent = dates[stage]; const showRelations = document.querySelector('#relations').checked; const flat = document.querySelector('#view').value === 'flat'; document.querySelector('.sidebar-note p').innerHTML = flat ? '时间沿外圈逆时针推进。
内部平铺知识节点,
时间点直接连接对应节点。' : '一个圆,一条时间线。
圆内节点是阶段总结,
圆间虚线是知识关联。'; document.querySelector('.canvas-caption').innerHTML = ` ${flat ? '外圈时间逆时针推进 · 点击节点高亮时间连线' : '阶段总结构成故事,关联让知识相遇'}`; const plotted = nodes.map(node => flat ? { ...node, x: [280, 430, 580][stories.findIndex(story => story.id === node.story)], y: [210, 330, 450][node.stage] } : node); let markup = stories.map(story => `${story.title}${stage + 1} 个阶段 · ${story.id === 'graph' ? '持续收敛' : story.id === 'knowledge' ? '多向探索' : '逐步归纳'}`).join(''); if (showRelations && stage === 2) markup += '来源追溯归纳依据'; if (flat) { const anchors = [{ x: 430, y: 65 }, { x: 95, y: 310 }, { x: 765, y: 310 }]; markup = ` 起点 · 逆时针沿外圈阅读 ↶`; markup += plotted.filter(node => node.stage <= stage).map(node => { const anchor = anchors[node.stage]; return ``; }).join(''); markup += stories.map(story => `${story.title}`).join(''); if (showRelations && stage === 2) markup += ''; markup += anchors.map((anchor, index) => `${dates[index].slice(0, 5)}${['首次提出', '持续补充', '形成归纳'][index]}`).join(''); } markup += plotted.filter(node => node.stage <= stage).map(node => { const story = stories.find(item => item.id === node.story); return `${node.label}`; }).join(''); graph.innerHTML = markup; showDetail(); } function selectNode(id) { selected = id; render(); } document.querySelector('#timeline-list').addEventListener('click', event => { const button = event.target.closest('[data-story]'); if (!button) return; activeStory = activeStory === button.dataset.story ? null : button.dataset.story; if (activeStory) selected = nodes.find(node => node.story === activeStory && node.stage === stage).id; render(); }); graph.addEventListener('click', event => { const node = event.target.closest('[data-node]'); if (node) selectNode(node.dataset.node); }); graph.addEventListener('keydown', event => { const node = event.target.closest('[data-node]'); if (node && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); const id = node.dataset.node; selectNode(id); graph.querySelector(`[data-node="${id}"]`).focus(); } }); detail.addEventListener('click', event => { const button = event.target.closest('[data-target]'); if (!button) return; activeStory = null; selectNode(button.dataset.target); }); document.querySelector('#relations').addEventListener('change', render); document.querySelector('#view').addEventListener('change', render); document.querySelector('#reset').addEventListener('click', () => { activeStory = null; render(); }); document.querySelector('#stage').addEventListener('input', event => { stage = Number(event.target.value); const story = activeStory || nodes.find(node => node.id === selected).story; selected = nodes.find(node => node.story === story && node.stage === stage).id; render(); }); document.querySelector('#theme').addEventListener('click', () => { document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; }); document.querySelector('#insight').addEventListener('click', () => { stage = 2; activeStory = null; document.querySelector('#stage').value = '2'; document.querySelector('#relations').checked = true; render(); detail.innerHTML = `
跨时间线聚合 / 示例

知识需要保留来路

三个故事指向同一个设计要求:不论是阶段总结、知识条目,还是分层记忆,归纳结果都需要连接原始依据。

来自 3 条时间线

${['g2', 'k2', 'm2'].map(id => { const node = nodes.find(item => item.id === id); return ``; }).join('')}

聚合说明

以上为预置的聚合示例。实际功能需要结合来源生成关联依据,并允许用户核对。

`; document.querySelector('.inspector').scrollIntoView({ block: 'nearest' }); }); render();