Files
Kokorone/prisma/schema.prisma
T
Jeffery d82b834e47 feat: 完成 I 群組 — 生活作息與離線生活
- ScheduleService:I-1 平日/週末/假期時段表(可為角色寫入高中生預設作息)、
  I-3 時間感知(Asia/Taipei 當地時間,不受伺服器時區影響)、剛醒/睡前偵測
- constants.ts:I-2 時段狀態 → 回應可用度對照(FREE/SEMI_BUSY/BUSY/SLEEP →
  NORMAL/BRIEF/DELAYED/NO_REPLY),沒有作息表的角色一律視為 NORMAL(相容既有角色)
- ScheduleExceptionService:I-4 三種破例(深夜還陪你聊/上課偷回訊息/為你調整行程),
  各自獨立冷卻期,觸發時記入關係帳本
- ScheduleEmotionBaselineService:I-5 作息驅動情緒基線(社團剛結束的疲憊、假日偏高心情),
  只在本輪沒有明確情緒訊號時介入,串接進 EmotionService.processInput 的新增選項
- OfflineEventService:I-6 離線事件生成,依「今天已發生的固定行程」挑選對應模板,
  一天只生成一次,寫入低權重情節記憶
- DialogueService 整合以上機制:離線事件生成 → 情緒基線標記 → 累積親密度 →
  作息可用度判定 → 不符合則嘗試破例 → 依可用度決定回覆內容
- 修正真實 bug:G-5 角色建立流程誤用 source=OFFLINE_GENERATED 標記背景故事,
  與 I-6「今天是否已生成離線事件」的判斷共用同一標記,已改為 SOURCE_EXTRACTION
- ChatView.tsx 補上 NO_REPLY/context 為 null 的防呆(作息可用度整合後的必要調整)
- scripts/smoke/I.mjs:以獨立測試角色(避免污染種子角色的排程狀態)驗證全部六項

npm run restart && npm run smoke -- I 皆通過(I-V),A~H 群組冒煙測試無回歸。
2026-08-13 14:08:24 +08:00

