@bittersmilezzz/dsh-asr-voice 0.3.3 → 0.4.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/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
- # - 设置页卡片:「设置 → 插件 → 配置」→「语音输入」(settings.plugin.item,
17
- # key: asr-voice;host 注册同名 settings namespace 让配置 tab 枚举到卡片)。
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);settings 走 scoped inject(可选,
23
- # 缺失时仅云端 ASR 无 key、不影响挂载),agentDefaultModel 在优化路由内
24
- # ctx.get 可选读取。client 侧 settingsScope / locale / slots 是硬依赖(设置卡与
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: "openai",
1115
+ keyPreset: "",
1112
1116
  hint: "I3/I4 开发态:host 用能量 VAD 假 provider 驱动整条管道,不花配额"
1113
1117
  }, {
1114
1118
  id: "dashscope-realtime",
@@ -1315,10 +1319,13 @@ window.__ModuleLoader__.load({
1315
1319
  }
1316
1320
  return false;
1317
1321
  }
1318
- /** 绑定 host settings scope 并订阅:首次读取当前值,之后 scope 变化回写本地快照并广播。 @param binder - settingsScope 服务的 binder(SettingsScopeBinder)。 @returns 订阅 disposer(随 fiber 清理)。 */
1319
- /** 绑定 host settings scope 并订阅:首次读取当前值,之后 scope 变化回写本地快照并广播。 @param binder - settingsScope 服务(官方 SettingsScopeBinder,随 fiber 注入)。 @returns 订阅 disposer(随 fiber 清理)。 */
1320
- function bindConfigScope(binder) {
1321
- const scope = binder.bind({ namespace: ASR_VOICE_NS });
1322
+ /**
1323
+ * 绑定 host Config form 并订阅:首次读取当前值,之后 form 变化回写本地快照并广播。
1324
+ * @param forms - configForms 服务(DSH 0.1.7 官方 ConfigForms,随 fiber 注入)。
1325
+ * @returns 订阅 disposer(随 fiber 清理)。
1326
+ */
1327
+ function bindConfigScope(forms) {
1328
+ const scope = forms.get(ASR_VOICE_NS);
1322
1329
  voiceScope = scope;
1323
1330
  const applySnapshot = () => {
1324
1331
  const value = scope.getSnapshot().value;
@@ -1372,7 +1379,7 @@ window.__ModuleLoader__.load({
1372
1379
  tts,
1373
1380
  ttsVoice,
1374
1381
  hotkey,
1375
- bargeIn,
1382
+ bargeIn: engine === "segmented" && bargeIn,
1376
1383
  maxSessionMs,
1377
1384
  language: source.language,
1378
1385
  settleMs: turn.settleMs,
@@ -2108,8 +2115,8 @@ window.__ModuleLoader__.load({
2108
2115
  behavior: "groupBehavior",
2109
2116
  realtime: "groupRealtime"
2110
2117
  };
2111
- /** 设置卡片:外层折叠与其他插件卡一致(header + chevron + 条件 body)。 */
2112
- function VoiceSettingsCard({ t }) {
2118
+ /** 设置卡片:插件详情页按 view 渲染(summary 一行 / page 表单),兜底路径自绘折叠外壳。 */
2119
+ function VoiceSettingsCard({ t, view }) {
2113
2120
  const version = useConfigVersion();
2114
2121
  const [open, setOpen] = react.useState(false);
2115
2122
  const [showAdvanced, setShowAdvanced] = react.useState(false);
@@ -2283,9 +2290,11 @@ window.__ModuleLoader__.load({
2283
2290
  ];
2284
2291
  const needKey = keyState !== null && !keyState.configured && keyState.failure === null;
2285
2292
  const keyNameMissing = presetById(provider.preset) === void 0 && provider.name.trim() === "";
2293
+ const pageView = view === "page";
2294
+ if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("cardCopy") });
2286
2295
  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", {
2296
+ className: "dshav-card" + (open || pageView ? " dshav-card-open" : "") + (pageView ? " dshav-card-page" : ""),
2297
+ children: [pageView ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2289
2298
  type: "button",
2290
2299
  className: "dshav-header",
2291
2300
  "aria-expanded": open,
@@ -2314,8 +2323,8 @@ window.__ModuleLoader__.load({
2314
2323
  strokeLinejoin: "round"
2315
2324
  })
2316
2325
  })]
2317
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2318
- className: "dshav-body",
2326
+ }), open || pageView ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2327
+ className: "dshav-body" + (pageView ? " dshav-body-page" : ""),
2319
2328
  children: [
2320
2329
  !writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2321
2330
  className: "dshav-field-hint",
@@ -2399,7 +2408,7 @@ window.__ModuleLoader__.load({
2399
2408
  placeholder: keyState?.configured === true ? t("keyKeepPlaceholder") : t("keyPastePlaceholder"),
2400
2409
  spellCheck: false,
2401
2410
  autoComplete: "off",
2402
- disabled: !writable || keyState?.writable === false,
2411
+ disabled: !writable || keyState === null || keyState?.writable === false,
2403
2412
  onChange: (e) => setKeyInput(e.target.value)
2404
2413
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2405
2414
  type: "button",
@@ -4894,6 +4903,7 @@ window.__ModuleLoader__.load({
4894
4903
  headers: { accept: "text/event-stream" },
4895
4904
  signal: controller.signal
4896
4905
  });
4906
+ if (res.status === 409) return;
4897
4907
  if (!res.ok || res.body === null) {
4898
4908
  if (!disposed) onEvent({
4899
4909
  type: "error",
@@ -6707,7 +6717,8 @@ window.__ModuleLoader__.load({
6707
6717
  const sys = systemDict();
6708
6718
  const title = chat.active ? chat.title : busy ? state === "recording" ? sys.recordingTitle : state === "transcribing" ? sys.transcribingTitle : sys.optimizingTitle : chat.enabled ? sys.micTitleHoldChat : sys.micTitle;
6709
6719
  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 shortcut = chat.enabled && config.realtime.hotkey !== "" ? config.realtime.hotkey.replace(/\bCtrl\b/gi, "Control").replace(/\bCmd\b/gi, "Meta") : void 0;
6720
+ const ariaKeyName = (spec) => spec.replace(/\bCtrl\b/gi, "Control").replace(/\bCmd\b/gi, "Meta").replace(/\bAlt\b/gi, "Alt");
6721
+ const shortcut = chat.enabled && config.realtime.hotkey !== "" ? ariaKeyName(config.realtime.hotkey) : config.behavior.hotkey !== "" ? ariaKeyName(config.behavior.hotkey) : void 0;
6711
6722
  const chatStatus = chat.active || !busy && error === null && notice === null ? chat.status : null;
6712
6723
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6713
6724
  className: "dshav-mic-wrap",
@@ -6848,6 +6859,20 @@ window.__ModuleLoader__.load({
6848
6859
  if (e.key === "Escape") {
6849
6860
  e.stopPropagation();
6850
6861
  closePreview();
6862
+ return;
6863
+ }
6864
+ if (e.key !== "Tab") return;
6865
+ const focusables = e.currentTarget.querySelectorAll("button");
6866
+ if (focusables.length === 0) return;
6867
+ const first = focusables[0];
6868
+ const last = focusables[focusables.length - 1];
6869
+ const active = document.activeElement;
6870
+ if (e.shiftKey && (active === first || !e.currentTarget.contains(active))) {
6871
+ e.preventDefault();
6872
+ last.focus();
6873
+ } else if (!e.shiftKey && (active === last || !e.currentTarget.contains(active))) {
6874
+ e.preventDefault();
6875
+ first.focus();
6851
6876
  }
6852
6877
  },
6853
6878
  children: [
@@ -6903,12 +6928,15 @@ window.__ModuleLoader__.load({
6903
6928
  //#endregion
6904
6929
  //#region src/client/index.ts
6905
6930
  const NS = "asr-voice";
6906
- /** 硬依赖:设置卡/配置读写必须的顶层服务(与兄弟插件同款:settingsScope 必须硬依赖,
6907
- * 否则卡片包在 scoped inject 里会因某服务不可注入而永不注册——这正是此前设置卡消失的根因)。 */
6931
+ /** 包名(`plugins.bundle.config` 的 key:bundle 的 package name)。 */
6932
+ const PACKAGE_NAME = "@bittersmilezzz/dsh-asr-voice";
6933
+ /** 硬依赖:设置卡/配置读写必须的顶层服务(DSH 0.1.7 起 configForms 取代
6934
+ * settingsScope;必须硬依赖,否则卡片包在 scoped inject 里会因服务不可注入而
6935
+ * 永不注册——这正是此前设置卡消失的根因)。 */
6908
6936
  const inject = [
6909
6937
  "slots",
6910
6938
  "locale",
6911
- "settingsScope"
6939
+ "configForms"
6912
6940
  ];
6913
6941
  /** 快捷键处理(按住说话 / 点击切换 / 实时对话进出),随 fiber 生命周期注册。 */
6914
6942
  function applyHotkey() {
@@ -7022,19 +7050,21 @@ window.__ModuleLoader__.load({
7022
7050
  const sessions = raw.sessions;
7023
7051
  cancelTurn = (sessionId) => {
7024
7052
  try {
7025
- ((sessions?.scope(sessionId))?.get("conversation"))?.cancel?.()?.catch?.(() => {});
7053
+ ((sessions?.scope(sessionId))?.get("conversation"))?.cancel?.().catch(() => {});
7026
7054
  } catch {}
7027
7055
  };
7028
7056
  });
7029
7057
  ctx.effect(applyHotkey, "asr-voice: hotkey");
7030
- ctx.effect(() => bindConfigScope(ctx.settingsScope), "asr-voice: settings scope sync");
7031
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
7032
- name: "settings.plugin.item",
7033
- key: "asr-voice",
7058
+ ctx.effect(() => bindConfigScope(ctx.configForms), "asr-voice: config form sync");
7059
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
7060
+ name: "plugins.bundle.config",
7061
+ key: PACKAGE_NAME,
7034
7062
  locale: NS
7035
- }, () => react_jsx_runtime.jsx(VoiceSettingsCard, { t })));
7063
+ }, (props) => react_jsx_runtime.jsx(VoiceSettingsCard, {
7064
+ t,
7065
+ view: props?.view
7066
+ })));
7036
7067
  ctx.inject([
7037
- "settingsScope",
7038
7068
  "connection",
7039
7069
  "remote",
7040
7070
  "remote.credentials"