dsh-dafeiyu-emotion 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Added `empathyStyle` with four reply strategies: gentle, direct, humorous and concise. The choice is persisted and injected into the emotion-aware prompt.
6
+ - Added an in-composer emotion timeline for analyses with multiple segments; hovering a segment shows its text, emotion and confidence.
7
+ - Added privacy/runtime status to the settings panel and `/health`: last emotion, last audio size, model load time, worker PID and last temp cleanup time.
8
+ - Added local-only `GET /plugins/dsh-dafeiyu-emotion/context` so other plugins can read the latest emotion context.
9
+ - Settings now resolve the real `entry.ns` from `settings.describe()` instead of assuming the npm package name.
10
+ - Settings writes now carry the settings revision and return 409 on a concurrent-window conflict.
11
+ - Removed the hard `settings` inject; settings are resolved dynamically after the host is up.
12
+ - Moved Python discovery to the first worker start, removing the synchronous filesystem stat from `apply`.
13
+ - An explicit empty `backendRoot` now clears the environment fallback, so the UI can disable the enhanced backend.
14
+ - Shortcut resolution now prefers the most recently used fish-ear instead of the last mounted one.
15
+ - Narrow sidebar fields wrap instead of squeezing controls.
16
+ - Removed test scripts from the published npm tarball.
17
+
18
+ ## 0.2.10
19
+
20
+ - Removed `settings` from the hard inject list. A hard `settings` dependency can deadlock host startup while the settings service is still building plugin `Config` projections.
21
+ - Settings are now resolved with `ctx.inject(['settings'], ...)` after the host is up; `/config` is read-only until then and becomes writable when the service arrives.
22
+ - Added a regression assertion that `settings` is not a hard dependency.
23
+
24
+ ## 0.2.9
25
+
26
+ - Fixed the host startup timeout: `apply` synchronously called `settings.describe()` while the host was still mounting plugins, which could deadlock startup for 60 seconds.
27
+ - `apply` now uses the patch config only; settings are read/written through `/config` after the host is up.
28
+ - Added cycle/depth guards to `plainConfig()` so an unexpected volatile-ref cycle cannot hang startup or a settings request.
29
+ - Added a regression test where `settings.describe()` throws if called during `apply`.
30
+
3
31
  ## 0.2.8
4
32
 
5
33
  - Fixed `backendRoot` showing as `[object Object]` on the settings page. DSH projects volatile `Config` fields as `{ get() }` refs; `readSettings()` and `resolveOptions()` now unwrap them before path/string/number handling.
package/README.md CHANGED
@@ -72,6 +72,10 @@
72
72
  - ⌨️ DSH 原生语音快捷键,可在快捷键设置中自行改键;
73
73
  - 🎛️ DSH 设置页里的完整设置卡片:启用、语言、增强后端、超时和录音上限;
74
74
  - 🧭 安装 Better Sidebar 后,可把「情绪耳朵」固定到右侧边栏;
75
+ - 🎭 共情策略四档:温柔、直接、幽默、简洁;
76
+ - 📈 多段语音的情绪时间线,可悬停查看每段文本、情绪和置信度;
77
+ - 🔐 设置页内的隐私与运行状态:最近音频大小、模型加载、worker PID、临时文件清理;
78
+ - 🔌 本机 `/context` 接口,其他插件可以读取最近一次情绪上下文;
75
79
  - 🐋 大肥鱼主题的“鱼耳”入口,和普通对话共用同一个 composer;
76
80
  - 🧠 情绪标签:喜悦 / 低落 / 生气 / 紧张 / 惊讶 / 厌烦 / 平静;
77
81
  - 📈 额外给出 confidence、valence、arousal 和语气摘要;
@@ -111,9 +115,9 @@ dsh plugin --profile desktop add github:LiDu-china/dsh-dafeiyu-emotion
111
115
  ### 通过 Release tarball
112
116
 
113
117
  ```bash
114
- curl -L -o dsh-dafeiyu-emotion-0.2.8.tgz \
115
- https://github.com/LiDu-china/dsh-dafeiyu-emotion/releases/download/v0.2.8/dsh-dafeiyu-emotion-0.2.8.tgz
116
- dsh plugin --profile desktop add "D:/path/to/dsh-dafeiyu-emotion-0.2.8.tgz"
118
+ curl -L -o dsh-dafeiyu-emotion-0.3.0.tgz \
119
+ https://github.com/LiDu-china/dsh-dafeiyu-emotion/releases/download/v0.3.0/dsh-dafeiyu-emotion-0.3.0.tgz
120
+ dsh plugin --profile desktop add "D:/path/to/dsh-dafeiyu-emotion-0.3.0.tgz"
117
121
  ```
