feat(ai-core): mood state machine and affection level
Cyrene CI / backend (push) Has been cancelled
Cyrene CI / frontend (push) Has been cancelled

- new emotion package: rule-based sentiment (positive/negative lexicon)
  drives mood transitions (开心/平静/难过) with 30min inactivity decay;
  affection 0-100 grows with positive interaction, with 初识→熟悉→信赖→
  羁绊 tiers
- pipeline records each user message and injects a mood line into the
  system prompt so the persona's tone follows her mood
- mood changes broadcast over WS; frontend shows a mood badge with
  affection tooltip; also handles the 'reminder' message type from the
  reminder scheduler
- tests: sentiment scoring, mood shift, affection bounds, prompt line,
  onChange callback
This commit is contained in:
Aska committed 2026-08-21 18:13:23 +08:00
1 parent 26d65dd6bb
commit b8fb7feb86
8 files changed
+309 -3

No files matched your search

@@ -4,9 +4,14 @@ import { useWebSocket } from '../../hooks/useWebSocket'
import MessageList from './MessageList'
import ChatInput from './ChatInput'
function moodEmoji(mood: string): string {
const map: Record<string, string> = { 开心: '😊', 平静: '😌', 难过: '😢', 害羞: '☺️', 感动: '🥹' }
return map[mood] || '😌'
}
export default function ChatContainer() {
const { send } = useWebSocket()
const { messages, isStreaming, currentSessionId, inputText, setInput, addMessage, setStreaming } = useChatStore()
const { messages, isStreaming, currentSessionId, inputText, mood, setInput, addMessage, setStreaming } = useChatStore()
const bottomRef = useRef<HTMLDivElement>(null)
useEffect(() => {
@@ -34,7 +39,16 @@ export default function ChatContainer() {
}
return (
<div className="flex flex-col h-full">
<div className="flex flex-col h-full relative">
{/* Mood badge */}
{mood && (
<div className="absolute top-3 right-4 z-10 px-3 py-1 rounded-full text-xs border"
style={{ background: 'var(--mx-surface)', borderColor: 'var(--mx-outline)', color: 'var(--mx-text-dim)' }}
title={`好感度 ${mood.affection}/100`}>
{moodEmoji(mood.mood)} {mood.mood}
</div>
)}
{/* Chat area */}
<div className="flex-1 overflow-auto">
<div className="max-w-2xl mx-auto px-4 py-6">