273 lines
8.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Kokorone 資料模型
// R-2 會將 provider 由 sqlite 切換為 postgresql,並為 MemoryRetriever 加上 pgvector 語意檢索。
generator client {
provider = "prisma-client"
output = "../packages/db/generated"
}
datasource db {
provider = "sqlite"
}
enum CharacterSource {
ORIGINAL // 原創
EXISTING_WORK // 既有作品
}
enum CharacterBuildStatus {
CANDIDATE // 候補
BUILT // 已建置
}
enum MemorySource {
INTERACTION // 互動
OFFLINE_GENERATED // 離線生成
SOURCE_EXTRACTION // 原作萃取
}
// 情緒標籤類型:對應情緒子系統六種狀態(見 D 群組)
enum EmotionTag {
CALM // 平靜
JOY // 愉悅
SAD // 低落
ALERT // 警戒
SHY // 害羞
GRUMPY // 彆扭
}
enum RelationshipStage {
STRANGER // 陌生 0-19
ACQUAINTANCE // 認識 20-39
FRIEND // 朋友 40-59
CLOSE_AMBIGUOUS // 摯友-曖昧 60-79
BONDED // 羈絆 80-100
}
// I-1 週期表:平日/週末/假期各一份時段表
enum ScheduleDayType {
WEEKDAY // 平日
WEEKEND // 週末
HOLIDAY // 假期/特殊日
}
enum ScheduleSlotStatus {
SLEEP // 睡眠
BUSY // 忙碌
SEMI_BUSY // 半忙碌
FREE // 空閒
}
model User {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
relationships Relationship[]
relatedMemories EpisodicMemory[]
@@map("users")
}
model Work {
id String @id @default(cuid())
title String
volumeProgress String? // 卷數進度
worldview String? // 世界觀
progressAnchor String? // 作品進度錨定點(M-9/N 群組使用;描述作品實際描寫過的時期)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
characters Character[]
@@map("works")
}
model Character {
id String @id @default(cuid())
workId String?
work Work? @relation(fields: [workId], references: [id], onDelete: SetNull)
source CharacterSource
buildStatus CharacterBuildStatus @default(CANDIDATE)
// 角色設定表七欄位
basicInfo String // 基本資料:名字、年齡、外型、出身
backgroundStory String // 背景故事:成長經歷、重大事件
personalityArchetype String // 性格原型:傲嬌/冷淡/天然呆/元氣/大小姐/三無
likesDislikes String // 喜好/厭惡
goalsObsessions String // 目標/執念
speechStyle String // 說話方式:口癖、語尾詞、第一/第二人稱
initialRelationValue String? // 人際初始值:對誰親近、對誰有心結
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
aliases CharacterAlias[]
episodicMemories EpisodicMemory[]
semanticMemories SemanticMemory[]
proceduralRules ProceduralRule[]
emotionState EmotionState?
relationships Relationship[]
contrastTriggerLogs ContrastTriggerLog[]
scheduleSlots ScheduleSlot[]
specialDays SpecialDay[]
scheduleExceptions ScheduleExceptionLog[]
@@map("characters")
}
// G-4 反差萌稀有度控制:記錄每次反差行為觸發的時間,供冷卻期判定。
model ContrastTriggerLog {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
triggeredAt DateTime @default(now())
@@map("contrast_trigger_logs")
}
// I-1 作息表:平日/週末/假期各一份時段表,每個時段一列。
model ScheduleSlot {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
dayType ScheduleDayType
startMinute Int // 當天 00:00 起算的分鐘數(0~1440),跨午夜的時段請拆成兩列
endMinute Int
status ScheduleSlotStatus
label String? // 固定行程名稱,例如「社團活動」
@@map("schedule_slots")
}
// I-1 特殊日:考試週、生日、原作紀念日——當天整天視為 HOLIDAY 作息。
model SpecialDay {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
date DateTime // 只使用日期部分(比對年/月/日)
label String
@@map("special_days")
}
// I-4 破例規則:記錄每種破例最後一次觸發的時間,供稀有度冷卻判定。
model ScheduleExceptionLog {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
exceptionType String // LATE_NIGHT_CHAT/SNEAK_REPLY_IN_CLASS/ADJUST_SCHEDULE
triggeredAt DateTime @default(now())
@@map("schedule_exception_logs")
}
model CharacterAlias {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
formalName String // 正式名
nickname String? // 暱稱
calledByOthers String? // 他人稱呼(作品中其他角色如何稱呼)
createdAt DateTime @default(now())
@@map("character_aliases")
}
model EpisodicMemory {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
content String
occurredAt DateTime // 發生時間
emotionTag EmotionTag
emotionIntensity Float // 情緒強度
retrievalCount Int @default(0) // 提取次數
lastRetrievedAt DateTime?
source MemorySource
weight Float @default(1) // 權重(供遺忘/檢索排序使用)
relatedUserId String? // 這段記憶與哪位使用者相關(E-5 關係加權檢索用)
relatedUser User? @relation(fields: [relatedUserId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@map("episodic_memories")
}
model SemanticMemory {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
fact String // 去情境化事實
about String // 對象(關於誰/什麼)
createdAt DateTime @default(now())
@@map("semantic_memories")
}
model ProceduralRule {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
situation String // 情境
responsePattern String // 回應模式
weight Float @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("procedural_rules")
}
model EmotionState {
id String @id @default(cuid())
characterId String @unique
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
calm Float @default(100) // 平靜
joy Float @default(0) // 愉悅
sad Float @default(0) // 低落
alert Float @default(0) // 警戒
shy Float @default(0) // 害羞
grumpy Float @default(0) // 彆扭
updatedAt DateTime @updatedAt
@@map("emotion_states")
}
model Relationship {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
intimacy Int @default(0) // 親密度 0~100
trust Int @default(0) // 信任度 0~100
firstMet DateTime?
lastInteraction DateTime?
interactionCount Int @default(0)
stage RelationshipStage @default(STRANGER)
ledgerEntries SentimentLedgerEntry[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([characterId, userId])
@@map("relationships")
}
model SentimentLedgerEntry {
id String @id @default(cuid())
relationshipId String
relationship Relationship @relation(fields: [relationshipId], references: [id], onDelete: Cascade)
date DateTime
event String
weight Float // 允許正負:正向事件為正值,負向事件為負值
createdAt DateTime @default(now())
@@map("sentiment_ledger_entries")
}