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:
Jeffery
2026-08-13 12:12:49 +08:00
parent 503024c92f
commit 9e5036f0a0
16 changed files with 550 additions and 29 deletions
+46
View File
@@ -0,0 +1,46 @@
interface ChatBubbleProps {
role: "user" | "character";
text: string;
}
// H-3 動作描寫標記渲染:把 *…* 標記以暮空紫小字呈現,與台詞區分。
function renderContent(text: string) {
const parts = text.split(/(*[^*]+*)/g).filter(Boolean);
return parts.map((part, index) => {
const match = part.match(/^*([^*]+)*$/);
if (match) {
return (
<span key={index} className="text-xs" style={{ color: "var(--violet)" }}>
{match[1]}
</span>
);
}
return <span key={index}>{part}</span>;
});
}
export function ChatBubble({ role, text }: ChatBubbleProps) {
const isUser = role === "user";
return (
<div
className="max-w-[78%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed"
style={
isUser
? {
alignSelf: "flex-end",
background: "var(--accent-bright)",
color: "#fff",
borderBottomRightRadius: 4,
}
: {
alignSelf: "flex-start",
background: "var(--bg-soft)",
border: "1px solid var(--line)",
borderBottomLeftRadius: 4,
}
}
>
{renderContent(text)}
</div>
);
}