59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { PrismaService } from "../prisma/prisma.service.js";
|
|
import { WorkingMemoryService } from "./working-memory.service.js";
|
|
import { HIGH_EMOTION_THRESHOLD } from "./constants.js";
|
|
import { isUserTrivia } from "../personality/cuteness-behavior.js";
|
|
|
|
function normalize(content: string): string {
|
|
return content.trim().toLowerCase();
|
|
}
|
|
|
|
// C-4 睡眠固化:對話中只累積在工作記憶,session 結束時才批次萃取寫入長期記憶。
|
|
@Injectable()
|
|
export class MemoryConsolidationService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly workingMemory: WorkingMemoryService,
|
|
) {}
|
|
|
|
async consolidate(characterId: string, sessionId: string): Promise<void> {
|
|
const entries = await this.workingMemory.getContext(sessionId);
|
|
|
|
const contentCounts = new Map<string, number>();
|
|
for (const entry of entries) {
|
|
const key = normalize(entry.content);
|
|
contentCounts.set(key, (contentCounts.get(key) ?? 0) + 1);
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
const emotionIntensity = entry.emotionIntensity ?? 0;
|
|
const isHighEmotion = emotionIntensity >= HIGH_EMOTION_THRESHOLD;
|
|
const isRepeated = (contentCounts.get(normalize(entry.content)) ?? 0) > 1;
|
|
|
|
if (!isHighEmotion && !isRepeated) {
|
|
// G-6 記住小事:使用者隨口提過的瑣事即使情緒強度低也額外加權保留(寫入語意記憶,日後可主動提起)。
|
|
if (entry.role === "user" && isUserTrivia(entry.content)) {
|
|
await this.prisma.client.semanticMemory.create({
|
|
data: { characterId, fact: entry.content, about: "使用者" },
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
await this.prisma.client.episodicMemory.create({
|
|
data: {
|
|
characterId,
|
|
content: entry.content,
|
|
occurredAt: entry.timestamp,
|
|
emotionTag: entry.emotionTag ?? "CALM",
|
|
emotionIntensity,
|
|
source: "INTERACTION",
|
|
weight: isHighEmotion ? 1 + emotionIntensity : 0.5,
|
|
},
|
|
});
|
|
}
|
|
|
|
await this.workingMemory.clear(sessionId);
|
|
}
|
|
}
|