- 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 群組冒煙測試無回歸。
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import type { EmotionTag, RelationshipStage } from "@kokorone/shared";
|
||
|
||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001";
|
||
|
||
export interface ChatResponseStyle {
|
||
tone: string;
|
||
sentenceLength: "short" | "medium" | "long";
|
||
initiative: "low" | "medium" | "high";
|
||
}
|
||
|
||
export interface ChatContext {
|
||
emotion: { dominant: EmotionTag; style: ChatResponseStyle };
|
||
relationship: { intimacy: number; trust: number; stage: RelationshipStage };
|
||
}
|
||
|
||
export type ChatAvailability = "NORMAL" | "BRIEF" | "DELAYED" | "NO_REPLY";
|
||
|
||
export interface ChatResult {
|
||
text: string;
|
||
actions: string[];
|
||
isFastChannel: boolean;
|
||
availability: ChatAvailability;
|
||
exceptionType: string | null;
|
||
// I-2:作息為睡眠且未破例時不會組裝上下文,此時為 null。
|
||
context: ChatContext | null;
|
||
}
|
||
|
||
export async function sendChatMessage(
|
||
characterId: string,
|
||
userId: string,
|
||
sessionId: string,
|
||
text: string,
|
||
): Promise<ChatResult> {
|
||
const res = await fetch(`${API_BASE_URL}/chat/${characterId}`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ userId, sessionId, text }),
|
||
});
|
||
if (!res.ok) {
|
||
throw new Error(`對話請求失敗(${res.status})`);
|
||
}
|
||
return res.json();
|
||
}
|
||
|
||
export async function endChatSession(sessionId: string, characterId: string): Promise<void> {
|
||
await fetch(`${API_BASE_URL}/session/${sessionId}/end`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ characterId }),
|
||
});
|
||
}
|