mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
feat: add story timeline layer
This commit is contained in:
@@ -18,6 +18,11 @@ import {
|
||||
normalizeMemoryScope,
|
||||
} from "../graph/memory-scope.js";
|
||||
import { ensureEventTitle, getNodeDisplayName } from "../graph/node-labels.js";
|
||||
import {
|
||||
deriveStoryTimeSpanFromNodes,
|
||||
describeNodeStoryTime,
|
||||
normalizeStoryTime,
|
||||
} from "../graph/story-timeline.js";
|
||||
import {
|
||||
buildTaskExecutionDebugContext,
|
||||
buildTaskLlmPayload,
|
||||
@@ -296,6 +301,12 @@ async function compressLevel({
|
||||
|
||||
compressedNode.level = level + 1;
|
||||
compressedNode.childIds = batch.map((n) => n.id);
|
||||
compressedNode.storyTime = normalizeStoryTime();
|
||||
compressedNode.storyTimeSpan = deriveStoryTimeSpanFromNodes(
|
||||
graph,
|
||||
batch,
|
||||
"derived",
|
||||
);
|
||||
|
||||
const embeddingText =
|
||||
normalizeCompressionFieldValue(
|
||||
@@ -471,11 +482,12 @@ async function summarizeBatch(
|
||||
) {
|
||||
const nodeDescriptions = nodes
|
||||
.map((n, i) => {
|
||||
const storyTimeLabel = describeNodeStoryTime(n);
|
||||
const fieldsStr = Object.entries(n.fields)
|
||||
.filter(([_, v]) => v)
|
||||
.map(([k, v]) => `${k}: ${v}`)
|
||||
.join("\n ");
|
||||
return `节点 ${i + 1} [楼层 ${n.seq}]:\n ${fieldsStr}`;
|
||||
return `节点 ${i + 1} [楼层 ${n.seq}]${storyTimeLabel ? ` [剧情时间 ${storyTimeLabel}]` : ""}:\n ${fieldsStr}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
@@ -508,6 +520,8 @@ async function summarizeBatch(
|
||||
"- 保留关键信息:因果关系、不可逆结果、未解决伏笔",
|
||||
"- 去除重复和低信息密度内容",
|
||||
"- 压缩后文本应精炼,目标 150 字左右",
|
||||
"- 必须保持剧情时间顺序,不要把不同阶段的内容写反",
|
||||
"- 不要把未来计划写成已经发生的客观事实",
|
||||
].join("\n"),
|
||||
compressRegexInput,
|
||||
"system",
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
canMergeScopedMemories,
|
||||
describeMemoryScope,
|
||||
} from "../graph/memory-scope.js";
|
||||
import {
|
||||
describeNodeStoryTime,
|
||||
isStoryTimeCompatible,
|
||||
} from "../graph/story-timeline.js";
|
||||
import {
|
||||
buildTaskExecutionDebugContext,
|
||||
buildTaskLlmPayload,
|
||||
@@ -110,7 +114,9 @@ const CONSOLIDATION_SYSTEM_PROMPT = `你是一个记忆整合分析器。当新
|
||||
- 例如:揭露卧底身份 → 修正该角色之前事件中的动机描述
|
||||
- 例如:发现地点的隐藏特性 → 更新地点节点的描述
|
||||
- 不要对无关记忆强行建立联系
|
||||
- neighbor_updates 中每条必须有实际意义的修改`;
|
||||
- neighbor_updates 中每条必须有实际意义的修改
|
||||
- 必须保持剧情时间一致;不同时间段的事件默认不要 merge
|
||||
- 同名事件若剧情时间不同,除非明确是同一事件的补充,否则应 keep`;
|
||||
|
||||
function normalizeLatestOnlyIdentityValue(value) {
|
||||
return String(value ?? "")
|
||||
@@ -119,6 +125,13 @@ function normalizeLatestOnlyIdentityValue(value) {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function canMergeTemporalScopedMemories(leftNode, rightNode) {
|
||||
if (!canMergeScopedMemories(leftNode, rightNode)) {
|
||||
return false;
|
||||
}
|
||||
return isStoryTimeCompatible(leftNode, rightNode).compatible;
|
||||
}
|
||||
|
||||
export async function analyzeAutoConsolidationGate({
|
||||
graph,
|
||||
newNodeIds,
|
||||
@@ -159,7 +172,7 @@ export async function analyzeAutoConsolidationGate({
|
||||
const typeDef = schemaByType.get(String(node.type || ""));
|
||||
const scopedCandidates = activeNodes.filter(
|
||||
(candidate) =>
|
||||
candidate?.id !== node.id && canMergeScopedMemories(node, candidate),
|
||||
candidate?.id !== node.id && canMergeTemporalScopedMemories(node, candidate),
|
||||
);
|
||||
|
||||
if (typeDef?.latestOnly) {
|
||||
@@ -364,7 +377,7 @@ export async function consolidateMemories({
|
||||
const candidates = candidatePool.filter((c) => {
|
||||
if (c.nodeId === entry.id) return false;
|
||||
const candidateNode = getNode(graph, c.nodeId);
|
||||
return canMergeScopedMemories(entry.node, candidateNode);
|
||||
return canMergeTemporalScopedMemories(entry.node, candidateNode);
|
||||
});
|
||||
|
||||
if (queryVectors?.[i] && candidates.length > 0) {
|
||||
@@ -406,7 +419,7 @@ export async function consolidateMemories({
|
||||
embeddingConfig,
|
||||
neighborCount,
|
||||
activeNodes.filter(
|
||||
(n) => n.id !== entry.id && canMergeScopedMemories(entry.node, n),
|
||||
(n) => n.id !== entry.id && canMergeTemporalScopedMemories(entry.node, n),
|
||||
),
|
||||
signal,
|
||||
);
|
||||
@@ -437,6 +450,7 @@ export async function consolidateMemories({
|
||||
.map(([k, v]) => `${k}: ${v}`)
|
||||
.join(", ");
|
||||
const newNodeScope = buildScopeBadgeText(entry.node.scope);
|
||||
const newNodeStoryTime = describeNodeStoryTime(entry.node);
|
||||
|
||||
// 构建近邻描述
|
||||
let neighborText;
|
||||
@@ -450,7 +464,7 @@ export async function consolidateMemories({
|
||||
const fieldsStr = Object.entries(node.fields)
|
||||
.map(([k, v]) => `${k}: ${v}`)
|
||||
.join(", ");
|
||||
return ` - [${node.id}] 类型=${node.type}, 作用域=${describeMemoryScope(node.scope)}, ${fieldsStr} (相似度=${n.score.toFixed(3)})`;
|
||||
return ` - [${node.id}] 类型=${node.type}, 作用域=${describeMemoryScope(node.scope)}${describeNodeStoryTime(node) ? `, 剧情时间=${describeNodeStoryTime(node)}` : ""}, ${fieldsStr} (相似度=${n.score.toFixed(3)})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
@@ -466,7 +480,7 @@ export async function consolidateMemories({
|
||||
userPromptSections.push(
|
||||
[
|
||||
`### 新记忆 #${i + 1}`,
|
||||
`[${entry.id}] 类型=${entry.node.type}, 作用域=${newNodeScope}, ${newNodeFieldsStr}`,
|
||||
`[${entry.id}] 类型=${entry.node.type}, 作用域=${newNodeScope}${newNodeStoryTime ? `, 剧情时间=${newNodeStoryTime}` : ""}, ${newNodeFieldsStr}`,
|
||||
"近邻记忆:",
|
||||
neighborText,
|
||||
hint,
|
||||
@@ -600,7 +614,7 @@ function processOneResult(graph, entry, result, stats) {
|
||||
if (
|
||||
targetNode &&
|
||||
!targetNode.archived &&
|
||||
canMergeScopedMemories(newNode, targetNode)
|
||||
canMergeTemporalScopedMemories(newNode, targetNode)
|
||||
) {
|
||||
debugLog(`[ST-BME] 记忆整合: merge ${newId} → ${targetId}`);
|
||||
|
||||
@@ -635,6 +649,14 @@ function processOneResult(graph, entry, result, stats) {
|
||||
Math.min(targetRange[0], newRange[0]),
|
||||
Math.max(targetRange[1], newRange[1]),
|
||||
];
|
||||
if (!String(targetNode?.storyTime?.segmentId || targetNode?.storyTime?.label || "").trim()) {
|
||||
targetNode.storyTime = { ...(newNode.storyTime || targetNode.storyTime || {}) };
|
||||
}
|
||||
if (!String(targetNode?.storyTimeSpan?.startSegmentId || targetNode?.storyTimeSpan?.startLabel || "").trim()) {
|
||||
targetNode.storyTimeSpan = {
|
||||
...(newNode.storyTimeSpan || targetNode.storyTimeSpan || {}),
|
||||
};
|
||||
}
|
||||
|
||||
targetNode.embedding = null;
|
||||
newNode.archived = true;
|
||||
@@ -683,7 +705,7 @@ function processOneResult(graph, entry, result, stats) {
|
||||
if (
|
||||
!oldNode ||
|
||||
oldNode.archived ||
|
||||
!canMergeScopedMemories(newNode, oldNode)
|
||||
!canMergeTemporalScopedMemories(newNode, oldNode)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ import {
|
||||
applyRegionUpdates,
|
||||
resolveKnowledgeOwner,
|
||||
} from "../graph/knowledge-state.js";
|
||||
import {
|
||||
applyBatchStoryTime,
|
||||
createSpanFromStoryTime,
|
||||
deriveStoryTimeSpanFromNodes,
|
||||
describeNodeStoryTime,
|
||||
normalizeStoryTime,
|
||||
upsertTimelineSegment,
|
||||
} from "../graph/story-timeline.js";
|
||||
import {
|
||||
buildTaskExecutionDebugContext,
|
||||
buildTaskLlmPayload,
|
||||
@@ -120,6 +128,7 @@ const EXTRACTION_OPERATION_META_KEYS = new Set([
|
||||
"importance",
|
||||
"clusters",
|
||||
"scope",
|
||||
"storyTime",
|
||||
"seq",
|
||||
"temporalStrength",
|
||||
"temporal_strength",
|
||||
@@ -302,6 +311,14 @@ function normalizeExtractionOperation(rawOp, schema) {
|
||||
delete normalized.scope;
|
||||
}
|
||||
|
||||
if (isPlainObject(rawOp?.storyTime)) {
|
||||
normalized.storyTime = normalizeStoryTime(rawOp.storyTime, {
|
||||
source: "extract",
|
||||
});
|
||||
} else if (action === "create" || action === "update") {
|
||||
delete normalized.storyTime;
|
||||
}
|
||||
|
||||
if (action === "create" || action === "update") {
|
||||
const fields = collectNormalizedOperationFields(rawOp, typeDef);
|
||||
if (Object.keys(fields).length > 0) {
|
||||
@@ -370,12 +387,19 @@ function normalizeExtractionResultPayload(result, schema) {
|
||||
: [],
|
||||
}
|
||||
: null;
|
||||
const normalizedBatchStoryTime = isPlainObject(result?.batchStoryTime)
|
||||
? {
|
||||
...normalizeStoryTime(result.batchStoryTime, { source: "extract" }),
|
||||
advancesActiveTimeline: result.batchStoryTime?.advancesActiveTimeline === true,
|
||||
}
|
||||
: null;
|
||||
|
||||
if (Array.isArray(result) || !isPlainObject(result)) {
|
||||
return {
|
||||
operations: normalizedOperations,
|
||||
cognitionUpdates: normalizedCognitionUpdates,
|
||||
regionUpdates: normalizedRegionUpdates,
|
||||
batchStoryTime: normalizedBatchStoryTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -384,6 +408,7 @@ function normalizeExtractionResultPayload(result, schema) {
|
||||
operations: normalizedOperations,
|
||||
cognitionUpdates: normalizedCognitionUpdates,
|
||||
regionUpdates: normalizedRegionUpdates,
|
||||
batchStoryTime: normalizedBatchStoryTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -546,6 +571,75 @@ function normalizeCognitionUpdatesWithOwnerContext(
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function supportsPointStoryTime(type = "") {
|
||||
return ["event", "pov_memory"].includes(String(type || ""));
|
||||
}
|
||||
|
||||
function supportsSpanStoryTime(type = "") {
|
||||
return ["thread", "synopsis", "reflection"].includes(String(type || ""));
|
||||
}
|
||||
|
||||
function resolveOperationStoryTime(
|
||||
graph,
|
||||
op = {},
|
||||
batchStoryTime = null,
|
||||
{ source = "extract" } = {},
|
||||
) {
|
||||
const explicitStoryTime = normalizeStoryTime(op?.storyTime, { source });
|
||||
const fallbackStoryTime = normalizeStoryTime(batchStoryTime, { source });
|
||||
const candidate =
|
||||
explicitStoryTime.segmentId || explicitStoryTime.label
|
||||
? explicitStoryTime
|
||||
: fallbackStoryTime.segmentId || fallbackStoryTime.label
|
||||
? fallbackStoryTime
|
||||
: null;
|
||||
if (!candidate) {
|
||||
return {
|
||||
storyTime: normalizeStoryTime(),
|
||||
storyTimeSpan: createSpanFromStoryTime(null, source),
|
||||
timelineAdvanceApplied: false,
|
||||
};
|
||||
}
|
||||
|
||||
const activeReferenceSegmentId = String(
|
||||
graph?.historyState?.activeStorySegmentId ||
|
||||
graph?.historyState?.lastExtractedStorySegmentId ||
|
||||
"",
|
||||
).trim();
|
||||
const upserted = upsertTimelineSegment(graph, candidate, {
|
||||
referenceSegmentId: activeReferenceSegmentId,
|
||||
source,
|
||||
});
|
||||
return {
|
||||
storyTime: upserted.storyTime,
|
||||
storyTimeSpan: createSpanFromStoryTime(upserted.storyTime, source),
|
||||
timelineAdvanceApplied: false,
|
||||
};
|
||||
}
|
||||
|
||||
function applyOperationStoryTimeToNode(
|
||||
graph,
|
||||
node,
|
||||
op = {},
|
||||
batchStoryTime = null,
|
||||
{ source = "extract" } = {},
|
||||
) {
|
||||
if (!node || typeof node !== "object") return;
|
||||
const resolved = resolveOperationStoryTime(graph, op, batchStoryTime, { source });
|
||||
if (supportsPointStoryTime(node.type)) {
|
||||
node.storyTime = resolved.storyTime;
|
||||
node.storyTimeSpan = createSpanFromStoryTime(null, source);
|
||||
return;
|
||||
}
|
||||
if (supportsSpanStoryTime(node.type)) {
|
||||
node.storyTime = normalizeStoryTime();
|
||||
node.storyTimeSpan = resolved.storyTimeSpan;
|
||||
return;
|
||||
}
|
||||
node.storyTime = normalizeStoryTime();
|
||||
node.storyTimeSpan = createSpanFromStoryTime(null, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对未处理的对话楼层执行记忆提取
|
||||
*
|
||||
@@ -774,6 +868,7 @@ export async function extractMemories({
|
||||
const updatedNodeIds = [];
|
||||
const refMap = new Map();
|
||||
const operationErrors = [];
|
||||
const normalizedBatchStoryTime = normalizedResult?.batchStoryTime || null;
|
||||
|
||||
for (const op of normalizedResult.operations) {
|
||||
try {
|
||||
@@ -789,6 +884,7 @@ export async function extractMemories({
|
||||
scopeRuntime,
|
||||
extractionOwnerContext,
|
||||
ownershipWarnings,
|
||||
normalizedBatchStoryTime,
|
||||
);
|
||||
if (createdId) newNodeIds.push(createdId);
|
||||
break;
|
||||
@@ -803,6 +899,7 @@ export async function extractMemories({
|
||||
scopeRuntime,
|
||||
extractionOwnerContext,
|
||||
ownershipWarnings,
|
||||
normalizedBatchStoryTime,
|
||||
);
|
||||
if (updatedNodeId) updatedNodeIds.push(updatedNodeId);
|
||||
}
|
||||
@@ -867,6 +964,11 @@ export async function extractMemories({
|
||||
changedNodeIds,
|
||||
source: "extract",
|
||||
});
|
||||
const batchStoryTimeResult = applyBatchStoryTime(
|
||||
graph,
|
||||
normalizedBatchStoryTime,
|
||||
"extract",
|
||||
);
|
||||
updateRuntimeScopeState(graph, newNodeIds, scopeRuntime, extractionOwnerContext);
|
||||
|
||||
debugLog(
|
||||
@@ -879,6 +981,8 @@ export async function extractMemories({
|
||||
...stats,
|
||||
newNodeIds,
|
||||
ownerWarnings: ownershipWarnings,
|
||||
batchStoryTime: normalizedBatchStoryTime,
|
||||
batchStoryTimeResult,
|
||||
processedRange: [effectiveStartSeq, effectiveEndSeq],
|
||||
};
|
||||
}
|
||||
@@ -896,6 +1000,7 @@ function handleCreate(
|
||||
scopeRuntime = {},
|
||||
ownerContext = {},
|
||||
ownershipWarnings = [],
|
||||
batchStoryTime = null,
|
||||
) {
|
||||
const normalizedFields =
|
||||
op.type === "event" ? ensureEventTitle(op.fields || {}) : op.fields || {};
|
||||
@@ -933,6 +1038,7 @@ function handleCreate(
|
||||
if (existing) {
|
||||
// 转为更新操作
|
||||
updateNode(graph, existing.id, { fields: op.fields, seq, scope: nodeScope });
|
||||
applyOperationStoryTimeToNode(graph, existing, op, batchStoryTime);
|
||||
stats.updatedNodes++;
|
||||
|
||||
if (op.ref) refMap.set(op.ref, existing.id);
|
||||
@@ -954,6 +1060,7 @@ function handleCreate(
|
||||
clusters: op.clusters || [],
|
||||
scope: nodeScope,
|
||||
});
|
||||
applyOperationStoryTimeToNode(graph, node, op, batchStoryTime);
|
||||
|
||||
addNode(graph, node);
|
||||
stats.newNodes++;
|
||||
@@ -982,6 +1089,7 @@ function handleUpdate(
|
||||
scopeRuntime = {},
|
||||
ownerContext = {},
|
||||
ownershipWarnings = [],
|
||||
batchStoryTime = null,
|
||||
) {
|
||||
if (!op.nodeId) {
|
||||
console.warn("[ST-BME] update 操作缺少 nodeId");
|
||||
@@ -1029,6 +1137,7 @@ function handleUpdate(
|
||||
stats.updatedNodes++;
|
||||
const node = getNode(graph, op.nodeId);
|
||||
if (node) {
|
||||
applyOperationStoryTimeToNode(graph, node, op, batchStoryTime);
|
||||
node.embedding = null;
|
||||
node.seq = Math.max(node.seq || 0, updateSeq);
|
||||
node.seqRange = [
|
||||
@@ -1381,14 +1490,16 @@ function buildDefaultExtractPrompt(schema) {
|
||||
"输出格式为严格 JSON:",
|
||||
"{",
|
||||
' "thought": "你对本段对话的分析(事件/角色变化/新信息/谁如何理解)",',
|
||||
' "batchStoryTime": {"label": "第二天清晨", "tense": "ongoing", "relation": "after", "anchorLabel": "昨夜冲突之后", "confidence": "high", "advancesActiveTimeline": true},',
|
||||
' "operations": [',
|
||||
" {",
|
||||
' "action": "create",',
|
||||
' "type": "event",',
|
||||
' "fields": {"title": "简短事件名", "summary": "...", "participants": "...", "status": "ongoing"},',
|
||||
' "scope": {"layer": "objective", "regionPrimary": "主地区", "regionPath": ["上级地区", "主地区"], "regionSecondary": ["次级地区"]},',
|
||||
' "importance": 6,',
|
||||
' "ref": "evt1",',
|
||||
' "action": "create",',
|
||||
' "type": "event",',
|
||||
' "fields": {"title": "简短事件名", "summary": "...", "participants": "...", "status": "ongoing"},',
|
||||
' "scope": {"layer": "objective", "regionPrimary": "主地区", "regionPath": ["上级地区", "主地区"], "regionSecondary": ["次级地区"]},',
|
||||
' "storyTime": {"label": "第二天清晨", "tense": "ongoing", "relation": "same", "confidence": "high"},',
|
||||
' "importance": 6,',
|
||||
' "ref": "evt1",',
|
||||
' "links": [',
|
||||
' {"targetNodeId": "existing-id", "relation": "involved_in", "strength": 0.9},',
|
||||
' {"targetRef": "char1", "relation": "occurred_at", "strength": 0.8}',
|
||||
@@ -1430,6 +1541,10 @@ function buildDefaultExtractPrompt(schema) {
|
||||
"",
|
||||
"规则:",
|
||||
"- 每批对话最多创建 1 个事件节点,多个子事件合并为一条",
|
||||
"- batchStoryTime 表示这批对话主叙事所处的剧情时间;普通当前场景尽量填写,推不出来就留空",
|
||||
"- operations[].storyTime 用于节点自己的剧情时间;不写时系统会继承 batchStoryTime",
|
||||
"- 必须区分聊天顺序和剧情顺序,不要把“后说到”误当成“后发生”",
|
||||
"- flashback / hypothetical / future 可以写 storyTime,但通常不要把 advancesActiveTimeline 设为 true",
|
||||
"- 涉及到的角色都尽量尝试生成对应 POV 记忆和 cognitionUpdates;不必强行覆盖全图所有角色",
|
||||
"- cognitionUpdates 用来表达谁确定知道、谁误解了什么、谁只是模糊可见",
|
||||
"- 多角色场景里,pov_memory 和 cognitionUpdates 必须写清具体人物;不要把角色卡名当作 POV owner",
|
||||
@@ -1462,6 +1577,8 @@ function buildCognitiveExtractAugmentPrompt() {
|
||||
"- visibility.score 取 0..1,1 表示亲历或明确得知,0.5 左右表示间接听闻。",
|
||||
"- regionUpdates.activeRegionHint 只在这批对话明确落到某个地区时填写。",
|
||||
"- regionUpdates.adjacency 只在文本里明确出现邻接关系时填写,不要猜。",
|
||||
"- batchStoryTime.label 尽量写成可复用的剧情时间标签,例如“第二天清晨”“昨夜之后”“回忆里的童年时期”。",
|
||||
"- advancesActiveTimeline 只有在这批确实推动当前主叙事时间线时才写 true。",
|
||||
"- 若没有认知或空间变化,可返回空数组或空对象,但不要返回无效结构。",
|
||||
].join("\n");
|
||||
}
|
||||
@@ -1493,7 +1610,10 @@ export async function generateSynopsis({
|
||||
if (eventNodes.length < 3) return;
|
||||
|
||||
const eventSummaries = eventNodes
|
||||
.map((n) => `[楼${n.seq}] ${n.fields.summary || "(无)"}`)
|
||||
.map((n) => {
|
||||
const storyLabel = describeNodeStoryTime(n);
|
||||
return `[楼${n.seq}]${storyLabel ? ` [${storyLabel}]` : ""} ${n.fields.summary || "(无)"}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const characterNodes = getActiveNodes(graph, "character");
|
||||
@@ -1503,8 +1623,16 @@ export async function generateSynopsis({
|
||||
|
||||
const threadNodes = getActiveNodes(graph, "thread");
|
||||
const threadSummary = threadNodes
|
||||
.map((n) => `${n.fields.title}: ${n.fields.status || "active"}`)
|
||||
.map((n) => {
|
||||
const storyLabel = describeNodeStoryTime(n);
|
||||
return `${n.fields.title}: ${n.fields.status || "active"}${storyLabel ? `(${storyLabel})` : ""}`;
|
||||
})
|
||||
.join("; ");
|
||||
const synopsisStoryTimeSpan = deriveStoryTimeSpanFromNodes(
|
||||
graph,
|
||||
[...eventNodes, ...threadNodes],
|
||||
"derived",
|
||||
);
|
||||
|
||||
const synopsisPromptBuild = await buildTaskPrompt(settings, "synopsis", {
|
||||
taskName: "synopsis",
|
||||
@@ -1572,6 +1700,7 @@ export async function generateSynopsis({
|
||||
updateNode(graph, existingSynopsis.id, {
|
||||
fields: { summary: result.summary, scope: `楼 1 ~ ${currentSeq}` },
|
||||
seq: Math.max(existingSynopsis.seq || 0, currentSeq),
|
||||
storyTimeSpan: synopsisStoryTimeSpan,
|
||||
});
|
||||
existingSynopsis.seqRange = [
|
||||
Math.min(existingSynopsis.seqRange?.[0] ?? currentSeq, currentSeq),
|
||||
@@ -1586,6 +1715,7 @@ export async function generateSynopsis({
|
||||
seq: currentSeq,
|
||||
importance: 9.0,
|
||||
});
|
||||
node.storyTimeSpan = synopsisStoryTimeSpan;
|
||||
addNode(graph, node);
|
||||
debugLog("[ST-BME] 全局概要已创建");
|
||||
}
|
||||
@@ -1618,7 +1748,10 @@ export async function generateReflection({
|
||||
.slice(-5);
|
||||
|
||||
const eventSummary = recentEvents
|
||||
.map((n) => `[楼${n.seq}] ${n.fields.summary || "(无)"}`)
|
||||
.map((n) => {
|
||||
const storyLabel = describeNodeStoryTime(n);
|
||||
return `[楼${n.seq}]${storyLabel ? ` [${storyLabel}]` : ""} ${n.fields.summary || "(无)"}`;
|
||||
})
|
||||
.join("\n");
|
||||
const characterSummary = recentCharacters
|
||||
.map(
|
||||
@@ -1629,9 +1762,14 @@ export async function generateReflection({
|
||||
const threadSummary = recentThreads
|
||||
.map(
|
||||
(n) =>
|
||||
`${n.fields.title || n.fields.name || n.id}: ${n.fields.status || n.fields.summary || "active"}`,
|
||||
`${n.fields.title || n.fields.name || n.id}: ${n.fields.status || n.fields.summary || "active"}${describeNodeStoryTime(n) ? `(${describeNodeStoryTime(n)})` : ""}`,
|
||||
)
|
||||
.join("\n");
|
||||
const reflectionStoryTimeSpan = deriveStoryTimeSpanFromNodes(
|
||||
graph,
|
||||
[...recentEvents, ...recentThreads],
|
||||
"derived",
|
||||
);
|
||||
const contradictionSummary = contradictEdges
|
||||
.map((e) => `${e.fromId} -> ${e.toId} (${e.relation})`)
|
||||
.join("\n");
|
||||
@@ -1714,6 +1852,7 @@ export async function generateReflection({
|
||||
seq: currentSeq,
|
||||
importance: Math.max(5, Math.min(10, result.importance ?? 7)),
|
||||
});
|
||||
reflectionNode.storyTimeSpan = reflectionStoryTimeSpan;
|
||||
addNode(graph, reflectionNode);
|
||||
|
||||
for (const eventNode of recentEvents.slice(-3)) {
|
||||
|
||||
Reference in New Issue
Block a user