深秋的黄昏往往来得特别快。当电脑屏幕前的最后一缕夕阳褪去,周二的疲惫和琐碎的工作压力常常会毫无预兆地涌上心头。在这个信息过载的时代,我们在社交网络上习惯了展示坚强与光鲜,而那些细微的失落、孤独或焦虑,却往往找不到一个可以安全安放的出口。
市面上的大多数心理健康应用或 AI 聊天机器人,都要求用户将私密的情感日记上传至远端大模型云端进行分析。但在“秋日手账杂货铺”的设计哲学里,私密的脆弱应当受到最严格的尊重与守护。用户的心事绝不应该变成任何云端大模型的训练语料。
本文将分享我们在前端构建的“落叶倾听罐”(Autumn Leaf Listening Jar)陪伴交互系统。整个系统完全运行在用户的本地浏览器中,基于轻量级词性分词、情绪能量加权算法与微行动推荐引擎。用户在银杏叶便签上写下心事,叶片伴随着重力与物理阻尼旋转飘落至半透明玻璃罐底,并在本地生成一段不讲宏大道理、不评判是非、只温柔接纳与共鸣的治愈反馈。
交互设计:让心事像落叶一样被温柔收纳
“落叶倾听罐”的交互隐喻来源于自然界中的落叶归根:秋风吹落黄叶,并不是凋零与终结,而是大自然安静沉淀与休养生息的必经过程。
- 银杏叶便签书写:主界面呈现一片带有温润叶脉纹理的银杏叶输入框,用户可以随时随地写下三两句碎碎念;
- 重力飘落与物理阻尼:点击“倾听”按钮,叶片脱离输入区,在 Canvas 画布中以自然的流体阻尼和微风扰动轨迹,缓缓飘转并沉入屏幕下方的玻璃罐中;
- 纯客户端情绪能量评估:在叶片飘落的 1.5 秒动画时间内,浏览器后台利用离线词库进行情感极性、否定词反转与程度副词加权计算,评估当下的情绪能量状态;
- 三层递进的暖心共情生成:算法不输出冰冷的分数,而是根据情绪维度(如“疲倦耗尽”、“迷茫焦虑”、“自我怀疑”),自适应拼接出“深度共情 + 自然隐喻 + 0门槛微小舒缓行动”三层温和陪伴文字。
核心算法:轻量情感极性与滑动窗口加权
在纯前端离线环境中,我们不需要加载动辄几十兆的大型自然语言处理模型。通过构建一个轻量级情绪特征库(仅占用不到 15 KB),结合否定词反转逻辑与程度副词滑动窗口(Sliding Window),就能以极高精度捕捉到中文短文本中的情绪偏向。
// services/emotion-evaluator.ts export type EmotionType = 'fatigue' | 'anxiety' | 'loneliness' | 'self_doubt' | 'neutral' | 'peace'; interface EmotionResult { primaryEmotion: EmotionType; energyLevel: number; // 0 ~ 100,越低代表能量越枯竭 keywords: string[]; } /** * 离线情绪特征字典与权重配置 */ const EMOTION_DICT: Record<EmotionType, { words: string[]; baseEnergy: number }> = { fatigue: { words: ['累', '好累', '疲惫', '耗尽', '困', '撑不住', '没力气', '不想动', '透支', '好烦'], baseEnergy: 30 }, anxiety: { words: ['慌', '焦虑', '怕', '来不及', '压力', '担心', '睡不着', '窒息', '催促', '紧绷'], baseEnergy: 35 }, loneliness: { words: ['一个人', '孤单', '寂寞', '没人懂', '空落落', '难过', '想哭', '失落', '疏离'], baseEnergy: 40 }, self_doubt: { words: ['搞砸了', '没用', '做不好', '自责', '笨', '拖延', '失败', '嫌弃自己', '差劲'], baseEnergy: 25 }, peace: { words: ['平静', '舒服', '释怀', '吹风', '喝茶', '看云', '发呆', '好转', '安静'], baseEnergy: 80 }, neutral: { words: [], baseEnergy: 60 } }; // 程度副词加权倍率 const INTENSIFIERS: Record<string, number> = { '特别': 1.6, '非常': 1.6, '太': 1.5, '真': 1.4, '好': 1.3, '极其': 1.8, '有点': 0.8, '稍微': 0.6 }; // 否定词集合 const NEGATIONS = new Set(['不', '没有', '别', '未', '并不', '不大']); export class LocalEmotionEvaluator { /** * 纯前端短文本分词与情绪特征提取 */ public static evaluate(text: string): EmotionResult { if (!text || text.trim().length === 0) { return { primaryEmotion: 'neutral', energyLevel: 60, keywords: [] }; } const cleanText = text.toLowerCase(); const scores: Record<EmotionType, number> = { fatigue: 0, anxiety: 0, loneliness: 0, self_doubt: 0, peace: 0, neutral: 0 }; const matchedKeywords: string[] = []; // 遍历情绪类别进行模式匹配与滑动窗口扫描 for (const [emotion, config] of Object.entries(EMOTION_DICT) as [EmotionType, typeof EMOTION_DICT[EmotionType]][]) { for (const word of config.words) { let searchIndex = 0; while ((searchIndex = cleanText.indexOf(word, searchIndex)) !== -1) { matchedKeywords.push(word); // 向前探索 4 个字符的滑动窗口,检测程度副词与否定词 const windowStart = Math.max(0, searchIndex - 4); const prefix = cleanText.substring(windowStart, searchIndex); let multiplier = 1.0; let isNegated = false; for (const neg of NEGATIONS) { if (prefix.includes(neg)) { isNegated = true; break; } } for (const [intensifier, weight] of Object.entries(INTENSIFIERS)) { if (prefix.includes(intensifier)) { multiplier = weight; break; } } // 核心评分累加:若有否定词,则抑制该负向得分 if (isNegated) { scores[emotion] -= 1.0 * multiplier; } else { scores[emotion] += 2.0 * multiplier; } searchIndex += word.length; } } } // 找出得分最高的情绪极性 let maxEmotion: EmotionType = 'neutral'; let maxScore = 0; for (const [emotion, score] of Object.entries(scores) as [EmotionType, number][]) { if (score > maxScore) { maxScore = score; maxEmotion = emotion; } } // 计算综合能量值 const baseEnergy = EMOTION_DICT[maxEmotion].baseEnergy; const penalty = Math.min(25, maxScore * 4); const calculatedEnergy = Math.max(10, Math.min(100, baseEnergy - penalty)); return { primaryEmotion: maxEmotion, energyLevel: calculatedEnergy, keywords: Array.from(new Set(matchedKeywords)) }; } }暖心陪伴反馈生成引擎
当情绪被准确识别后,传统算法往往会给出教科书式的说教:“建议你多运动、早点睡觉”。这种回答不仅毫无温度,反而常常增加用户的心理负担。
听汐的陪伴设计采用了**“心理学无条件积极接纳”**原则:首先确认对方情绪的合理性,接着用秋日生活细节化解沉重感,最后提出一项耗费精力不超过 30 秒的极简微动作。
// services/companion-generator.ts import { EmotionType } from './emotion-evaluator'; interface WarmResponse { empathy: string; // 确认与接纳 metaphor: string; // 自然意象 microAction: string; // 零门槛微动作 } export class WarmCompanionEngine { private static RESPONSE_TEMPLATES: Record<EmotionType, WarmResponse[]> = { fatigue: [ { empathy: '今天真的辛苦啦,能坚持忙完这些琐碎的事情,你已经非常了不起了。', metaphor: '树叶在秋天落下来,不是因为放弃,而是大树想先睡个好觉。', microAction: '现在把双肩彻底沉下去,缓缓呼出一大口气,去给杯子里添半杯温水吧。' }, { empathy: '感觉到你身上的电量几乎要见底了呢,这时候不用逼自己继续表现得积极。', metaphor: '秋天的微风吹得慢悠悠的,生活本来就不需要每天都开足马力赶路。', microAction: '闭上眼睛三十秒,什么都不去想,只听一听周围细微的环境白噪音。' } ], anxiety: [ { empathy: '心里乱糟糟的时候,哪怕是一件小事都会让人觉得像巨石一样沉重,这种感觉我很理解。', metaphor: '你看落叶飘进玻璃罐的时候,从来不会急着找方向,随风转一转,它总能安稳落到底部的。', microAction: '找一张空白草稿纸,随手画三个圈圈,把脑子里最烦的那件事缩写写在圈里,然后折起来放在一边。' } ], loneliness: [ { empathy: '有时候四周人来人往,心里却像隔着一层玻璃,这种空落落的感觉不需要感到不好意思。', metaphor: '秋天的银杏树也是一叶一叶脱落的,但每一片树叶在地上相遇时,都在默默给泥土提供温度。', microAction: '把双手合十搓热,轻轻敷在自己的手腕或后颈上,感受一下掌心暖暖的温度。' } ], self_doubt: [ { empathy: '不要急着苛责自己,哪怕今天的事情没有做到完美,也不代表你不够优秀。', metaphor: '手账本上即使写错了一个字、画歪了一条线,贴上一张小贴纸,依然是一副独一无二的秋日画卷。', microAction: '摸摸自己的头,对自己轻声说一句:“没关系的,今天我已经尽力了”。' } ], peace: [ { empathy: '能感受到你文字里那份淡淡的澄澈与安详,真替你感到开心。', metaphor: '就像秋日午后的阳光晒在木桌上,整个人都变得松软温和起来。', microAction: '把这一刻的平静用文字珍藏在手账里,日后重温时它就是你的能量补给站。' } ], neutral: [ { empathy: '谢谢你愿意把当下的心事放进倾听罐,每一个平常的日子都值得被安静倾听。', metaphor: '落叶安安静静地躺在罐底,默默守护着你的每一个日常小瞬间。', microAction: '伸一个舒服的懒腰,转动一下微酸的脖子,窗外的云正在慢慢飘走呢。' } ] }; /** * 根据情绪类型与时间段动态生成陪伴话语 */ public static generate(emotion: EmotionType): WarmResponse { const list = this.RESPONSE_TEMPLATES[emotion] || this.RESPONSE_TEMPLATES.neutral; const randomIndex = Math.floor(Math.random() * list.length); return list[randomIndex]; } }物理体验:落叶重力飘落动画与清空粉碎机制
为了让文字的倾诉更具有仪式感,我们使用 CSS 自定义变换与轻量动画关键帧,模拟落叶飘落进玻璃罐的物理质感:
/* 落叶倾诉动画 */ @keyframes leafFloatDown { 0% { transform: translateY(-80px) rotate(0deg) scale(1); opacity: 0; } 20% { opacity: 1; transform: translateY(20px) rotate(-15deg) scale(0.98); } 50% { transform: translateY(120px) rotate(18deg) scale(0.95); } 80% { transform: translateY(220px) rotate(-8deg) scale(0.92); } 100% { transform: translateY(300px) rotate(5deg) scale(0.9); opacity: 0.85; } } .listening-leaf-falling { animation: leafFloatDown 2.2s cubic-bezier(0.25, 1, 0.5, 1) forwards; pointer-events: none; }而在隐私控制上,系统提供了**“随风化作泥”**的一键清空按钮。点击后,罐中所有存储的文字记录立即从浏览器的 IndexedDB 与内存中执行零覆盖抹除,并伴随细碎的金色粒子消散特效。心事被倾听了、被接纳了,随后便重归泥土,绝不在客户端留下任何未加密的隐患。
听汐的陪伴手记:软件也可以有体温
在写这段纯前端情绪算法时,我一直在反复思考一个问题:我们究竟需要什么样的技术?
是更复杂的神经网络,是能滔滔不绝生成长篇大论的庞大模型,还是一个懂得适时沉默、懂得轻柔接纳微小情绪的温暖工具?
这个“落叶倾听罐”没有调用任何外部 API,代码体积甚至比不上一张高清图片。但当深夜里一位手账用户敲下“今天真的好累”,看着一片小小的金黄落叶缓缓沉入玻璃罐,读到屏幕上那句“现在把双肩沉下去,去喝半杯温水吧”,技术便超越了冷冰冰的代码逻辑,化作了一缕实实在在的秋日暖阳。