feat: 完成 H 群組 — 網頁對話介面(第一個可實際使用的垂直切片)
- POST /chat/:characterId:正式對外對話端點,DialogueService 補上情緒標記與親密度 累積(每輪對話走完整管線:標記情緒→檢索記憶→組裝→生成→過濾) - POST /session/:sessionId/end:觸發睡眠固化 - NestJS 啟用 CORS(WEB_ORIGIN,預設 http://localhost:3100),本清單第一次有瀏覽器直接呼叫 api - apps/web/app/chat:對話頁面(ChatView 用戶端元件),電腦雙欄/行動單欄同一份 RWD 程式, 立繪半身像置頂佔 40% 為行動版背景層 - 情緒晶片+親密度數字顯示,跟著每輪回應即時更新 - 動作描寫標記(*…*)以暮空紫小字獨立呈現,與台詞區分 - HeartbeatWave 元件:載入時由左至右畫出、親密度決定振幅、通知紅點心跳脈動, 皆尊重 prefers-reduced-motion;首頁與對話頁共用同一元件 - 色票換成 wiki 主視覺提案頁的完整 token 組(含官方深色模式數值) - scripts/smoke/H.mjs:端到端驗證對話 API 狀態變化、session 結束固化、頁面結構標記; 另以 Playwright + headless Chromium 實際開瀏覽器驗證桌面/行動版面、送出訊息、 動作標記渲染,過程無 console 錯誤 npm run restart && npm run smoke -- H 皆通過(H-V),A~G 群組冒煙測試無回歸。
This commit is contained in:
@@ -7,6 +7,7 @@ import { EmotionModule } from "./emotion/emotion.module.js";
|
||||
import { RelationshipModule } from "./relationship/relationship.module.js";
|
||||
import { LlmModule } from "./llm/llm.module.js";
|
||||
import { PersonalityModule } from "./personality/personality.module.js";
|
||||
import { ChatModule } from "./chat/chat.module.js";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -17,6 +18,7 @@ import { PersonalityModule } from "./personality/personality.module.js";
|
||||
RelationshipModule,
|
||||
LlmModule,
|
||||
PersonalityModule,
|
||||
ChatModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Body, Controller, Param, Post } from "@nestjs/common";
|
||||
import { DialogueService } from "../llm/dialogue.service.js";
|
||||
import { MemoryConsolidationService } from "../memory/memory-consolidation.service.js";
|
||||
|
||||
interface ChatMessageBody {
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface EndSessionBody {
|
||||
characterId: string;
|
||||
}
|
||||
|
||||
// H-1 對話 API:POST /chat/:characterId 走完整管線,POST /session/:sessionId/end 觸發睡眠固化。
|
||||
// 這是本清單第一個對外的正式使用者端點,先前 F 群組的 /dialogue/* 僅供工程內部驗證。
|
||||
@Controller()
|
||||
export class ChatController {
|
||||
constructor(
|
||||
private readonly dialogue: DialogueService,
|
||||
private readonly consolidation: MemoryConsolidationService,
|
||||
) {}
|
||||
|
||||
@Post("chat/:characterId")
|
||||
async chat(@Param("characterId") characterId: string, @Body() body: ChatMessageBody) {
|
||||
return this.dialogue.handleMessage(characterId, body.userId, body.sessionId, body.text);
|
||||
}
|
||||
|
||||
@Post("session/:sessionId/end")
|
||||
async endSession(@Param("sessionId") sessionId: string, @Body() body: EndSessionBody) {
|
||||
await this.consolidation.consolidate(body.characterId, sessionId);
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { LlmModule } from "../llm/llm.module.js";
|
||||
import { MemoryModule } from "../memory/memory.module.js";
|
||||
import { ChatController } from "./chat.controller.js";
|
||||
|
||||
@Module({
|
||||
imports: [LlmModule, MemoryModule],
|
||||
controllers: [ChatController],
|
||||
})
|
||||
export class ChatModule {}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
import { WorkingMemoryService } from "../memory/working-memory.service.js";
|
||||
import { EmotionService } from "../emotion/emotion.service.js";
|
||||
import { RelationshipService } from "../relationship/relationship.service.js";
|
||||
import { LLM_PROVIDER, type LLMProvider } from "./llm-provider.js";
|
||||
import { ContextAssemblerService } from "./context-assembler.service.js";
|
||||
import { OutputFilterService } from "./output-filter.service.js";
|
||||
@@ -35,6 +37,8 @@ export class DialogueService {
|
||||
private readonly contextAssembler: ContextAssemblerService,
|
||||
private readonly outputFilter: OutputFilterService,
|
||||
private readonly workingMemory: WorkingMemoryService,
|
||||
private readonly emotion: EmotionService,
|
||||
private readonly relationship: RelationshipService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
@@ -48,6 +52,10 @@ export class DialogueService {
|
||||
const now = options.now ?? new Date();
|
||||
this.workingMemory.append(sessionId, { role: "user", content: userInput, timestamp: now });
|
||||
|
||||
// H-1:每輪對話先標記情緒、累積日常互動的親密度,讓後續組裝的上下文反映本輪的變化。
|
||||
await this.emotion.processInput(characterId, userInput, { now });
|
||||
await this.relationship.recordInteraction(characterId, userId, now);
|
||||
|
||||
const { isFastChannel, reason } = this.fastChannel.detect(userInput);
|
||||
|
||||
const context = await this.contextAssembler.assemble(characterId, userId, sessionId, userInput, {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AppModule } from "./app.module.js";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, { logger: false });
|
||||
app.enableCors({ origin: process.env.WEB_ORIGIN ?? "http://localhost:3100" });
|
||||
const port = Number(process.env.PORT ?? 3001);
|
||||
await app.listen(port);
|
||||
log("啟動", "INF", `api 服務已啟動,監聽埠 ${port}`);
|
||||
|
||||
Reference in New Issue
Block a user