import { Injectable } from "@nestjs/common"; import { PrismaService } from "../prisma/prisma.service.js"; import { RuleBasedEmotionTagger } from "../emotion/emotion-tagger.js"; import { stageForIntimacy } from "../relationship/constants.js"; export interface InitialRelationshipInput { userId: string; intimacy?: number; trust?: number; } export interface CreateCharacterInput { id?: string; source: "ORIGINAL" | "EXISTING_WORK"; buildStatus?: "CANDIDATE" | "BUILT"; workId?: string; formalName: string; basicInfo: string; backgroundStory: string; personalityArchetype: string; likesDislikes: string; goalsObsessions: string; speechStyle: string; initialRelationValue?: string; initialRelationship?: InitialRelationshipInput; } // G-5 角色建立流程:由角色設定表產生初始語意記憶+初始情節記憶+情緒參數+關係初始值。 @Injectable() export class CharacterCreationService { constructor( private readonly prisma: PrismaService, private readonly tagger: RuleBasedEmotionTagger, ) {} async createCharacter(input: CreateCharacterInput, now: Date = new Date()) { const character = await this.prisma.client.character.create({ data: { id: input.id, source: input.source, buildStatus: input.buildStatus ?? "CANDIDATE", workId: input.workId, basicInfo: input.basicInfo, backgroundStory: input.backgroundStory, personalityArchetype: input.personalityArchetype, likesDislikes: input.likesDislikes, goalsObsessions: input.goalsObsessions, speechStyle: input.speechStyle, initialRelationValue: input.initialRelationValue, }, }); await this.prisma.client.characterAlias.create({ data: { characterId: character.id, formalName: input.formalName }, }); // 初始語意記憶:關於自己的事實。 await this.prisma.client.semanticMemory.create({ data: { characterId: character.id, fact: input.basicInfo, about: "自己" }, }); // 初始情節記憶:背景故事,重用 D-1 的規則標記器判斷情緒基調(帶情緒標記)。 const signal = this.tagger.tag({ text: input.backgroundStory }); await this.prisma.client.episodicMemory.create({ data: { characterId: character.id, content: input.backgroundStory, occurredAt: now, emotionTag: signal.tag, emotionIntensity: signal.intensity || 0.3, // 來自角色設定表本身,不是 I-6 的「離線生成日常事件」,兩者不可共用同一 source 值 // (I-6 用 source=OFFLINE_GENERATED 判斷「今天是否已生成過離線事件」,混用會誤判)。 source: "SOURCE_EXTRACTION", weight: 2, }, }); // 情緒參數:預設值(calm=100,其餘為 0)。 await this.prisma.client.emotionState.create({ data: { characterId: character.id } }); // 關係初始值(若指定了對象)。 if (input.initialRelationship) { const intimacy = input.initialRelationship.intimacy ?? 0; await this.prisma.client.relationship.create({ data: { characterId: character.id, userId: input.initialRelationship.userId, intimacy, trust: input.initialRelationship.trust ?? 0, firstMet: now, stage: stageForIntimacy(intimacy), }, }); } return character; } }