89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common";
|
|
import type { EmotionTag } from "@kokorone/shared";
|
|
import { WorkingMemoryService } from "./working-memory.service.js";
|
|
import { MemoryConsolidationService } from "./memory-consolidation.service.js";
|
|
import { ForgettingSweepService } from "./forgetting-sweep.service.js";
|
|
import { KeywordMemoryRetriever } from "./memory-retriever.service.js";
|
|
import { InProcessJobQueue } from "./in-process-job-queue.service.js";
|
|
|
|
interface AppendMessageBody {
|
|
role: "user" | "character";
|
|
content: string;
|
|
emotionTag?: EmotionTag;
|
|
emotionIntensity?: number;
|
|
}
|
|
|
|
@Controller("memory")
|
|
export class MemoryController {
|
|
// C-3 排程抽象層驗證用:記錄測試性延遲工作是否已觸發,僅供 smoke test 檢查 schedule(at) 準時性。
|
|
private readonly scheduledJobFired = new Map<string, boolean>();
|
|
|
|
constructor(
|
|
private readonly workingMemory: WorkingMemoryService,
|
|
private readonly consolidation: MemoryConsolidationService,
|
|
private readonly forgettingSweep: ForgettingSweepService,
|
|
private readonly retriever: KeywordMemoryRetriever,
|
|
private readonly jobQueue: InProcessJobQueue,
|
|
) {}
|
|
|
|
@Post(":characterId/sessions/:sessionId/messages")
|
|
async appendMessage(@Param("sessionId") sessionId: string, @Body() body: AppendMessageBody) {
|
|
await this.workingMemory.append(sessionId, {
|
|
role: body.role,
|
|
content: body.content,
|
|
timestamp: new Date(),
|
|
emotionTag: body.emotionTag,
|
|
emotionIntensity: body.emotionIntensity,
|
|
});
|
|
return { context: await this.workingMemory.getContext(sessionId) };
|
|
}
|
|
|
|
@Get(":characterId/sessions/:sessionId/messages")
|
|
async getContext(@Param("sessionId") sessionId: string) {
|
|
return { context: await this.workingMemory.getContext(sessionId) };
|
|
}
|
|
|
|
@Post(":characterId/sessions/:sessionId/consolidate")
|
|
async consolidate(@Param("characterId") characterId: string, @Param("sessionId") sessionId: string) {
|
|
await this.consolidation.consolidate(characterId, sessionId);
|
|
return { ok: true };
|
|
}
|
|
|
|
@Get(":characterId/retrieve")
|
|
async retrieve(
|
|
@Param("characterId") characterId: string,
|
|
@Query("query") query: string,
|
|
@Query("limit") limit?: string,
|
|
@Query("relatedUserId") relatedUserId?: string,
|
|
@Query("excludePrivate") excludePrivate?: string,
|
|
) {
|
|
const results = await this.retriever.retrieve(characterId, query ?? "", {
|
|
limit: limit ? Number(limit) : undefined,
|
|
relatedUserId,
|
|
excludePrivate: excludePrivate === "true",
|
|
});
|
|
return { results };
|
|
}
|
|
|
|
@Post("forgetting-sweep")
|
|
async runForgettingSweep(@Body() body: { now?: string }) {
|
|
const now = body?.now ? new Date(body.now) : new Date();
|
|
return this.forgettingSweep.run(now);
|
|
}
|
|
|
|
@Post("test-scheduled-job")
|
|
scheduleTestJob(@Body() body: { id: string; delaySeconds: number }) {
|
|
this.scheduledJobFired.set(body.id, false);
|
|
const at = new Date(Date.now() + body.delaySeconds * 1000);
|
|
this.jobQueue.schedule(at, `test-job-${body.id}`, () => {
|
|
this.scheduledJobFired.set(body.id, true);
|
|
});
|
|
return { scheduled: true };
|
|
}
|
|
|
|
@Get("test-scheduled-job/:id")
|
|
checkTestJob(@Param("id") id: string) {
|
|
return { fired: this.scheduledJobFired.get(id) ?? false };
|
|
}
|
|
}
|