@bittersmilezzz/dsh-asr-voice 0.3.3 → 0.4.1
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/cordis.patch.yml +6 -5
- package/lib/client.js +86 -27
- package/lib/client.js.map +1 -1
- package/lib/index.js +72 -16
- package/lib/index.js.map +1 -1
- package/lib/optimize.js +1 -1
- package/lib/optimize.js.map +1 -1
- package/lib/presets.js +2 -1
- package/lib/presets.js.map +1 -1
- package/lib/realtime-host.js +19 -1
- package/lib/realtime-host.js.map +1 -1
- package/lib/realtime-tts.js +49 -29
- package/lib/realtime-tts.js.map +1 -1
- package/lib/settings.js +17 -3
- package/lib/settings.js.map +1 -1
- package/lib/types/index.d.ts +18 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/optimize.d.ts +8 -0
- package/lib/types/optimize.d.ts.map +1 -1
- package/lib/types/presets.d.ts +4 -1
- package/lib/types/presets.d.ts.map +1 -1
- package/lib/types/realtime-host.d.ts +4 -0
- package/lib/types/realtime-host.d.ts.map +1 -1
- package/lib/types/realtime-tts.d.ts.map +1 -1
- package/lib/types/settings.d.ts +37 -26
- package/lib/types/settings.d.ts.map +1 -1
- package/package.json +33 -28
package/cordis.patch.yml
CHANGED
|
@@ -13,15 +13,16 @@
|
|
|
13
13
|
# host 实时通道(realtime.provider 选 'builtin' 假 provider 或真云端预置)。
|
|
14
14
|
# - 提示词优化:本地启发式(zh/en 清洗)或可选 LLM 重写(optimize.mode 默认
|
|
15
15
|
# llm,走 DSH 当前所选模型的官方 LLM 通道,host 代理)。
|
|
16
|
-
# - 设置页卡片:「设置 → 插件 → 配置」→「语音输入」(
|
|
17
|
-
# key: asr-voice
|
|
16
|
+
# - 设置页卡片:「设置 → 插件 → 配置」→「语音输入」(plugins.bundle.config,
|
|
17
|
+
# key: asr-voice;配置 schema 是入口模块顶层导出的 Config,DSH 0.1.7 起
|
|
18
|
+
# profile-backed forms,entry id 即 namespace)。
|
|
18
19
|
# - API key 只进 **DSH 凭据服务**(预置引用名与官方 LLM 凭据同名),settings 与浏览器
|
|
19
20
|
# 都不持有密钥;音频与转写请求经 /api/asr-voice/* 私有 JSON 代理。纯 Node HTTP 实现,
|
|
20
21
|
# macOS / Windows 双平台。
|
|
21
22
|
#
|
|
22
|
-
# host inject 只列硬依赖(webServer/llm
|
|
23
|
-
#
|
|
24
|
-
# ctx.get 可选读取。client 侧
|
|
23
|
+
# host inject 只列硬依赖(webServer/llm);配置经函数插件第二参注入(DSH 0.1.7
|
|
24
|
+
# Config 契约),无需 inject settings;agentDefaultModel 在优化路由内
|
|
25
|
+
# ctx.get 可选读取。client 侧 configForms / locale / slots 是硬依赖(设置卡与
|
|
25
26
|
# 按钮座位必需),connection / sessions / remote.credentials 是可选 ctx.inject
|
|
26
27
|
# (缺失只让对应能力退化:密钥读写 / 回合打断,不影响挂载)。
|
|
27
28
|
# 注意:inject 项是 Cordis 服务名(非 entry id)。
|
package/lib/client.js
CHANGED
|
@@ -818,6 +818,9 @@ window.__ModuleLoader__.load({
|
|
|
818
818
|
}
|
|
819
819
|
.dshav-card:hover { border-color: var(--dsw-alias-label-dimmed); }
|
|
820
820
|
.dshav-card.dshav-card-open { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-label-dimmed);}
|
|
821
|
+
/* 插件详情页(view: 'page'):官方页面已画标题/外壳,卡片容器透明化、body 无分隔线。 */
|
|
822
|
+
.dshav-card.dshav-card-page { border: 0; background: none; }
|
|
823
|
+
.dshav-card.dshav-card-page:hover { border-color: transparent; }
|
|
821
824
|
.dshav-header {
|
|
822
825
|
width: 100%;
|
|
823
826
|
appearance: none;
|
|
@@ -846,6 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
846
849
|
.dshav-chevron { flex: none; color: var(--dsw-alias-label-tertiary); transition: transform .16s;}
|
|
847
850
|
.dshav-chevron.dshav-open { transform: rotate(180deg); }
|
|
848
851
|
.dshav-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding-bottom: 8px;}
|
|
852
|
+
.dshav-body.dshav-body-page { border-top: 0; margin: 0; padding-top: 0; }
|
|
849
853
|
.dshav-group { display: flex; flex-direction: column; }
|
|
850
854
|
/* 分组之间用与字段一致的分割线(官方 border-l2)。 */
|
|
851
855
|
.dshav-group + .dshav-group { border-top: 1px solid var(--dsw-alias-border-l2); }
|
|
@@ -1108,7 +1112,7 @@ window.__ModuleLoader__.load({
|
|
|
1108
1112
|
label: "内置模拟(开发)",
|
|
1109
1113
|
wssUrl: "",
|
|
1110
1114
|
defaultModel: "",
|
|
1111
|
-
keyPreset: "
|
|
1115
|
+
keyPreset: "",
|
|
1112
1116
|
hint: "I3/I4 开发态:host 用能量 VAD 假 provider 驱动整条管道,不花配额"
|
|
1113
1117
|
}, {
|
|
1114
1118
|
id: "dashscope-realtime",
|
|
@@ -1215,8 +1219,23 @@ window.__ModuleLoader__.load({
|
|
|
1215
1219
|
listeners.delete(fn);
|
|
1216
1220
|
};
|
|
1217
1221
|
}
|
|
1218
|
-
/**
|
|
1219
|
-
|
|
1222
|
+
/**
|
|
1223
|
+
* settings namespace = host 半区 `cordis.patch.yml` 的 entry `id`(DSH 0.1.7 起
|
|
1224
|
+
* `SettingsForms.update/describe` 与 `ConfigForms.get` 都用这个字符串定位 entry,
|
|
1225
|
+
* 官方实现是 `entries().find(row => row.options.id === ns)`)。
|
|
1226
|
+
*
|
|
1227
|
+
* ⚠ 它**不是** npm 包名(本插件 npm 包名是 `@bittersmilezzz/dsh-asr-voice`),也不
|
|
1228
|
+
* 是文件里其它地方的插件短名。拿错字符串不会抛错:host 侧 update 抛
|
|
1229
|
+
* `No configurable plugin entry`(migrateLegacyKeys 只落一句 warn),client 侧
|
|
1230
|
+
* configForms.get 拿到 unavailable 快照 → 设置卡能渲染但**所有读写静默失效**。
|
|
1231
|
+
* 一致性由 test/entry-id-parity.test.mjs 钉住(读 cordis.patch.yml 的 id,并与
|
|
1232
|
+
* host 侧 `src/settings.ts` 的 `ASR_VOICE_SETTINGS_NAMESPACE` 三方比对)。
|
|
1233
|
+
*
|
|
1234
|
+
* 这里刻意**不再 import** host 侧的常量:client bundle 由 tsdown 打包,把
|
|
1235
|
+
* `src/settings.ts`(整棵 schemastery schema)拖进浏览器产物没有意义;用一个字面量
|
|
1236
|
+
* 加一条三方比对测试守住等价性,比共享导入更轻。
|
|
1237
|
+
*/
|
|
1238
|
+
const ASR_VOICE_NS = "dsh-asr-voice";
|
|
1220
1239
|
/** 把旧版凭据通道适配成新版形状(alpha.3 前的老运行时仍暴露旧面时兜底)。 */
|
|
1221
1240
|
function adaptLegacyCredentials(legacy) {
|
|
1222
1241
|
if (legacy === void 0) return void 0;
|
|
@@ -1315,10 +1334,13 @@ window.__ModuleLoader__.load({
|
|
|
1315
1334
|
}
|
|
1316
1335
|
return false;
|
|
1317
1336
|
}
|
|
1318
|
-
/**
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1337
|
+
/**
|
|
1338
|
+
* 绑定 host Config form 并订阅:首次读取当前值,之后 form 变化回写本地快照并广播。
|
|
1339
|
+
* @param forms - configForms 服务(DSH 0.1.7 官方 ConfigForms,随 fiber 注入)。
|
|
1340
|
+
* @returns 订阅 disposer(随 fiber 清理)。
|
|
1341
|
+
*/
|
|
1342
|
+
function bindConfigScope(forms) {
|
|
1343
|
+
const scope = forms.get(ASR_VOICE_NS);
|
|
1322
1344
|
voiceScope = scope;
|
|
1323
1345
|
const applySnapshot = () => {
|
|
1324
1346
|
const value = scope.getSnapshot().value;
|
|
@@ -1372,7 +1394,7 @@ window.__ModuleLoader__.load({
|
|
|
1372
1394
|
tts,
|
|
1373
1395
|
ttsVoice,
|
|
1374
1396
|
hotkey,
|
|
1375
|
-
bargeIn,
|
|
1397
|
+
bargeIn: engine === "segmented" && bargeIn,
|
|
1376
1398
|
maxSessionMs,
|
|
1377
1399
|
language: source.language,
|
|
1378
1400
|
settleMs: turn.settleMs,
|
|
@@ -1502,6 +1524,10 @@ window.__ModuleLoader__.load({
|
|
|
1502
1524
|
/** 保存草稿:只把真正改过的顶层段写回 host,然后**读回校验**。
|
|
1503
1525
|
* `SettingsScope.set` 会把失败吞掉并重载宿主状态(promise 成功不代表落盘),所以
|
|
1504
1526
|
* 判定标准只能是写完之后宿主那边到底剩什么——这也正是官方设置卡的做法。
|
|
1527
|
+
*
|
|
1528
|
+
* 两道防线:① `set` 返回 `false` = 宿主拒写/跳过,直接计入失败集合并提前返回段名,
|
|
1529
|
+
* 不等读回——因为拒写时宿主刚触发过一次状态重载,重载值恰好等于草稿时读回会算出
|
|
1530
|
+
* 「零变更」→ 静默报成功;② 读回校验兜住「set 说成功但宿主没留住」这一类。
|
|
1505
1531
|
* @returns 未落盘的段名,undefined 表示全部落定。
|
|
1506
1532
|
*/
|
|
1507
1533
|
async function writeDraft(draft) {
|
|
@@ -1519,7 +1545,17 @@ window.__ModuleLoader__.load({
|
|
|
1519
1545
|
announce();
|
|
1520
1546
|
return;
|
|
1521
1547
|
}
|
|
1522
|
-
|
|
1548
|
+
const refused = [];
|
|
1549
|
+
for (const key of changed) {
|
|
1550
|
+
let accepted = false;
|
|
1551
|
+
try {
|
|
1552
|
+
accepted = await scope.set(key, draft[key]);
|
|
1553
|
+
} catch {
|
|
1554
|
+
accepted = false;
|
|
1555
|
+
}
|
|
1556
|
+
if (!accepted) refused.push(key);
|
|
1557
|
+
}
|
|
1558
|
+
if (refused.length > 0) return refused[0];
|
|
1523
1559
|
mergeHostValue(draft);
|
|
1524
1560
|
announce();
|
|
1525
1561
|
const resolved = scope.getSnapshot().value;
|
|
@@ -2108,8 +2144,8 @@ window.__ModuleLoader__.load({
|
|
|
2108
2144
|
behavior: "groupBehavior",
|
|
2109
2145
|
realtime: "groupRealtime"
|
|
2110
2146
|
};
|
|
2111
|
-
/**
|
|
2112
|
-
function VoiceSettingsCard({ t }) {
|
|
2147
|
+
/** 设置卡片:插件详情页按 view 渲染(summary 一行 / page 表单),兜底路径自绘折叠外壳。 */
|
|
2148
|
+
function VoiceSettingsCard({ t, view }) {
|
|
2113
2149
|
const version = useConfigVersion();
|
|
2114
2150
|
const [open, setOpen] = react.useState(false);
|
|
2115
2151
|
const [showAdvanced, setShowAdvanced] = react.useState(false);
|
|
@@ -2283,9 +2319,11 @@ window.__ModuleLoader__.load({
|
|
|
2283
2319
|
];
|
|
2284
2320
|
const needKey = keyState !== null && !keyState.configured && keyState.failure === null;
|
|
2285
2321
|
const keyNameMissing = presetById(provider.preset) === void 0 && provider.name.trim() === "";
|
|
2322
|
+
const pageView = view === "page";
|
|
2323
|
+
if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("cardCopy") });
|
|
2286
2324
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2287
|
-
className: "dshav-card" + (open ? " dshav-card-open" : ""),
|
|
2288
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2325
|
+
className: "dshav-card" + (open || pageView ? " dshav-card-open" : "") + (pageView ? " dshav-card-page" : ""),
|
|
2326
|
+
children: [pageView ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2289
2327
|
type: "button",
|
|
2290
2328
|
className: "dshav-header",
|
|
2291
2329
|
"aria-expanded": open,
|
|
@@ -2314,8 +2352,8 @@ window.__ModuleLoader__.load({
|
|
|
2314
2352
|
strokeLinejoin: "round"
|
|
2315
2353
|
})
|
|
2316
2354
|
})]
|
|
2317
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2318
|
-
className: "dshav-body",
|
|
2355
|
+
}), open || pageView ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2356
|
+
className: "dshav-body" + (pageView ? " dshav-body-page" : ""),
|
|
2319
2357
|
children: [
|
|
2320
2358
|
!writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2321
2359
|
className: "dshav-field-hint",
|
|
@@ -2399,7 +2437,7 @@ window.__ModuleLoader__.load({
|
|
|
2399
2437
|
placeholder: keyState?.configured === true ? t("keyKeepPlaceholder") : t("keyPastePlaceholder"),
|
|
2400
2438
|
spellCheck: false,
|
|
2401
2439
|
autoComplete: "off",
|
|
2402
|
-
disabled: !writable || keyState?.writable === false,
|
|
2440
|
+
disabled: !writable || keyState === null || keyState?.writable === false,
|
|
2403
2441
|
onChange: (e) => setKeyInput(e.target.value)
|
|
2404
2442
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2405
2443
|
type: "button",
|
|
@@ -4894,6 +4932,7 @@ window.__ModuleLoader__.load({
|
|
|
4894
4932
|
headers: { accept: "text/event-stream" },
|
|
4895
4933
|
signal: controller.signal
|
|
4896
4934
|
});
|
|
4935
|
+
if (res.status === 409) return;
|
|
4897
4936
|
if (!res.ok || res.body === null) {
|
|
4898
4937
|
if (!disposed) onEvent({
|
|
4899
4938
|
type: "error",
|
|
@@ -6707,7 +6746,8 @@ window.__ModuleLoader__.load({
|
|
|
6707
6746
|
const sys = systemDict();
|
|
6708
6747
|
const title = chat.active ? chat.title : busy ? state === "recording" ? sys.recordingTitle : state === "transcribing" ? sys.transcribingTitle : sys.optimizingTitle : chat.enabled ? sys.micTitleHoldChat : sys.micTitle;
|
|
6709
6748
|
const ariaLabel = chat.active ? chat.ariaLabel : busy ? state === "recording" ? t("recordingTitle") : state === "transcribing" ? t("transcribingTitle") : t("optimizingTitle") : chat.enabled ? t("micTitleHoldChat") : t("micTitle");
|
|
6710
|
-
const
|
|
6749
|
+
const ariaKeyName = (spec) => spec.replace(/\bCtrl\b/gi, "Control").replace(/\bCmd\b/gi, "Meta").replace(/\bAlt\b/gi, "Alt");
|
|
6750
|
+
const shortcut = chat.enabled && config.realtime.hotkey !== "" ? ariaKeyName(config.realtime.hotkey) : config.behavior.hotkey !== "" ? ariaKeyName(config.behavior.hotkey) : void 0;
|
|
6711
6751
|
const chatStatus = chat.active || !busy && error === null && notice === null ? chat.status : null;
|
|
6712
6752
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6713
6753
|
className: "dshav-mic-wrap",
|
|
@@ -6848,6 +6888,20 @@ window.__ModuleLoader__.load({
|
|
|
6848
6888
|
if (e.key === "Escape") {
|
|
6849
6889
|
e.stopPropagation();
|
|
6850
6890
|
closePreview();
|
|
6891
|
+
return;
|
|
6892
|
+
}
|
|
6893
|
+
if (e.key !== "Tab") return;
|
|
6894
|
+
const focusables = e.currentTarget.querySelectorAll("button");
|
|
6895
|
+
if (focusables.length === 0) return;
|
|
6896
|
+
const first = focusables[0];
|
|
6897
|
+
const last = focusables[focusables.length - 1];
|
|
6898
|
+
const active = document.activeElement;
|
|
6899
|
+
if (e.shiftKey && (active === first || !e.currentTarget.contains(active))) {
|
|
6900
|
+
e.preventDefault();
|
|
6901
|
+
last.focus();
|
|
6902
|
+
} else if (!e.shiftKey && (active === last || !e.currentTarget.contains(active))) {
|
|
6903
|
+
e.preventDefault();
|
|
6904
|
+
first.focus();
|
|
6851
6905
|
}
|
|
6852
6906
|
},
|
|
6853
6907
|
children: [
|
|
@@ -6903,12 +6957,15 @@ window.__ModuleLoader__.load({
|
|
|
6903
6957
|
//#endregion
|
|
6904
6958
|
//#region src/client/index.ts
|
|
6905
6959
|
const NS = "asr-voice";
|
|
6906
|
-
/**
|
|
6907
|
-
|
|
6960
|
+
/** 包名(`plugins.bundle.config` 的 key:bundle 的 package name)。 */
|
|
6961
|
+
const PACKAGE_NAME = "@bittersmilezzz/dsh-asr-voice";
|
|
6962
|
+
/** 硬依赖:设置卡/配置读写必须的顶层服务(DSH 0.1.7 起 configForms 取代
|
|
6963
|
+
* settingsScope;必须硬依赖,否则卡片包在 scoped inject 里会因服务不可注入而
|
|
6964
|
+
* 永不注册——这正是此前设置卡消失的根因)。 */
|
|
6908
6965
|
const inject = [
|
|
6909
6966
|
"slots",
|
|
6910
6967
|
"locale",
|
|
6911
|
-
"
|
|
6968
|
+
"configForms"
|
|
6912
6969
|
];
|
|
6913
6970
|
/** 快捷键处理(按住说话 / 点击切换 / 实时对话进出),随 fiber 生命周期注册。 */
|
|
6914
6971
|
function applyHotkey() {
|
|
@@ -7022,19 +7079,21 @@ window.__ModuleLoader__.load({
|
|
|
7022
7079
|
const sessions = raw.sessions;
|
|
7023
7080
|
cancelTurn = (sessionId) => {
|
|
7024
7081
|
try {
|
|
7025
|
-
((sessions?.scope(sessionId))?.get("conversation"))?.cancel?.()
|
|
7082
|
+
((sessions?.scope(sessionId))?.get("conversation"))?.cancel?.().catch(() => {});
|
|
7026
7083
|
} catch {}
|
|
7027
7084
|
};
|
|
7028
7085
|
});
|
|
7029
7086
|
ctx.effect(applyHotkey, "asr-voice: hotkey");
|
|
7030
|
-
ctx.effect(() => bindConfigScope(ctx.
|
|
7031
|
-
ctx.slots.inject("
|
|
7032
|
-
name: "
|
|
7033
|
-
key:
|
|
7087
|
+
ctx.effect(() => bindConfigScope(ctx.configForms), "asr-voice: config form sync");
|
|
7088
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
7089
|
+
name: "plugins.bundle.config",
|
|
7090
|
+
key: PACKAGE_NAME,
|
|
7034
7091
|
locale: NS
|
|
7035
|
-
}, () => react_jsx_runtime.jsx(VoiceSettingsCard, {
|
|
7092
|
+
}, (props) => react_jsx_runtime.jsx(VoiceSettingsCard, {
|
|
7093
|
+
t,
|
|
7094
|
+
view: props?.view
|
|
7095
|
+
})));
|
|
7036
7096
|
ctx.inject([
|
|
7037
|
-
"settingsScope",
|
|
7038
7097
|
"connection",
|
|
7039
7098
|
"remote",
|
|
7040
7099
|
"remote.credentials"
|