2026-01-26 01:16:35 +08:00
|
|
|
|
// Story Summary - Store
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// L2 (events/characters/arcs) + L3 (facts) 统一存储
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
import { getContext, saveMetadataDebounced } from "../../../../../../extensions.js";
|
|
|
|
|
|
import { chat_metadata } from "../../../../../../../script.js";
|
|
|
|
|
|
import { EXT_ID } from "../../../core/constants.js";
|
|
|
|
|
|
import { xbLog } from "../../../core/debug-core.js";
|
|
|
|
|
|
import { clearEventVectors, deleteEventVectorsByIds } from "../vector/chunk-store.js";
|
2026-02-01 16:26:29 +08:00
|
|
|
|
import { clearEventTextIndex } from '../vector/text-search.js';
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
const MODULE_ID = 'summaryStore';
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 基础存取
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
export function getSummaryStore() {
|
|
|
|
|
|
const { chatId } = getContext();
|
|
|
|
|
|
if (!chatId) return null;
|
|
|
|
|
|
chat_metadata.extensions ||= {};
|
|
|
|
|
|
chat_metadata.extensions[EXT_ID] ||= {};
|
|
|
|
|
|
chat_metadata.extensions[EXT_ID].storySummary ||= {};
|
2026-02-02 21:45:01 +08:00
|
|
|
|
|
|
|
|
|
|
const store = chat_metadata.extensions[EXT_ID].storySummary;
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 自动迁移旧数据
|
|
|
|
|
|
if (store.json && !store.json.facts) {
|
|
|
|
|
|
const hasOldData = store.json.world?.length || store.json.characters?.relationships?.length;
|
|
|
|
|
|
if (hasOldData) {
|
|
|
|
|
|
store.json.facts = migrateToFacts(store.json);
|
|
|
|
|
|
// 删除旧字段
|
|
|
|
|
|
delete store.json.world;
|
|
|
|
|
|
if (store.json.characters) {
|
|
|
|
|
|
delete store.json.characters.relationships;
|
|
|
|
|
|
}
|
|
|
|
|
|
store.updatedAt = Date.now();
|
|
|
|
|
|
saveSummaryStore();
|
|
|
|
|
|
xbLog.info(MODULE_ID, `自动迁移完成: ${store.json.facts.length} 条 facts`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return store;
|
2026-01-26 01:16:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function saveSummaryStore() {
|
|
|
|
|
|
saveMetadataDebounced?.();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getKeepVisibleCount() {
|
|
|
|
|
|
const store = getSummaryStore();
|
|
|
|
|
|
return store?.keepVisibleCount ?? 3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 22:51:44 +08:00
|
|
|
|
export function calcHideRange(boundary) {
|
|
|
|
|
|
if (boundary == null || boundary < 0) return null;
|
|
|
|
|
|
|
2026-01-26 01:16:35 +08:00
|
|
|
|
const keepCount = getKeepVisibleCount();
|
2026-01-27 22:51:44 +08:00
|
|
|
|
const hideEnd = boundary - keepCount;
|
2026-01-26 01:16:35 +08:00
|
|
|
|
if (hideEnd < 0) return null;
|
|
|
|
|
|
return { start: 0, end: hideEnd };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function addSummarySnapshot(store, endMesId) {
|
|
|
|
|
|
store.summaryHistory ||= [];
|
|
|
|
|
|
store.summaryHistory.push({ endMesId });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// Fact 工具函数
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断是否为关系类 fact
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function isRelationFact(f) {
|
|
|
|
|
|
return /^对.+的/.test(f.p);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 22:48:07 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 从 facts 提取关系(供关系图 UI 使用)
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
export function extractRelationshipsFromFacts(facts) {
|
|
|
|
|
|
return (facts || [])
|
|
|
|
|
|
.filter(f => !f.retracted && isRelationFact(f))
|
|
|
|
|
|
.map(f => {
|
|
|
|
|
|
const match = f.p.match(/^对(.+)的/);
|
|
|
|
|
|
const to = match ? match[1] : '';
|
|
|
|
|
|
if (!to) return null;
|
|
|
|
|
|
return {
|
|
|
|
|
|
from: f.s,
|
|
|
|
|
|
to,
|
|
|
|
|
|
label: f.o,
|
|
|
|
|
|
trend: f.trend || '陌生',
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生成 fact 的唯一键(s + p)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function factKey(f) {
|
|
|
|
|
|
return `${f.s}::${f.p}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成下一个 fact ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getNextFactId(existingFacts) {
|
|
|
|
|
|
let maxId = 0;
|
|
|
|
|
|
for (const f of existingFacts || []) {
|
|
|
|
|
|
const match = f.id?.match(/^f-(\d+)$/);
|
|
|
|
|
|
if (match) {
|
|
|
|
|
|
maxId = Math.max(maxId, parseInt(match[1], 10));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return maxId + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// Facts 合并(KV 覆盖模型)
|
2026-01-26 01:16:35 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
export function mergeFacts(existingFacts, updates, floor) {
|
2026-01-26 01:16:35 +08:00
|
|
|
|
const map = new Map();
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// 加载现有 facts
|
|
|
|
|
|
for (const f of existingFacts || []) {
|
|
|
|
|
|
if (!f.retracted) {
|
|
|
|
|
|
map.set(factKey(f), f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取下一个 ID
|
|
|
|
|
|
let nextId = getNextFactId(existingFacts);
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// 应用更新
|
|
|
|
|
|
for (const u of updates || []) {
|
|
|
|
|
|
if (!u.s || !u.p) continue;
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
const key = factKey(u);
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// 删除操作
|
|
|
|
|
|
if (u.retracted === true) {
|
2026-01-26 01:16:35 +08:00
|
|
|
|
map.delete(key);
|
2026-02-02 21:45:01 +08:00
|
|
|
|
continue;
|
2026-01-26 01:16:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// 无 o 则跳过
|
|
|
|
|
|
if (!u.o || !String(u.o).trim()) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 覆盖或新增
|
|
|
|
|
|
const existing = map.get(key);
|
|
|
|
|
|
const newFact = {
|
|
|
|
|
|
id: existing?.id || `f-${nextId++}`,
|
|
|
|
|
|
s: u.s.trim(),
|
|
|
|
|
|
p: u.p.trim(),
|
|
|
|
|
|
o: String(u.o).trim(),
|
|
|
|
|
|
since: floor,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 关系类保留 trend
|
|
|
|
|
|
if (isRelationFact(newFact) && u.trend) {
|
|
|
|
|
|
newFact.trend = u.trend;
|
|
|
|
|
|
}
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// 保留原始 _addedAt(如果是更新)
|
|
|
|
|
|
if (existing?._addedAt != null) {
|
|
|
|
|
|
newFact._addedAt = existing._addedAt;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newFact._addedAt = floor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
map.set(key, newFact);
|
|
|
|
|
|
}
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
return Array.from(map.values());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 旧数据迁移
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
export function migrateToFacts(json) {
|
|
|
|
|
|
if (!json) return [];
|
|
|
|
|
|
|
|
|
|
|
|
// 已有 facts 则跳过迁移
|
|
|
|
|
|
if (json.facts?.length) return json.facts;
|
|
|
|
|
|
|
|
|
|
|
|
const facts = [];
|
|
|
|
|
|
let nextId = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 迁移 world(worldUpdate 的持久化结果)
|
|
|
|
|
|
for (const w of json.world || []) {
|
|
|
|
|
|
if (!w.category || !w.topic || !w.content) continue;
|
|
|
|
|
|
|
|
|
|
|
|
let s, p;
|
|
|
|
|
|
|
|
|
|
|
|
// 解析 topic 格式:status/knowledge/relation 用 "::" 分隔
|
|
|
|
|
|
if (w.topic.includes('::')) {
|
|
|
|
|
|
[s, p] = w.topic.split('::').map(x => x.trim());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// inventory/rule 类
|
|
|
|
|
|
s = w.topic.trim();
|
|
|
|
|
|
p = w.category;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!s || !p) continue;
|
|
|
|
|
|
|
|
|
|
|
|
facts.push({
|
|
|
|
|
|
id: `f-${nextId++}`,
|
|
|
|
|
|
s,
|
|
|
|
|
|
p,
|
|
|
|
|
|
o: w.content.trim(),
|
|
|
|
|
|
since: w.floor ?? w._addedAt ?? 0,
|
|
|
|
|
|
_addedAt: w._addedAt ?? w.floor ?? 0,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 迁移 relationships
|
|
|
|
|
|
for (const r of json.characters?.relationships || []) {
|
|
|
|
|
|
if (!r.from || !r.to) continue;
|
|
|
|
|
|
|
|
|
|
|
|
facts.push({
|
|
|
|
|
|
id: `f-${nextId++}`,
|
|
|
|
|
|
s: r.from,
|
|
|
|
|
|
p: `对${r.to}的看法`,
|
|
|
|
|
|
o: r.label || '未知',
|
|
|
|
|
|
trend: r.trend,
|
|
|
|
|
|
since: r._addedAt ?? 0,
|
|
|
|
|
|
_addedAt: r._addedAt ?? 0,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return facts;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 01:16:35 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 数据合并(L2 + L3)
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
export function mergeNewData(oldJson, parsed, endMesId) {
|
|
|
|
|
|
const merged = structuredClone(oldJson || {});
|
|
|
|
|
|
|
|
|
|
|
|
// L2 初始化
|
|
|
|
|
|
merged.keywords ||= [];
|
|
|
|
|
|
merged.events ||= [];
|
|
|
|
|
|
merged.characters ||= {};
|
|
|
|
|
|
merged.characters.main ||= [];
|
|
|
|
|
|
merged.arcs ||= [];
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// L3 初始化(不再迁移,getSummaryStore 已处理)
|
|
|
|
|
|
merged.facts ||= [];
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
// L2 数据合并
|
|
|
|
|
|
if (parsed.keywords?.length) {
|
|
|
|
|
|
merged.keywords = parsed.keywords.map(k => ({ ...k, _addedAt: endMesId }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
(parsed.events || []).forEach(e => {
|
|
|
|
|
|
e._addedAt = endMesId;
|
|
|
|
|
|
merged.events.push(e);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// newCharacters
|
2026-01-26 01:16:35 +08:00
|
|
|
|
const existingMain = new Set(
|
|
|
|
|
|
(merged.characters.main || []).map(m => typeof m === 'string' ? m : m.name)
|
|
|
|
|
|
);
|
|
|
|
|
|
(parsed.newCharacters || []).forEach(name => {
|
|
|
|
|
|
if (!existingMain.has(name)) {
|
|
|
|
|
|
merged.characters.main.push({ name, _addedAt: endMesId });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// arcUpdates
|
2026-01-26 01:16:35 +08:00
|
|
|
|
const arcMap = new Map((merged.arcs || []).map(a => [a.name, a]));
|
|
|
|
|
|
(parsed.arcUpdates || []).forEach(update => {
|
|
|
|
|
|
const existing = arcMap.get(update.name);
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
|
existing.trajectory = update.trajectory;
|
|
|
|
|
|
existing.progress = update.progress;
|
|
|
|
|
|
if (update.newMoment) {
|
|
|
|
|
|
existing.moments = existing.moments || [];
|
|
|
|
|
|
existing.moments.push({ text: update.newMoment, _addedAt: endMesId });
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arcMap.set(update.name, {
|
|
|
|
|
|
name: update.name,
|
|
|
|
|
|
trajectory: update.trajectory,
|
|
|
|
|
|
progress: update.progress,
|
|
|
|
|
|
moments: update.newMoment ? [{ text: update.newMoment, _addedAt: endMesId }] : [],
|
|
|
|
|
|
_addedAt: endMesId,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
merged.arcs = Array.from(arcMap.values());
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// L3 factUpdates 合并
|
|
|
|
|
|
merged.facts = mergeFacts(merged.facts, parsed.factUpdates || [], endMesId);
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 回滚
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
export async function rollbackSummaryIfNeeded() {
|
|
|
|
|
|
const { chat, chatId } = getContext();
|
|
|
|
|
|
const currentLength = Array.isArray(chat) ? chat.length : 0;
|
|
|
|
|
|
const store = getSummaryStore();
|
|
|
|
|
|
|
|
|
|
|
|
if (!store || store.lastSummarizedMesId == null || store.lastSummarizedMesId < 0) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lastSummarized = store.lastSummarizedMesId;
|
|
|
|
|
|
|
|
|
|
|
|
if (currentLength <= lastSummarized) {
|
|
|
|
|
|
const deletedCount = lastSummarized + 1 - currentLength;
|
|
|
|
|
|
|
|
|
|
|
|
if (deletedCount < 2) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xbLog.warn(MODULE_ID, `删除已总结楼层 ${deletedCount} 条,触发回滚`);
|
|
|
|
|
|
|
|
|
|
|
|
const history = store.summaryHistory || [];
|
|
|
|
|
|
let targetEndMesId = -1;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
|
|
|
|
if (history[i].endMesId < currentLength) {
|
|
|
|
|
|
targetEndMesId = history[i].endMesId;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await executeRollback(chatId, store, targetEndMesId, currentLength);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function executeRollback(chatId, store, targetEndMesId, currentLength) {
|
|
|
|
|
|
const oldEvents = store.json?.events || [];
|
|
|
|
|
|
|
|
|
|
|
|
if (targetEndMesId < 0) {
|
|
|
|
|
|
store.lastSummarizedMesId = -1;
|
|
|
|
|
|
store.json = null;
|
|
|
|
|
|
store.summaryHistory = [];
|
|
|
|
|
|
store.hideSummarizedHistory = false;
|
|
|
|
|
|
|
|
|
|
|
|
await clearEventVectors(chatId);
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const deletedEventIds = oldEvents
|
|
|
|
|
|
.filter(e => (e._addedAt ?? 0) > targetEndMesId)
|
|
|
|
|
|
.map(e => e.id);
|
|
|
|
|
|
|
|
|
|
|
|
const json = store.json || {};
|
|
|
|
|
|
|
|
|
|
|
|
// L2 回滚
|
|
|
|
|
|
json.events = (json.events || []).filter(e => (e._addedAt ?? 0) <= targetEndMesId);
|
|
|
|
|
|
json.keywords = (json.keywords || []).filter(k => (k._addedAt ?? 0) <= targetEndMesId);
|
|
|
|
|
|
json.arcs = (json.arcs || []).filter(a => (a._addedAt ?? 0) <= targetEndMesId);
|
|
|
|
|
|
json.arcs.forEach(a => {
|
|
|
|
|
|
a.moments = (a.moments || []).filter(m =>
|
|
|
|
|
|
typeof m === 'string' || (m._addedAt ?? 0) <= targetEndMesId
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (json.characters) {
|
|
|
|
|
|
json.characters.main = (json.characters.main || []).filter(m =>
|
|
|
|
|
|
typeof m === 'string' || (m._addedAt ?? 0) <= targetEndMesId
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// L3 facts 回滚
|
|
|
|
|
|
json.facts = (json.facts || []).filter(f => (f._addedAt ?? 0) <= targetEndMesId);
|
2026-01-26 01:16:35 +08:00
|
|
|
|
|
|
|
|
|
|
store.json = json;
|
|
|
|
|
|
store.lastSummarizedMesId = targetEndMesId;
|
|
|
|
|
|
store.summaryHistory = (store.summaryHistory || []).filter(h => h.endMesId <= targetEndMesId);
|
|
|
|
|
|
|
|
|
|
|
|
if (deletedEventIds.length > 0) {
|
|
|
|
|
|
await deleteEventVectorsByIds(chatId, deletedEventIds);
|
|
|
|
|
|
xbLog.info(MODULE_ID, `回滚删除 ${deletedEventIds.length} 个事件向量`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
store.updatedAt = Date.now();
|
|
|
|
|
|
saveSummaryStore();
|
|
|
|
|
|
|
|
|
|
|
|
xbLog.info(MODULE_ID, `回滚完成,目标楼层: ${targetEndMesId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function clearSummaryData(chatId) {
|
|
|
|
|
|
const store = getSummaryStore();
|
|
|
|
|
|
if (store) {
|
|
|
|
|
|
delete store.json;
|
|
|
|
|
|
store.lastSummarizedMesId = -1;
|
|
|
|
|
|
store.updatedAt = Date.now();
|
|
|
|
|
|
saveSummaryStore();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (chatId) {
|
|
|
|
|
|
await clearEventVectors(chatId);
|
|
|
|
|
|
}
|
2026-02-02 21:45:01 +08:00
|
|
|
|
|
2026-02-01 16:26:29 +08:00
|
|
|
|
clearEventTextIndex();
|
2026-02-02 21:45:01 +08:00
|
|
|
|
|
2026-01-26 01:16:35 +08:00
|
|
|
|
xbLog.info(MODULE_ID, '总结数据已清空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
2026-02-02 21:45:01 +08:00
|
|
|
|
// L3 数据读取(供 prompt.js / recall.js 使用)
|
2026-01-26 01:16:35 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-02-02 21:45:01 +08:00
|
|
|
|
export function getFacts() {
|
2026-01-26 01:16:35 +08:00
|
|
|
|
const store = getSummaryStore();
|
2026-02-02 21:45:01 +08:00
|
|
|
|
return (store?.json?.facts || []).filter(f => !f.retracted);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getNewCharacters() {
|
|
|
|
|
|
const store = getSummaryStore();
|
|
|
|
|
|
return (store?.json?.characters?.main || []).map(m =>
|
|
|
|
|
|
typeof m === 'string' ? m : m.name
|
|
|
|
|
|
);
|
2026-01-27 22:51:44 +08:00
|
|
|
|
}
|