From 4e5edda6c68384af690459e68e404a8e7f315ce2 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Mar 2026 21:05:55 +0000 Subject: [PATCH] feat(thought): add mood-contextual emoji picker Co-Authored-By: Claude Opus 4.6 --- server/src/llm/__tests__/thoughtEmoji.test.ts | 44 +++++++++++++++++++ server/src/llm/thoughtEmoji.ts | 24 ++++++++++ 2 files changed, 68 insertions(+) create mode 100644 server/src/llm/__tests__/thoughtEmoji.test.ts create mode 100644 server/src/llm/thoughtEmoji.ts diff --git a/server/src/llm/__tests__/thoughtEmoji.test.ts b/server/src/llm/__tests__/thoughtEmoji.test.ts new file mode 100644 index 0000000..5225755 --- /dev/null +++ b/server/src/llm/__tests__/thoughtEmoji.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { pickThoughtEmoji, type ThoughtContext } from '../thoughtEmoji.js'; + +describe('pickThoughtEmoji', () => { + it('returns hungry emoji when hunger is critical', () => { + expect(pickThoughtEmoji({ trigger: 'need_critical', hunger: 15, energy: 80 })).toBe('🍖'); + }); + + it('returns tired emoji when energy is critical', () => { + expect(pickThoughtEmoji({ trigger: 'need_critical', hunger: 80, energy: 10 })).toBe('😴'); + }); + + it('returns hungry emoji when both are critical (hunger wins)', () => { + expect(pickThoughtEmoji({ trigger: 'need_critical', hunger: 10, energy: 10 })).toBe('🍖'); + }); + + it('returns happy emoji for positive interaction', () => { + expect(pickThoughtEmoji({ trigger: 'post_interaction', interactionPositive: true })).toBe('😊'); + }); + + it('returns frustrated emoji for negative interaction', () => { + expect(pickThoughtEmoji({ trigger: 'post_interaction', interactionPositive: false })).toBe('😤'); + }); + + it('returns thinking emoji for idle trigger', () => { + expect(pickThoughtEmoji({ trigger: 'idle' })).toBe('🤔'); + }); + + it('returns thinking emoji for periodic trigger', () => { + expect(pickThoughtEmoji({ trigger: 'periodic' })).toBe('🤔'); + }); + + it('returns happy emoji for tier_change positive', () => { + expect(pickThoughtEmoji({ trigger: 'tier_change', interactionPositive: true })).toBe('😊'); + }); + + it('returns frustrated emoji for tier_change negative', () => { + expect(pickThoughtEmoji({ trigger: 'tier_change', interactionPositive: false })).toBe('😤'); + }); + + it('returns fallback for unknown trigger', () => { + expect(pickThoughtEmoji({ trigger: 'unknown' as any })).toBe('💭'); + }); +}); diff --git a/server/src/llm/thoughtEmoji.ts b/server/src/llm/thoughtEmoji.ts new file mode 100644 index 0000000..5455e03 --- /dev/null +++ b/server/src/llm/thoughtEmoji.ts @@ -0,0 +1,24 @@ +export interface ThoughtContext { + trigger: 'need_critical' | 'post_interaction' | 'tier_change' | 'idle' | 'periodic'; + hunger?: number; + energy?: number; + interactionPositive?: boolean; +} + +export function pickThoughtEmoji(ctx: ThoughtContext): string { + switch (ctx.trigger) { + case 'need_critical': { + const hunger = ctx.hunger ?? 100; + const energy = ctx.energy ?? 100; + return hunger <= energy ? '🍖' : '😴'; + } + case 'post_interaction': + case 'tier_change': + return ctx.interactionPositive ? '😊' : '😤'; + case 'idle': + case 'periodic': + return '🤔'; + default: + return '💭'; + } +}