118
122
 
119
123
  > DSH Desktop 的 profile 由 Electron 应用管理;命令行安装请使用桌面应用自带的 `dsh` 命令。
@@ -130,6 +134,7 @@ dsh plugin --profile desktop add "D:/path/to/dsh-dafeiyu-emotion-0.2.8.tgz"
130
134
 
131
135
  - 启用 / 停用情绪耳朵;
132
136
  - 识别语言:中文、English 或自动;
137
+ - 共情策略:温柔、直接、幽默、简洁;
133
138
  - VoiceSense 增强后端目录与 Python 路径;
134
139
  - 单次录音上限、后端启动超时、单次分析超时。
135
140
 
@@ -184,6 +189,7 @@ dsh plugin --profile desktop add "D:/path/to/dsh-dafeiyu-emotion-0.2.8.tgz"
184
189
  | `backendRoot` | 空 | VoiceSense 项目根目录;空则使用 DSH Voice Input 轻量模式 |
185
190
  | `pythonPath` | 空 | 自定义 Python;空则自动寻找 `.venv-ser` |
186
191
  | `language` | `zh` | 识别语言:`zh` / `en` / `auto` |
192
+ | `empathyStyle` | `gentle` | 共情策略:`gentle` / `direct` / `humorous` / `concise` |
187
193
  | `maxAudioBytes` | 12 MB | 单次录音大小上限 |
188
194
  | `startupTimeoutMs` | 180000 | 首次加载增强后端的最长等待 |
189
195
  | `requestTimeoutMs` | 180000 | 单次分析请求超时 |
package/cordis.patch.yml CHANGED
@@ -28,6 +28,7 @@
28
28
  backendRoot: ""
29
29
  pythonPath: ""
30
30
  language: zh
31
+ empathyStyle: gentle
31
32
  maxAudioBytes: 12582912
32
33
  startupTimeoutMs: 180000
33
34
  requestTimeoutMs: 180000
@@ -72,7 +72,7 @@ https://github.com/LiDu-china/dsh-dafeiyu-emotion/releases/download/v0.2.3/dsh-d
72
72
  dsh plugin --profile desktop add dsh-dafeiyu-emotion
