Files
ST-Amily2-Chat-Optimisation/core/relationship-graph/manager.js

238 lines
8.3 KiB
JavaScript

import { getContext, extension_settings } from "/scripts/extensions.js";
import { saveSettingsDebounced } from "/script.js";
import { getCharacterStableId } from "../utils/context-utils.js";
import { getMemoryState } from "../table-system/manager.js";
import { extensionName } from "../../utils/settings.js";
const GRAPH_KEY = 'Amily2_Relationship_Graph';
let graphData = {
nodes: [],
edges: []
};
export function getGraph() {
return graphData;
}
export function clearGraph() {
graphData = { nodes: [], edges: [] };
saveGraph();
}
export function syncGraphFromTables() {
const tables = getMemoryState();
if (!tables) return;
graphData = { nodes: [], edges: [] };
const context = getContext();
const userName = context.name1 || '用户';
addNode('user', userName, 'user');
// 1. 处理角色表 (Character Table)
const charTable = tables.find(t => t.name.includes('角色') || t.name === 'Character');
if (charTable) {
const nameIdx = charTable.headers.findIndex(h => h.includes('角色名') || h.includes('Name'));
const relationIdx = charTable.headers.findIndex(h => h.includes('关系') || h.includes('Relation'));
const infoIdx = charTable.headers.findIndex(h => h.includes('重要信息') || h.includes('Info'));
const targetIdx = charTable.headers.findIndex(h => /(对象|指向|Target|To|Object)/i.test(h));
if (nameIdx !== -1) {
charTable.rows.forEach(row => {
const name = row[nameIdx];
if (!name) return;
const metadata = {};
if (infoIdx !== -1) metadata.info = row[infoIdx];
addNode(name, name, 'character', metadata);
if (relationIdx !== -1 && row[relationIdx]) {
const relation = row[relationIdx];
let targetId = 'user';
// Check if there is an explicit target for the relationship
if (targetIdx !== -1 && row[targetIdx]) {
targetId = row[targetIdx].trim();
addNode(targetId, targetId, targetId === 'user' || targetId === userName ? 'user' : 'entity');
}
addEdge(name, targetId, relation);
}
});
}
}
// 2. 处理关系表 (Relationship Table)
const relTable = tables.find(t => t.name.includes('关系') || t.name === 'Relationship');
if (relTable) {
const sourceIdx = relTable.headers.findIndex(h => /(主动方|Source|Subject|From)/i.test(h));
const targetIdx = relTable.headers.findIndex(h => /(被动方|对象|Target|Object|To)/i.test(h));
const relationIdx = relTable.headers.findIndex(h => /(关系|Relation)/i.test(h));
const detailIdx = relTable.headers.findIndex(h => /(详情|Detail|Info)/i.test(h));
if (sourceIdx !== -1 && targetIdx !== -1 && relationIdx !== -1) {
relTable.rows.forEach(row => {
const source = row[sourceIdx];
const target = row[targetIdx];
const relation = row[relationIdx];
if (!source || !target || !relation) return;
// 确保节点存在
addNode(source, source, source === userName ? 'user' : 'entity');
addNode(target, target, target === userName ? 'user' : 'entity');
addEdge(source, target, relation);
});
}
}
console.log(`[关系图谱] 已从表格同步 ${graphData.nodes.length} 个节点和 ${graphData.edges.length} 条边。`);
saveGraph();
}
export function addNode(id, label, type = 'entity', metadata = {}) {
const safeId = id.trim();
if (!graphData.nodes.find(n => n.id === safeId)) {
graphData.nodes.push({ id: safeId, label, type, metadata });
return true;
}
return false;
}
export function addEdge(source, target, relation, weight = 1.0) {
const safeSource = source.trim();
const safeTarget = target.trim();
const sourceNode = graphData.nodes.find(n => n.id === safeSource);
const targetNode = graphData.nodes.find(n => n.id === safeTarget);
if (!sourceNode || !targetNode) {
return false;
}
const existingEdge = graphData.edges.find(e =>
e.source === safeSource && e.target === safeTarget && e.relation === relation
);
if (!existingEdge) {
graphData.edges.push({ source: safeSource, target: safeTarget, relation, weight });
return true;
}
return false;
}
export function getRelatedNodes(nodeId, maxDepth = 1) {
const related = [];
const queue = [{ id: nodeId, depth: 0 }];
const visited = new Set([nodeId]);
while (queue.length > 0) {
const { id, depth } = queue.shift();
if (depth >= maxDepth) continue;
const outgoing = graphData.edges.filter(e => e.source === id);
for (const edge of outgoing) {
if (!visited.has(edge.target)) {
visited.add(edge.target);
const node = graphData.nodes.find(n => n.id === edge.target);
if (node) {
related.push({ node, relation: edge.relation, direction: 'out', depth: depth + 1 });
queue.push({ id: edge.target, depth: depth + 1 });
}
}
}
const incoming = graphData.edges.filter(e => e.target === id);
for (const edge of incoming) {
if (!visited.has(edge.source)) {
visited.add(edge.source);
const node = graphData.nodes.find(n => n.id === edge.source);
if (node) {
related.push({ node, relation: edge.relation, direction: 'in', depth: depth + 1 });
queue.push({ id: edge.source, depth: depth + 1 });
}
}
}
}
return related;
}
function getGraphStore(create = false) {
if (!extension_settings[extensionName]) {
if (!create) return null;
extension_settings[extensionName] = {};
}
const root = extension_settings[extensionName];
if (!root.relationship_graphs) {
if (!create) return null;
root.relationship_graphs = {};
}
return root.relationship_graphs;
}
function migrateLegacyRelationshipGraphs() {
const legacy = extension_settings.relationship_graphs;
if (!legacy || typeof legacy !== 'object') return;
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
}
const root = extension_settings[extensionName];
if (!root.relationship_graphs) {
root.relationship_graphs = legacy;
console.log(`[关系图谱] 已迁移旧版 'relationship_graphs' 到 extension_settings['${extensionName}']。`);
} else {
console.log(`[关系图谱] 发现遗留顶层 'relationship_graphs',但新位置已存在;合并遗留数据并清理顶层。`);
for (const [cid, data] of Object.entries(legacy)) {
if (!root.relationship_graphs[cid]) {
root.relationship_graphs[cid] = data;
}
}
}
delete extension_settings.relationship_graphs;
saveSettingsDebounced();
}
export async function saveGraph() {
const charId = getCharacterStableId();
if (!charId) return;
const store = getGraphStore(true);
if (!store) return;
store[charId] = graphData;
saveSettingsDebounced();
}
export async function loadGraph() {
const charId = getCharacterStableId();
if (!charId) return;
const store = getGraphStore(false);
if (store && store[charId]) {
graphData = store[charId];
console.log(`[关系图谱] 已加载角色 ${charId} 的图谱: ${graphData.nodes.length} 个节点, ${graphData.edges.length} 条边。`);
} else {
graphData = { nodes: [], edges: [] };
}
}
const context = getContext();
if (context) {
migrateLegacyRelationshipGraphs();
loadGraph();
document.addEventListener('AMILY2_TABLE_UPDATED', (e) => {
const { tableName } = e.detail;
if (tableName.includes('角色') || tableName === 'Character' || tableName.includes('关系') || tableName === 'Relationship') {
console.log('[关系图谱] 检测到相关表格更新,正在同步图谱...');
syncGraphFromTables();
}
});
}