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 +28 -0
- package/README.md +9 -3
- package/cordis.patch.yml +1 -0
- package/docs/MARKETPLACE.md +1 -1
- package/docs/RELEASE-0.2.10.md +20 -0
- package/docs/RELEASE-0.2.9.md +17 -0
- package/docs/RELEASE-0.3.0.md +29 -0
- package/lib/client.js +146 -15
- package/package.json +1 -2
- package/src/index.js +159 -41
- package/scripts/test-analyze-handler.mjs +0 -223
- package/scripts/test-backend-cancel.mjs +0 -56
- package/scripts/test-client-registration.mjs +0 -140
- package/scripts/test-config-handler.mjs +0 -196
- package/scripts/test-config-schema.mjs +0 -79
- package/scripts/test-delivery-policy.mjs +0 -47
- package/scripts/test-shortcut-registration.mjs +0 -68
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.
|
|
115
|
-
https://github.com/LiDu-china/dsh-dafeiyu-emotion/releases/download/v0.
|
|
116
|
-
dsh plugin --profile desktop add "D:/path/to/dsh-dafeiyu-emotion-0.
|
|
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
package/docs/MARKETPLACE.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 =
|
|
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 =
|
|
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: '
|
|
603
|
-
|
|
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(
|
|
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(() =>
|
|
1623
|
-
|
|
1624
|
-
|
|
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', {
|
|
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.
|
|
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",
|