73
73
  ```
74
74
 
75
- Published as `dsh-dafeiyu-emotion@0.2.8` on npm. The 1024Store entry refreshes its one-click install automatically. GitHub-source and Release-tarball installs remain available as fallbacks.
75
+ Published as `dsh-dafeiyu-emotion@0.3.0` on npm. The 1024Store entry refreshes its one-click install automatically. GitHub-source and Release-tarball installs remain available as fallbacks.
76
76
 
77
77
  ## Privacy
78
78
 
@@ -0,0 +1,20 @@
1
+ ## 0.2.10
2
+
3
+ 修复 Host 启动阶段的依赖死锁,并修正 volatile 配置解包。
4
+
5
+ - 移除 `settings` 硬依赖。改为 `ctx.inject(['settings'], ...)` 动态获取,避免 settings 服务尚未就绪时插件 fiber 一直 pending。
6
+ - `apply` 不再调用 `settings.describe()`,只在 `/config` 请求时读取运行时配置。
7
+ - `plainConfig()` 改为递归栈 + `finally` 清理,修复共享对象被误判为循环引用而丢失的问题。
8
+ - 新增共享引用、真实循环、`settings.describe()` 不应在启动阶段调用的回归测试。
9
+
10
+ ## 注意
11
+
12
+ 当前环境如果仍无法启动,请先确认 `magpie-mcp-git` / `magpie-mcp-anysearch` 没有阻塞 Host 启动。这两个 MCP 客户端在连接不可用时会等待重试,可能把 Host 启动拖过 60 秒。
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ dsh plugin --profile desktop add dsh-dafeiyu-emotion
18
+ ```
19
+
20
+ 升级后请完全退出并重新启动 the agent harness。
@@ -0,0 +1,17 @@
1
+ ## 0.2.9
2
+
3
+ 修复“the agent harness 打不开 / Host startup exceeded 60 seconds”。
4
+
5
+ - 根因:0.2.8 在 `apply` 期间同步调用 `settings.describe()`。此时 Host 还在挂载插件,遍历所有插件配置可能造成启动死锁,最终 60 秒超时。
6
+ - 0.2.9 的 `apply` 只读取 `cordis.patch.yml` 的 patch 配置,不再在启动事务里调用 `settings.describe()`。
7
+ - 设置页的读取/写入仍通过 `/plugins/dsh-dafeiyu-emotion/config`;`PATCH` 会更新共享运行时配置并调用 `settings.update()` 持久化。
8
+ - `plainConfig()` 增加循环引用和深度保护,避免异常 volatile ref 卡死启动或设置请求。
9
+ - 新增回归测试:`settings.describe()` 在 `apply` 期间如果被调用会直接抛错。
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ dsh plugin --profile desktop add dsh-dafeiyu-emotion
15
+ ```
16
+
17
+ 升级后请完全退出并重新启动 the agent harness。若之前因 0.2.8 无法启动,可先进入安全模式或临时移除插件,再安装 0.2.9。
@@ -0,0 +1,29 @@
1
+ ## 0.3.0
2
+
3
+ 这是情绪耳朵的功能版本,新增共情策略、情绪时间线、隐私状态和可订阅的情绪上下文。
4
+
5
+ ### 新增
6
+
7
+ - **共情策略**:温柔 / 直接 / 幽默 / 简洁,保存后会影响情绪化 prompt。
8
+ - **情绪时间线**:多段分析时在输入框内显示一条可悬停的情绪条,标出每段文本、情绪和置信度。
9
+ - **隐私与运行状态**:设置页显示最近情绪、最近音频大小、模型加载耗时、worker PID 和最近临时文件清理时间。
10
+ - **`/context` 接口**:本机 `GET /plugins/dsh-dafeiyu-emotion/context` 返回最近一次情绪上下文,供其他插件读取。
11
+
12
+ ### 修复
13
+
14
+ - settings 从 `describe()` 解析真实 `entry.ns`,不再硬编码包名。
15
+ - 保存设置时携带 revision,两个窗口同时修改会返回 409。
16
+ - `settings` 改为动态注入,避免 Host 启动阶段硬依赖。
17
+ - Python 查找延迟到首次启动 worker,`apply` 不再同步 stat 文件系统。
18
+ - 显式清空 `backendRoot` 可以覆盖环境变量。
19
+ - 快捷键优先使用最近操作过的鱼耳,而不是最后挂载的实例。
20
+ - 窄侧边栏字段自动换行。
21
+ - 发布包不再包含测试脚本。
22
+
23
+ ### 安装
24
+
25
+ ```bash
26
+ dsh plugin --profile desktop add dsh-dafeiyu-emotion
27
+ ```
28
+
29
+ 升级后请完全退出并重新启动 the agent harness。
package/lib/client.js CHANGED
@@ -37,19 +37,35 @@ window.__ModuleLoader__.load({
37
37
  }
38
38
 
39
39
  function registerVoiceShortcut(handler) {
40
- voiceShortcutHandlers.push(handler)
41
- return () => {
42
- const index = voiceShortcutHandlers.lastIndexOf(handler)
40
+ const entry = { ...handler, lastActiveAt: Date.now() }
41
+ voiceShortcutHandlers.push(entry)
42
+ const dispose = () => {
43
+ const index = voiceShortcutHandlers.lastIndexOf(entry)
43
44
  if (index >= 0) voiceShortcutHandlers.splice(index, 1)
44
45
  }
46
+ dispose.touch = () => {
47
+ entry.lastActiveAt = Date.now()
48
+ }
49
+ return dispose
45
50
  }
46
51
 
47
52
  function canUseVoiceShortcut(phase, locked) {
48
53
  return !locked && (phase === 'idle' || phase === 'recording' || phase === 'error')
49
54
  }
50
55
 
56
+ function activeVoiceShortcutHandler() {
57
+ const candidates = [...voiceShortcutHandlers].sort((left, right) => right.lastActiveAt - left.lastActiveAt)
58
+ return candidates.find((handler) => {
59
+ try {
60
+ return handler.canHandle?.() === true
61
+ } catch {
62
+ return false
63
+ }
64
+ }) || candidates[0]
65
+ }
66
+
51
67
  function canInvokeVoiceShortcut() {
52
- const handler = voiceShortcutHandlers.at(-1)
68
+ const handler = activeVoiceShortcutHandler()
53
69
  if (!handler) return false
54
70
  try {
55
71
  return handler.canHandle?.() === true
@@ -59,7 +75,7 @@ window.__ModuleLoader__.load({
59
75
  }
60
76
 
61
77
  function invokeVoiceShortcut() {
62
- const handler = voiceShortcutHandlers.at(-1)
78
+ const handler = activeVoiceShortcutHandler()
63
79
  if (!handler) return false
64
80
  try {
65
81
  return handler.run?.() === true
@@ -175,6 +191,23 @@ window.__ModuleLoader__.load({
175
191
  fontSize: 11,
176
192
  opacity: 0.6,
177
193
  },
194
+ timeline: {
195
+ display: 'inline-flex',
196
+ alignItems: 'center',
197
+ gap: 1,
198
+ width: 58,
199
+ height: 6,
200
+ flex: '0 0 auto',
201
+ borderRadius: 99,
202
+ overflow: 'hidden',
203
+ background: 'color-mix(in srgb, currentColor 10%, transparent)',
204
+ },
205
+ timelinePart: {
206
+ height: '100%',
207
+ minWidth: 2,
208
+ borderRadius: 99,
209
+ opacity: 0.9,
210
+ },
178
211
  }
179
212
 
180
213
  const T = {
@@ -599,15 +632,15 @@ window.__ModuleLoader__.load({
599
632
  function SettingsField({ icon, label, hint, children, wide = false }) {
600
633
  return h('div', {
601
634
  style: {
602
- display: 'grid',
603
- gridTemplateColumns: wide ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto',
635
+ display: 'flex',
636
+ flexWrap: 'wrap',
604
637
  gap: wide ? 7 : 12,
605
638
  alignItems: 'center',
606
639
  padding: '11px 0',
607
640
  borderBottom: `1px solid ${T.border}`,
608
641
  },
609
642
  },
610
- h('div', { style: { minWidth: 0 } },
643
+ h('div', { style: { minWidth: 0, flex: wide ? '1 1 100%' : '1 1 200px' } },
611
644
  h('div', {
612
645
  style: {
613
646
  display: 'flex',
@@ -789,6 +822,7 @@ window.__ModuleLoader__.load({
789
822
  || draft.backendRoot !== config.backendRoot
790
823
  || draft.pythonPath !== config.pythonPath
791
824
  || draft.language !== config.language
825
+ || draft.empathyStyle !== config.empathyStyle
792
826
  || draft.maxAudioBytes !== config.maxAudioBytes
793
827
  || draft.startupTimeoutMs !== config.startupTimeoutMs
794
828
  || draft.requestTimeoutMs !== config.requestTimeoutMs
@@ -797,19 +831,25 @@ window.__ModuleLoader__.load({
797
831
  const save = async () => {
798
832
  if (!config || !draft || saving) return
799
833
  const patch = {}
800
- for (const key of ['enabled', 'backendRoot', 'pythonPath', 'language', 'maxAudioBytes', 'startupTimeoutMs', 'requestTimeoutMs']) {
834
+ for (const key of ['enabled', 'backendRoot', 'pythonPath', 'language', 'empathyStyle', 'maxAudioBytes', 'startupTimeoutMs', 'requestTimeoutMs']) {
801
835
  if (draft[key] !== config[key]) patch[key] = draft[key]
802
836
  }
803
837
  if (Object.keys(patch).length === 0) return
804
838
  setSaving(true)
805
839
  setNotice(null)
806
840
  try {
841
+ const body = { ...patch }
842
+ if (Number.isInteger(config.revision)) body.expectedRevision = config.revision
807
843
  const response = await fetch(CONFIG_ENDPOINT, {
808
844
  method: 'PATCH',
809
845
  headers: { 'content-type': 'application/json' },
810
- body: JSON.stringify(patch),
846
+ body: JSON.stringify(body),
811
847
  })
812
848
  const payload = await response.json().catch(() => ({}))
849
+ if (response.status === 409) {
850
+ setNotice({ kind: 'error', text: '设置已被其他窗口修改,请重新读取后再保存' })
851
+ return
852
+ }
813
853
  if (!response.ok) throw new Error(payload.error || `保存失败(${response.status})`)
814
854
  setConfig(payload)
815
855
  setDraft(payload)
@@ -942,6 +982,18 @@ window.__ModuleLoader__.load({
942
982
  cursor: dirty && !saving ? 'pointer' : 'not-allowed',
943
983
  opacity: dirty && !saving ? 1 : 0.45,
944
984
  }
985
+ const stat = (label, value) => h('div', { style: { minWidth: 0 } },
986
+ h('div', { style: { color: T.faint, fontSize: 10.5, lineHeight: 1.35 } }, label),
987
+ h('div', {
988
+ style: {
989
+ marginTop: 2,
990
+ color: T.text,
991
+ fontSize: 12,
992
+ lineHeight: 1.4,
993
+ wordBreak: 'break-word',
994
+ },
995
+ }, value),
996
+ )
945
997
 
946
998
  return h(Root, {
947
999
  'data-testid': 'dsh-dafeiyu-emotion-settings',
@@ -1166,6 +1218,22 @@ window.__ModuleLoader__.load({
1166
1218
  ],
1167
1219
  onChange: (value) => update('language', value),
1168
1220
  })),
1221
+ h(SettingsField, {
1222
+ icon: 'spark',
1223
+ label: '共情策略',
1224
+ hint: '决定大肥鱼先怎么接住情绪,再回答问题。',
1225
+ }, h(SettingsSegmented, {
1226
+ value: draft.empathyStyle || 'gentle',
1227
+ label: '共情策略',
1228
+ disabled,
1229
+ options: [
1230
+ { value: 'gentle', label: '温柔' },
1231
+ { value: 'direct', label: '直接' },
1232
+ { value: 'humorous', label: '幽默' },
1233
+ { value: 'concise', label: '简洁' },
1234
+ ],
1235
+ onChange: (value) => update('empathyStyle', value),
1236
+ })),
1169
1237
  ),
1170
1238
  h(SettingsSection, { title: '增强后端', hint: '可选' },
1171
1239
  h(SettingsField, {
@@ -1203,6 +1271,36 @@ window.__ModuleLoader__.load({
1203
1271
  style: { ...inputStyle, width: '100%' },
1204
1272
  })),
1205
1273
  ),
1274
+ h(SettingsSection, { title: '隐私与运行状态' },
1275
+ h('div', {
1276
+ style: {
1277
+ display: 'grid',
1278
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
1279
+ gap: '10px 14px',
1280
+ padding: '6px 0 2px',
1281
+ },
1282
+ },
1283
+ stat('最近情绪', health?.lastAnalysis
1284
+ ? `${health.lastAnalysis.emotionZh || '—'} · ${Math.round((Number(health.lastAnalysis.confidence) || 0) * 100)}%`
1285
+ : '还没有分析记录'),
1286
+ stat('最近音频', health?.privacy?.lastAudioBytes
1287
+ ? `${Math.round(health.privacy.lastAudioBytes / 1024)} KB`
1288
+ : '—'),
1289
+ stat('模型加载', health?.privacy?.modelLoadMs != null
1290
+ ? `${health.privacy.modelLoadMs} ms`
1291
+ : '—'),
1292
+ stat('worker PID', health?.privacy?.workerPid ?? '—'),
1293
+ stat('最近清理', health?.privacy?.lastCleanupAt
1294
+ ? new Date(health.privacy.lastCleanupAt).toLocaleTimeString()
1295
+ : '—'),
1296
+ stat('共情策略', ({
1297
+ gentle: '温柔',
1298
+ direct: '直接',
1299
+ humorous: '幽默',
1300
+ concise: '简洁',
1301
+ })[draft.empathyStyle] || '温柔'),
1302
+ ),
1303
+ ),
1206
1304
  h('details', {
1207
1305
  onToggle: (event) => {
1208
1306
  const marker = event.currentTarget.querySelector('[data-chevron]')
@@ -1359,6 +1457,7 @@ window.__ModuleLoader__.load({
1359
1457
  const generation = useRef(0)
1360
1458
  const levelsRef = useRef([])
1361
1459
  const shortcutAction = useRef(null)
1460
+ const shortcutRegistration = useRef(null)
1362
1461
  const deliveryGate = useRef(null)
1363
1462
  if (!deliveryGate.current) deliveryGate.current = createDeliveryGate()
1364
1463
  const useInputSnapshot = typeof useInput === 'function' ? useInput : useNoInput
@@ -1619,10 +1718,21 @@ window.__ModuleLoader__.load({
1619
1718
  }
1620
1719
  }, [locked, phase, sessionId, pluginEnabled])
1621
1720
 
1622
- useEffect(() => registerVoiceShortcut({
1623
- canHandle: () => shortcutAction.current?.canHandle?.() === true,
1624
- run: () => shortcutAction.current?.run?.() === true,
1625
- }), [])
1721
+ useEffect(() => {
1722
+ const registration = registerVoiceShortcut({
1723
+ canHandle: () => shortcutAction.current?.canHandle?.() === true,
1724
+ run: () => shortcutAction.current?.run?.() === true,
1725
+ })
1726
+ shortcutRegistration.current = registration
1727
+ return () => {
1728
+ registration()
1729
+ if (shortcutRegistration.current === registration) shortcutRegistration.current = null
1730
+ }
1731
+ }, [])
1732
+
1733
+ const touchShortcut = () => {
1734
+ shortcutRegistration.current?.touch?.()
1735
+ }
1626
1736
 
1627
1737
  useEffect(() => {
1628
1738
  let active = true
@@ -1682,6 +1792,7 @@ window.__ModuleLoader__.load({
1682
1792
  event.preventDefault()
1683
1793
  event.currentTarget.style.transform = 'translateY(0)'
1684
1794
  },
1795
+ onFocus: touchShortcut,
1685
1796
  onClick: () => void start(),
1686
1797
  },
1687
1798
  h('span', { style: { display: 'inline-flex', color: '#3e7cb1' } }, h(WhaleMark, { size: 16 })),
@@ -1692,7 +1803,12 @@ window.__ModuleLoader__.load({
1692
1803
  const emotion = analysis?.emotion || 'neutral'
1693
1804
  const color = EMOTION_COLORS[emotion] || EMOTION_COLORS.neutral
1694
1805
  const emotionText = analysis?.emotionZh || EMOTION_LABELS[emotion] || '分析中'
1695
- return h('div', { style: S.row, 'data-voice-emotion': phase },
1806
+ return h('div', {
1807
+ style: S.row,
1808
+ 'data-voice-emotion': phase,
1809
+ onPointerDown: touchShortcut,
1810
+ onFocusCapture: touchShortcut,
1811
+ },
1696
1812
  phase === 'recording'
1697
1813
  ? bars(level, levels)
1698
1814
  : h('span', { style: { ...S.pill, color: phase === 'result' ? color : 'inherit' } },
@@ -1707,6 +1823,21 @@ window.__ModuleLoader__.load({
1707
1823
  ? emotionText
1708
1824
  : '出错',
1709
1825
  ),
1826
+ phase === 'result' && analysis?.segments?.length > 1
1827
+ ? h('span', {
1828
+ style: S.timeline,
1829
+ role: 'img',
1830
+ 'aria-label': '情绪时间线',
1831
+ }, ...analysis.segments.map((segment, index) => h('i', {
1832
+ key: index,
1833
+ title: `${segment.text} · ${EMOTION_LABELS[segment.emotion] || segment.emotion} ${Math.round((Number(segment.confidence) || 0) * 100)}%`,
1834
+ style: {
1835
+ ...S.timelinePart,
1836
+ width: `${Math.max(2, (Number(segment.end) - Number(segment.start)) / Math.max(0.1, Number(analysis.duration) || 1) * 100)}%`,
1837
+ background: EMOTION_COLORS[segment.emotion] || EMOTION_COLORS.neutral,
1838
+ },
1839
+ })))
1840
+ : null,
1710
1841
  h('span', { style: S.message, title: message || '正在录音' },
1711
1842
  phase === 'recording'
1712
1843
  ? h(React.Fragment, null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-dafeiyu-emotion",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "大肥鱼语音情绪耳朵:听语气、识别情绪,并让 DSH 按对方当下的状态自然回复。 · Voice + emotion-aware companion for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -17,7 +17,6 @@
17
17
  "docs/*.md",
18
18
  "docs/*.svg",
19
19
  "docs/*.png",
20
- "scripts/*.mjs",
21
20
  "assets/*.png",
22
21
  "screenshots.json",
23
22
  "icon.svg",