package emotion import "strings" // Reaction is one turn's emotional reading of the user's message (design/01 §2: // the persona config carries the emotion state-machine parameters and the // affection curve; the machine itself only knows states and legal moves). // // design/01 fixes the model (states + transitions + decay + affection curve) but // does not enumerate the trigger vocabulary, so the minimal reading implemented // here is: a clearly angry turn, a clearly down turn, a strongly positive turn // and a mildly positive turn, each moving the matching state and stepping the // affection curve once. A turn with no cue carries no signal, which leaves the // emotion to decay toward calm instead of jumping (see Observer.Observe). type Reaction struct { // State is the emotion state this turn moves the machine toward. It is a // target, not a forced value: the machine walks its configured transition // table toward it (Machine.Move), so a persona's own table always wins. State EmotionState // AffectionDelta is the signed number of affection *steps* for this turn // (+1 warm, -1 cold, 0 neutral). The concrete magnitude is the persona's // configured affection curve step, applied by the caller. AffectionDelta int // Signal reports whether the turn carried any emotional cue at all. Signal bool } // React classifies one user turn into the persona's emotional response. Anger // and negative cues win over positive ones, so a mixed message ("不开心" also // contains "开心") never reads as positive. func React(text string) Reaction { t := strings.ToLower(text) switch { case t == "": return Reaction{} case containsCue(t, angerCues()): return Reaction{State: Angry, AffectionDelta: affectionDown, Signal: true} case containsCue(t, negativeCues()): return Reaction{State: Sad, AffectionDelta: affectionDown, Signal: true} case containsCue(t, strongPositiveCues()): return Reaction{State: Excited, AffectionDelta: affectionUp, Signal: true} case containsCue(t, positiveCues()): return Reaction{State: Happy, AffectionDelta: affectionUp, Signal: true} default: return Reaction{} } } // Affection step signs. The magnitude comes from the persona's own curve. const ( affectionUp = 1 affectionDown = -1 ) // containsCue reports whether text contains any of the cues. func containsCue(text string, cues []string) bool { for _, c := range cues { if strings.Contains(text, c) { return true } } return false } // angerCues are the protest/hostility cues that move the persona to angry. They // are checked first so an angry turn is never read as merely sad or happy. func angerCues() []string { return []string{ "讨厌你", "烦你", "恨你", "别烦", "好烦", "烦死", "烦人", "生气", "气死", "气愤", "滚开", "走开", "闭嘴", "不理你", "分手", "凶我", "骂你", "angry", "annoying", "annoyed", "shut up", "hate you", "mad at", } } // negativeCues are the down/low cues that move the persona to sad — including // tiredness, which in the five built-in states reads as the gentle/caring lane. func negativeCues() []string { return []string{ "不开心", "很难过", "难过", "伤心", "委屈", "失望", "沮丧", "郁闷", "崩溃", "心累", "累了", "好累", "疲惫", "困了", "想哭", "呜呜", "555", "糟糕", "焦虑", "孤独", "孤单", "寂寞", "失眠", "睡不着", "被冷落", "sad", "tired", "upset", "exhausted", "lonely", "depressed", "feel bad", } } // strongPositiveCues are the high-arousal warm cues that move the persona to // excited (a stronger state than happy). func strongPositiveCues() []string { return []string{ "太开心", "好开心", "最喜欢", "太喜欢", "爱死", "太棒", "好耶", "好激动", "兴奋", "幸福死了", "amazing", "awesome", "so happy", "love you", } } // positiveCues are the everyday warm cues that move the persona to happy. func positiveCues() []string { return []string{ "开心", "高兴", "喜欢", "爱你", "想你", "抱抱", "么么", "亲亲", "摸摸", "谢谢", "辛苦", "哈哈", "嘿嘿", "嘻嘻", "真好", "厉害", "好棒", "期待", "满足", "幸福", "好呀", "好哒", "早安", "早上好", "中午好", "下午好", "晚上好", "晚安", "nice", "great", "thanks", "thank you", "haha", "hug", "miss you", "good morning", "good night", "good evening", } }