dsh-live-voice 0.3.0 → 0.3.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/CHANGELOG.md +16 -0
- package/PLAN.md +6 -0
- package/README.md +3 -1
- package/docs/ARCHITECTURE.md +9 -16
- package/lib/client.js +43 -20
- package/lib/server.js +1 -1
- package/package.json +2 -2
- package/src/app/AGENTS.md +7 -0
- package/src/app/ARCHITECTURE.md +10 -0
- package/src/app/client/i18n/catalogs/base.ts +1 -0
- package/src/app/client/i18n/catalogs/en.ts +1 -0
- package/src/app/client/i18n/catalogs/es.ts +1 -0
- package/src/app/client/i18n/catalogs/fr.ts +1 -0
- package/src/app/client/i18n/catalogs/hi.ts +1 -0
- package/src/app/client/i18n/catalogs/pt-BR.ts +1 -0
- package/src/app/client/i18n/catalogs/zh.ts +1 -0
- package/src/modules/AGENTS.md +5 -0
- package/src/modules/conversation/ARCHITECTURE.md +5 -0
- package/src/modules/core/ARCHITECTURE.md +5 -0
- package/src/modules/core/qwen/QwenHttpHost.ts +1 -1
- package/src/modules/recognition/ARCHITECTURE.md +5 -0
- package/src/modules/settings/ARCHITECTURE.md +5 -0
- package/src/modules/settings/components/LiveVoiceSettings.tsx +11 -3
- package/src/modules/settings/sections/conversation/ConversationSettingsSection.tsx +12 -6
- package/src/modules/settings/sections/recognition/RecognitionEngineSettings.tsx +2 -2
- package/src/modules/settings/sections/recognition/RecognitionFilterSettings.tsx +1 -1
- package/src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx +7 -3
- package/src/modules/settings/sections/recognition/SilenceDetectionSettings.tsx +1 -1
- package/src/modules/settings/sections/recognition/VoiceCommandSettings.tsx +1 -1
- package/src/modules/settings/sections/speak/OutputFilterSettings.tsx +1 -1
- package/src/modules/settings/sections/speak/SpeakSettingsSection.tsx +7 -3
- package/src/modules/settings/sections/speak/SpeechAdvancedSettings.tsx +2 -2
- package/src/modules/settings/services/releases.ts +1 -1
- package/src/modules/speak/ARCHITECTURE.md +5 -0
- package/src/shared/ARCHITECTURE.md +5 -0
- package/src/shared/design-system/icons/icons.ts +4 -0
- package/src/shared/design-system/layout/SettingsSubcard.tsx +15 -3
- package/src/shared/design-system/layout/SettingsTabs.tsx +4 -2
- package/src/styles/index.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to DSH Live Voice are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.1] - Unreleased
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
- Group the primary speech, recognition, and conversation settings into collapsed General sections, with icons on tabs and subsection headings.
|
|
10
|
+
- Start silence-detection settings collapsed and keep recognition capability status visible outside the General section.
|
|
11
|
+
- Add the General section label to every supported interface language and update accessibility and mounted-component regression coverage.
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- Split agent guidance and architecture documentation by application, domain module, and shared design-system boundaries.
|
|
16
|
+
|
|
17
|
+
### Compatibility
|
|
18
|
+
|
|
19
|
+
- Confirm tested compatibility with DeepSeek Harness **0.2.0-rc.2**.
|
|
20
|
+
|
|
5
21
|
## [0.3.0] - Unreleased
|
|
6
22
|
|
|
7
23
|
### Features
|
package/PLAN.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
This document describes the product plan and local implementation progress. The published npm version is a documentation placeholder; the working tree now contains an initial plugin undergoing integration validation. Features below describe intended behavior unless verified in the progress section.
|
|
4
4
|
|
|
5
|
+
## 0.3.1 release preparation
|
|
6
|
+
|
|
7
|
+
The Settings interface now groups primary fields under collapsed General sections, adds icons to tabs and subsection headings, and keeps recognition capability status outside the General section. All six interface catalogs include the new section label. Release metadata and the lockfile use 0.3.1; the version-badge regression derives the current and next release versions from build-injected package metadata.
|
|
8
|
+
|
|
9
|
+
Validation on an isolated release snapshot: `npm test` passed all 182 automated tests, including the build and TypeScript check; `npm run format:check` passed; an isolated Chrome preview with a synthetic controller verified the three tabs, initially collapsed General sections, and disclosure interactions without browser errors; `npm publish --dry-run --access public --tag latest` passed package validation. These checks do not constitute physical-microphone, native speech-engine, or authenticated DSH runtime acceptance. No npm publication or version tag was created.
|
|
10
|
+
|
|
5
11
|
## Qwen3 local engine
|
|
6
12
|
|
|
7
13
|
Qwen3 is available as independent recognition and speaking selections backed by a separately managed HTTP process on the DSH host. Runtime installation, weights, and service lifecycle stay outside the repository. The authenticated host bridge accepts only a loopback base URL, validates bounded mono PCM16 WAV before ASR forwarding, maps `pt-BR` to Portuguese, forwards TTS text without logging it, and returns WAV audio for browser playback. Browser cancellation aborts pending host/model requests and stale synthesized audio cannot begin playback after cancellation. Automated plugin coverage includes configuration normalization, loopback validation, HTTP payloads, WAV transport, browser playback, and route cleanup. Native model and endpoint acceptance must be reported separately from these tests.
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# DSH Live Voice
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-live-voice)
|
|
4
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.0-rc.2)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
+
**Tested and working with DeepSeek Harness v0.2.0-rc.2.** This is the current tested DSH version, recorded in the `dshTestedVersion` field in [package.json](package.json).
|
|
8
|
+
|
|
7
9
|
**A local-first, hands-free voice assistant plugin for DeepSeek Harness (DSH).**
|
|
8
10
|
*Built in Brazil 🇧🇷 and tested daily with Brazilian Portuguese on macOS.*
|
|
9
11
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -11,24 +11,17 @@ DSH Live Voice uses application composition roots, domain modules, and a domain-
|
|
|
11
11
|
5. Feature modules use the intentionally centralized client i18n API, but never register DSH slots or host routes.
|
|
12
12
|
6. Browser and host dependency graphs remain separate. Host-only Node imports must never enter the client bundle.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Ownership map
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- [Application composition, slots, routes, i18n](../src/app/ARCHITECTURE.md)
|
|
17
|
+
- [Core voice policy, ownership, settings normalization](../src/modules/core/ARCHITECTURE.md)
|
|
18
|
+
- [Conversation and composer behavior](../src/modules/conversation/ARCHITECTURE.md)
|
|
19
|
+
- [Settings presentation and storage](../src/modules/settings/ARCHITECTURE.md)
|
|
20
|
+
- [Recognition engines and capability](../src/modules/recognition/ARCHITECTURE.md)
|
|
21
|
+
- [Speaking engines and playback](../src/modules/speak/ARCHITECTURE.md)
|
|
22
|
+
- [Shared design system](../src/shared/ARCHITECTURE.md)
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
The only client language tree is `src/app/client/i18n`. It owns typed catalogs, the React runtime, DSH catalog registration, and synchronization with `ctx.locale`. Every DSH slot component is wrapped by the application language boundary. UI locale, recognition language, synthesis language, and user-defined command phrases are independent values.
|
|
24
|
-
|
|
25
|
-
## Modules
|
|
26
|
-
|
|
27
|
-
- `modules/core`: shared voice-domain coordination, settings normalization, ownership, transcript handling, microphone policy, filters, and shared Qwen configuration.
|
|
28
|
-
- `modules/conversation`: composer/chat UI, conversation components, chat models, and session-facing behavior.
|
|
29
|
-
- `modules/settings`: modular Settings shell, hooks, services, and Conversation/Recognition/Speech sections.
|
|
30
|
-
- `modules/recognition/engines/{browser,qwen,whisper}`: recognition adapters and provider-specific host/UI code.
|
|
31
|
-
- `modules/speak/engines/{browser,qwen,say,audio}`: speech adapters, host audio, transcoding, and provider clients.
|
|
24
|
+
Read the relevant local architecture file before editing a boundary. The source of truth for plugin and tested-DSH versions is `package.json`.
|
|
32
25
|
|
|
33
26
|
## Stable contracts
|
|
34
27
|
|
package/lib/client.js
CHANGED
|
@@ -1372,6 +1372,7 @@ var en = {
|
|
|
1372
1372
|
"dsh-live-voice.settings.delivery.toggle": "Automatic delivery mode",
|
|
1373
1373
|
"dsh-live-voice.settings.engine.refresh": "Refresh available engines",
|
|
1374
1374
|
"dsh-live-voice.settings.filters.title": "Filtering",
|
|
1375
|
+
"dsh-live-voice.settings.general.title": "General",
|
|
1375
1376
|
"dsh-live-voice.settings.tabs.conversation": "Conversation",
|
|
1376
1377
|
"dsh-live-voice.settings.tabs.recognition": "Speech recognition",
|
|
1377
1378
|
"dsh-live-voice.settings.tabs.speak": "Speech",
|
|
@@ -1588,6 +1589,7 @@ var es = {
|
|
|
1588
1589
|
"dsh-live-voice.settings.delivery.toggle": "Modo de env\xEDo autom\xE1tico",
|
|
1589
1590
|
"dsh-live-voice.settings.engine.refresh": "Actualizar los motores disponibles",
|
|
1590
1591
|
"dsh-live-voice.settings.filters.title": "Filtrado",
|
|
1592
|
+
"dsh-live-voice.settings.general.title": "General",
|
|
1591
1593
|
"dsh-live-voice.settings.tabs.conversation": "Conversaci\xF3n",
|
|
1592
1594
|
"dsh-live-voice.settings.tabs.recognition": "Reconocimiento de voz",
|
|
1593
1595
|
"dsh-live-voice.settings.tabs.speak": "S\xEDntesis de voz",
|
|
@@ -1804,6 +1806,7 @@ var fr = {
|
|
|
1804
1806
|
"dsh-live-voice.settings.delivery.toggle": "Mode d\u2019envoi automatique",
|
|
1805
1807
|
"dsh-live-voice.settings.engine.refresh": "Actualiser les moteurs disponibles",
|
|
1806
1808
|
"dsh-live-voice.settings.filters.title": "Filtrage",
|
|
1809
|
+
"dsh-live-voice.settings.general.title": "G\xE9n\xE9ral",
|
|
1807
1810
|
"dsh-live-voice.settings.tabs.conversation": "Conversation",
|
|
1808
1811
|
"dsh-live-voice.settings.tabs.recognition": "Reconnaissance vocale",
|
|
1809
1812
|
"dsh-live-voice.settings.tabs.speak": "Synth\xE8se vocale",
|
|
@@ -2020,6 +2023,7 @@ var hi = {
|
|
|
2020
2023
|
"dsh-live-voice.settings.delivery.toggle": "\u0938\u094D\u0935\u091A\u093E\u0932\u093F\u0924 \u092D\u0947\u091C\u0928\u0947 \u0915\u093E \u092E\u094B\u0921",
|
|
2021
2024
|
"dsh-live-voice.settings.engine.refresh": "\u0909\u092A\u0932\u092C\u094D\u0927 \u0907\u0902\u091C\u0928 \u0930\u0940\u092B\u093C\u094D\u0930\u0947\u0936 \u0915\u0930\u0947\u0902",
|
|
2022
2025
|
"dsh-live-voice.settings.filters.title": "\u092B\u093C\u093F\u0932\u094D\u091F\u0930\u093F\u0902\u0917",
|
|
2026
|
+
"dsh-live-voice.settings.general.title": "\u0938\u093E\u092E\u093E\u0928\u094D\u092F",
|
|
2023
2027
|
"dsh-live-voice.settings.tabs.conversation": "\u092C\u093E\u0924\u091A\u0940\u0924",
|
|
2024
2028
|
"dsh-live-voice.settings.tabs.recognition": "\u0935\u093E\u0915\u094D \u092A\u0939\u091A\u093E\u0928",
|
|
2025
2029
|
"dsh-live-voice.settings.tabs.speak": "\u0935\u093E\u091A\u0928",
|
|
@@ -2236,6 +2240,7 @@ var ptBR = {
|
|
|
2236
2240
|
"dsh-live-voice.settings.delivery.toggle": "Modo de entrega autom\xE1tica",
|
|
2237
2241
|
"dsh-live-voice.settings.engine.refresh": "Atualizar mecanismos dispon\xEDveis",
|
|
2238
2242
|
"dsh-live-voice.settings.filters.title": "Filtragem",
|
|
2243
|
+
"dsh-live-voice.settings.general.title": "Gerais",
|
|
2239
2244
|
"dsh-live-voice.settings.tabs.conversation": "Conversa",
|
|
2240
2245
|
"dsh-live-voice.settings.tabs.recognition": "Reconhecimento de voz",
|
|
2241
2246
|
"dsh-live-voice.settings.tabs.speak": "Fala",
|
|
@@ -2452,6 +2457,7 @@ var zh = {
|
|
|
2452
2457
|
"dsh-live-voice.settings.delivery.toggle": "\u81EA\u52A8\u53D1\u9001\u6A21\u5F0F",
|
|
2453
2458
|
"dsh-live-voice.settings.engine.refresh": "\u5237\u65B0\u53EF\u7528\u5F15\u64CE",
|
|
2454
2459
|
"dsh-live-voice.settings.filters.title": "\u8FC7\u6EE4",
|
|
2460
|
+
"dsh-live-voice.settings.general.title": "\u5E38\u89C4",
|
|
2455
2461
|
"dsh-live-voice.settings.tabs.conversation": "\u5BF9\u8BDD",
|
|
2456
2462
|
"dsh-live-voice.settings.tabs.recognition": "\u8BED\u97F3\u8BC6\u522B",
|
|
2457
2463
|
"dsh-live-voice.settings.tabs.speak": "\u8BED\u97F3\u5408\u6210",
|
|
@@ -2741,6 +2747,10 @@ var iconPaths = {
|
|
|
2741
2747
|
skipNext: "M5 5l10 7-10 7V5M19 5v14",
|
|
2742
2748
|
send: "M3 11.5L21 3l-8.5 18-2-7.5L3 11.5zm7.5 2L21 3",
|
|
2743
2749
|
queue: "M5 6h14M5 12h10M5 18h6M18 15v6M15 18h6",
|
|
2750
|
+
settings: "M4 7h16M4 17h16M8 4v6M16 14v6",
|
|
2751
|
+
filter: "M3 5h18l-7 8v6l-4 2v-8L3 5z",
|
|
2752
|
+
link: "M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.7 1.7M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.7-1.7",
|
|
2753
|
+
chevron: "M6 9l6 6 6-6",
|
|
2744
2754
|
close: "M6 6l12 12M18 6 6 18"
|
|
2745
2755
|
};
|
|
2746
2756
|
|
|
@@ -2862,9 +2872,10 @@ function SettingsSubcard({
|
|
|
2862
2872
|
title,
|
|
2863
2873
|
children,
|
|
2864
2874
|
open = false,
|
|
2865
|
-
ariaLabel
|
|
2875
|
+
ariaLabel,
|
|
2876
|
+
icon = "settings"
|
|
2866
2877
|
}) {
|
|
2867
|
-
return /* @__PURE__ */ import_react17.default.createElement("details", { className: "dlv-settings-subcard", open, "aria-label": ariaLabel }, /* @__PURE__ */ import_react17.default.createElement("
|
|
2878
|
+
return /* @__PURE__ */ import_react17.default.createElement("details", { className: "dlv-settings-subcard", open }, /* @__PURE__ */ import_react17.default.createElement("summary", { "aria-label": ariaLabel }, /* @__PURE__ */ import_react17.default.createElement(Icon, { name: icon, className: "dlv-settings-subcard-icon" }), /* @__PURE__ */ import_react17.default.createElement("span", null, title), /* @__PURE__ */ import_react17.default.createElement(Icon, { name: "chevron", className: "dlv-settings-subcard-chevron" })), /* @__PURE__ */ import_react17.default.createElement("div", { className: "dlv-settings-subcard-body" }, children));
|
|
2868
2879
|
}
|
|
2869
2880
|
|
|
2870
2881
|
// src/shared/design-system/layout/SettingsTabs.tsx
|
|
@@ -2905,7 +2916,8 @@ function SettingsTabs({ label, tabs, active, idPrefix, onChange }) {
|
|
|
2905
2916
|
onClick: () => onChange(tab.id),
|
|
2906
2917
|
onKeyDown: (event) => onKeyDown(event, index)
|
|
2907
2918
|
},
|
|
2908
|
-
tab.
|
|
2919
|
+
tab.icon ? /* @__PURE__ */ import_react18.default.createElement(Icon, { name: tab.icon, className: "dlv-settings-tab-icon" }) : null,
|
|
2920
|
+
/* @__PURE__ */ import_react18.default.createElement("span", null, tab.label)
|
|
2909
2921
|
)));
|
|
2910
2922
|
}
|
|
2911
2923
|
|
|
@@ -3856,8 +3868,8 @@ function useConversationController(controller) {
|
|
|
3856
3868
|
var import_react23 = __toESM(require("react"), 1);
|
|
3857
3869
|
|
|
3858
3870
|
// src/modules/settings/services/releases.ts
|
|
3859
|
-
var CURRENT_VERSION = "0.3.
|
|
3860
|
-
var TESTED_DSH_VERSION = "0.
|
|
3871
|
+
var CURRENT_VERSION = "0.3.1";
|
|
3872
|
+
var TESTED_DSH_VERSION = "0.2.0-rc.2";
|
|
3861
3873
|
var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
|
|
3862
3874
|
var RELEASES_URL = `${REPOSITORY_URL}/releases`;
|
|
3863
3875
|
var LATEST_RELEASE_API_URL = "https://api.github.com/repos/victorwads/dsh-live-voice/releases/latest";
|
|
@@ -3865,7 +3877,7 @@ var RELEASE_CHECK_STORAGE_KEY = "dsh-live-voice.latest-release-check";
|
|
|
3865
3877
|
var RELEASE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3866
3878
|
var LIVE_VOICE_BADGE_URL = "https://cdn.simpleicons.org/npm/white";
|
|
3867
3879
|
var DSH_BADGE_URL = "https://cdn.simpleicons.org/deepseek/white";
|
|
3868
|
-
var TESTED_DSH_RELEASE_URL = `https://github.com/deepseek-ai/deepseek-harness/releases/tag/v${TESTED_DSH_VERSION}`;
|
|
3880
|
+
var TESTED_DSH_RELEASE_URL = `https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v${TESTED_DSH_VERSION}`;
|
|
3869
3881
|
function versionParts(version) {
|
|
3870
3882
|
const normalized = String(version || "").trim().replace(/^v/i, "").split("+", 1)[0];
|
|
3871
3883
|
const [core = "", prerelease = ""] = normalized.split("-", 2);
|
|
@@ -4084,7 +4096,8 @@ function PlaybackPolicySettings({ settings, updateSettings }) {
|
|
|
4084
4096
|
|
|
4085
4097
|
// src/modules/settings/sections/conversation/ConversationSettingsSection.tsx
|
|
4086
4098
|
function ConversationSettingsSection(props) {
|
|
4087
|
-
|
|
4099
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4100
|
+
return /* @__PURE__ */ import_react29.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react29.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react29.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "send" }, /* @__PURE__ */ import_react29.default.createElement("div", { className: "dlv-conversation-fields" }, /* @__PURE__ */ import_react29.default.createElement(DeliverySettings, { ...props }), /* @__PURE__ */ import_react29.default.createElement(HoldToTalkSettings, { ...props }), /* @__PURE__ */ import_react29.default.createElement(VoiceModeSettings, { ...props }), /* @__PURE__ */ import_react29.default.createElement(PlaybackPolicySettings, { ...props }), /* @__PURE__ */ import_react29.default.createElement(ConversationDelaySettings, { ...props })))));
|
|
4088
4101
|
}
|
|
4089
4102
|
|
|
4090
4103
|
// src/modules/settings/sections/recognition/RecognitionEngineSettings.tsx
|
|
@@ -4398,7 +4411,7 @@ function RecognitionEngineSettings({
|
|
|
4398
4411
|
checked: settings.recognitionAutoInstall !== false,
|
|
4399
4412
|
onChange: (event) => updateSettings({ recognitionAutoInstall: event.target.checked })
|
|
4400
4413
|
}
|
|
4401
|
-
) : /* @__PURE__ */ import_react32.default.createElement("p", { role: "status" }, recognition.browser.remoteServiceWarning())), engine === "whisper-http" && /* @__PURE__ */ import_react32.default.createElement(SettingsSubcard, { title: commons.connection.title() }, /* @__PURE__ */ import_react32.default.createElement("p", null, recognition.whisper.captureHelp()), /* @__PURE__ */ import_react32.default.createElement(WhisperSettings, { controller })), engine === "qwen-http" && /* @__PURE__ */ import_react32.default.createElement(SettingsSubcard, { title: commons.connection.title() }, /* @__PURE__ */ import_react32.default.createElement("p", null, recognition.qwen.captureHelp()), settings.engine !== "qwen-http" && /* @__PURE__ */ import_react32.default.createElement(QwenSettings, { controller })));
|
|
4414
|
+
) : /* @__PURE__ */ import_react32.default.createElement("p", { role: "status" }, recognition.browser.remoteServiceWarning())), engine === "whisper-http" && /* @__PURE__ */ import_react32.default.createElement(SettingsSubcard, { title: commons.connection.title(), icon: "link" }, /* @__PURE__ */ import_react32.default.createElement("p", null, recognition.whisper.captureHelp()), /* @__PURE__ */ import_react32.default.createElement(WhisperSettings, { controller })), engine === "qwen-http" && /* @__PURE__ */ import_react32.default.createElement(SettingsSubcard, { title: commons.connection.title(), icon: "link" }, /* @__PURE__ */ import_react32.default.createElement("p", null, recognition.qwen.captureHelp()), settings.engine !== "qwen-http" && /* @__PURE__ */ import_react32.default.createElement(QwenSettings, { controller })));
|
|
4402
4415
|
}
|
|
4403
4416
|
|
|
4404
4417
|
// src/modules/settings/sections/recognition/RecognitionFilterSettings.tsx
|
|
@@ -4406,7 +4419,7 @@ var import_react33 = __toESM(require("react"), 1);
|
|
|
4406
4419
|
function RecognitionFilterSettings({ settings, updateSettings }) {
|
|
4407
4420
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4408
4421
|
const enabled = settings.recognitionFilterEnabled !== false;
|
|
4409
|
-
return /* @__PURE__ */ import_react33.default.createElement(SettingsSubcard, { title: recognition.minimumWords.label() }, /* @__PURE__ */ import_react33.default.createElement(
|
|
4422
|
+
return /* @__PURE__ */ import_react33.default.createElement(SettingsSubcard, { title: recognition.minimumWords.label(), icon: "filter" }, /* @__PURE__ */ import_react33.default.createElement(
|
|
4410
4423
|
CheckboxField,
|
|
4411
4424
|
{
|
|
4412
4425
|
label: recognition.minimumWords.enabled(),
|
|
@@ -4445,7 +4458,7 @@ function SilenceDetectionSettings({ settings, updateSettings }) {
|
|
|
4445
4458
|
{
|
|
4446
4459
|
title: recognition.silenceDetection.label(),
|
|
4447
4460
|
ariaLabel: recognition.silenceDetection.title(),
|
|
4448
|
-
|
|
4461
|
+
icon: "pause"
|
|
4449
4462
|
},
|
|
4450
4463
|
/* @__PURE__ */ import_react34.default.createElement("p", null, recognition.silenceDetection.help()),
|
|
4451
4464
|
/* @__PURE__ */ import_react34.default.createElement(
|
|
@@ -4502,7 +4515,7 @@ var COMMANDS = [
|
|
|
4502
4515
|
function VoiceCommandSettings({ settings, updateSettings }) {
|
|
4503
4516
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4504
4517
|
const enabled = settings.voiceCommandsEnabled !== false;
|
|
4505
|
-
return /* @__PURE__ */ import_react35.default.createElement(SettingsSubcard, { title: recognition.commands.title() }, /* @__PURE__ */ import_react35.default.createElement(
|
|
4518
|
+
return /* @__PURE__ */ import_react35.default.createElement(SettingsSubcard, { title: recognition.commands.title(), icon: "send" }, /* @__PURE__ */ import_react35.default.createElement(
|
|
4506
4519
|
CheckboxField,
|
|
4507
4520
|
{
|
|
4508
4521
|
label: recognition.commands.enabled(),
|
|
@@ -4532,7 +4545,8 @@ function RecognitionStatus({ capabilities = {} }) {
|
|
|
4532
4545
|
|
|
4533
4546
|
// src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx
|
|
4534
4547
|
function RecognitionSettingsSection(props) {
|
|
4535
|
-
|
|
4548
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4549
|
+
return /* @__PURE__ */ import_react37.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react37.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react37.default.createElement(RecognitionStatus, { ...props }), /* @__PURE__ */ import_react37.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "mic" }, /* @__PURE__ */ import_react37.default.createElement(RecognitionEngineSettings, { ...props })), /* @__PURE__ */ import_react37.default.createElement(VoiceCommandSettings, { ...props }), /* @__PURE__ */ import_react37.default.createElement(RecognitionFilterSettings, { ...props }), /* @__PURE__ */ import_react37.default.createElement(SilenceDetectionSettings, { ...props })));
|
|
4536
4550
|
}
|
|
4537
4551
|
|
|
4538
4552
|
// src/modules/settings/sections/speak/OutputFilterSettings.tsx
|
|
@@ -4540,7 +4554,7 @@ var import_react38 = __toESM(require("react"), 1);
|
|
|
4540
4554
|
function OutputFilterSettings({ settings, updateSettings }) {
|
|
4541
4555
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4542
4556
|
const enabled = settings.outputCodeFilterEnabled !== false;
|
|
4543
|
-
return /* @__PURE__ */ import_react38.default.createElement(SettingsSubcard, { title: speak.filters.code.enabled() }, /* @__PURE__ */ import_react38.default.createElement(
|
|
4557
|
+
return /* @__PURE__ */ import_react38.default.createElement(SettingsSubcard, { title: speak.filters.code.enabled(), icon: "filter" }, /* @__PURE__ */ import_react38.default.createElement(
|
|
4544
4558
|
CheckboxField,
|
|
4545
4559
|
{
|
|
4546
4560
|
label: speak.filters.code.enabled(),
|
|
@@ -4678,7 +4692,7 @@ function SpeechAdvancedSettings({
|
|
|
4678
4692
|
options: outputOptions,
|
|
4679
4693
|
onChange: (event) => updateSettings({ outputDeviceId: event.target.value })
|
|
4680
4694
|
}
|
|
4681
|
-
) : /* @__PURE__ */ import_react40.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.outputHelp()), settings.engine === "qwen-http" && /* @__PURE__ */ import_react40.default.createElement(SettingsSubcard, { title: speak.qwen.connection() }, /* @__PURE__ */ import_react40.default.createElement(QwenSettings, { controller })), /* @__PURE__ */ import_react40.default.createElement(SettingsSubcard, { title: speak.agentContext.label() }, /* @__PURE__ */ import_react40.default.createElement(
|
|
4695
|
+
) : /* @__PURE__ */ import_react40.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.outputHelp()), settings.engine === "qwen-http" && /* @__PURE__ */ import_react40.default.createElement(SettingsSubcard, { title: speak.qwen.connection(), icon: "link" }, /* @__PURE__ */ import_react40.default.createElement(QwenSettings, { controller })), /* @__PURE__ */ import_react40.default.createElement(SettingsSubcard, { title: speak.agentContext.label(), icon: "settings" }, /* @__PURE__ */ import_react40.default.createElement(
|
|
4682
4696
|
CheckboxField,
|
|
4683
4697
|
{
|
|
4684
4698
|
label: speak.agentContext.enabled(),
|
|
@@ -4705,7 +4719,8 @@ function SpeechAdvancedSettings({
|
|
|
4705
4719
|
|
|
4706
4720
|
// src/modules/settings/sections/speak/SpeakSettingsSection.tsx
|
|
4707
4721
|
function SpeakSettingsSection(props) {
|
|
4708
|
-
|
|
4722
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4723
|
+
return /* @__PURE__ */ import_react41.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react41.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react41.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "speaker" }, /* @__PURE__ */ import_react41.default.createElement(SpeechEngineSettings, { ...props }), /* @__PURE__ */ import_react41.default.createElement(SpeechAdvancedSettings, { ...props })), /* @__PURE__ */ import_react41.default.createElement(OutputFilterSettings, { ...props })));
|
|
4709
4724
|
}
|
|
4710
4725
|
|
|
4711
4726
|
// src/modules/settings/components/SettingsHeader.tsx
|
|
@@ -4783,9 +4798,17 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4783
4798
|
const [activeTab, setActiveTab] = import_react44.default.useState("conversation");
|
|
4784
4799
|
const tabsId = import_react44.default.useId();
|
|
4785
4800
|
const tabs = [
|
|
4786
|
-
{ id: "speech", label: settingsLanguage.tabs.speak() },
|
|
4787
|
-
{
|
|
4788
|
-
|
|
4801
|
+
{ id: "speech", label: settingsLanguage.tabs.speak(), icon: "speaker" },
|
|
4802
|
+
{
|
|
4803
|
+
id: "recognition",
|
|
4804
|
+
label: settingsLanguage.tabs.recognition(),
|
|
4805
|
+
icon: "mic"
|
|
4806
|
+
},
|
|
4807
|
+
{
|
|
4808
|
+
id: "conversation",
|
|
4809
|
+
label: settingsLanguage.tabs.conversation(),
|
|
4810
|
+
icon: "send"
|
|
4811
|
+
}
|
|
4789
4812
|
];
|
|
4790
4813
|
const sectionProps = {
|
|
4791
4814
|
controller,
|
|
@@ -5285,10 +5308,10 @@ var styles = `
|
|
|
5285
5308
|
.dlv-settings h3,.dlv-settings p{margin:0}
|
|
5286
5309
|
.dlv-settings-heading{display:flex;align-items:center;gap:8px;min-width:0}.dlv-settings-heading h3{white-space:nowrap}.dlv-heading-divider{height:1px;min-width:12px;flex:1;background:var(--dsw-alias-border-l1)}.dlv-settings-heading .dlv-star-badge{flex:none}
|
|
5287
5310
|
.dlv-version-badges{display:flex;flex:none;gap:5px;align-items:center}.dlv-shields-badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:20px;gap:4px;padding:3px 5px;border:1px solid var(--dsw-alias-border-l1);border-radius:5px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:10px;font-weight:650;line-height:12px;text-decoration:none}.dlv-shields-badge img{display:block;width:12px;height:12px;max-width:100%}.dlv-shields-badge:focus-visible{border-radius:4px;outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.dlv-version-badge{display:inline-flex;align-items:center;min-height:24px;box-sizing:border-box;padding:3px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:650;line-height:16px;text-decoration:none}.dlv-version-badges a.dlv-version-badge:hover{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.dlv-star-badge{padding:4px 10px;color:var(--dsw-alias-label-primary)}.dlv-update-icon{margin-right:4px;font-size:14px;line-height:1}.dlv-star-icon{margin-right:4px;color:#f59e0b;font-size:14px;line-height:1;text-shadow:0 0 8px rgba(245,158,11,.28)}.dlv-version-badges a.dlv-star-badge:hover{border-color:#f59e0b;background:rgba(245,158,11,.08)}.dlv-update-badge{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}
|
|
5288
|
-
.dlv-settings-tabs{border-bottom:.5px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;gap:22px;overflow-x:auto}.dlv-settings-tab{position:relative;flex:none;padding:7px 1px 9px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:
|
|
5311
|
+
.dlv-settings-tabs{border-bottom:.5px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;gap:22px;overflow-x:auto}.dlv-settings-tab{position:relative;display:inline-flex;align-items:center;gap:6px;flex:none;padding:7px 1px 9px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:16px;font-weight:600;line-height:22px;cursor:pointer}.dlv-settings-tab-icon{width:17px;height:17px;flex:none}.dlv-settings-tab:hover,.dlv-settings-tab[data-active=true]{color:var(--dsw-alias-label-primary)}.dlv-settings-tab[data-active=true]:after,.dlv-settings-tab:focus-visible:after{content:"";position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary)}.dlv-settings-tab:focus-visible{border-radius:2px;outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;color:var(--dsw-alias-label-primary)}.dlv-settings-panel{min-width:0;padding-top:2px}.dlv-settings-panel[hidden]{display:none}.dlv-settings-group{margin:0;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;min-width:0;display:grid;gap:14px;padding:16px}.dlv-settings-group legend{padding:0 6px;font-weight:600;color:var(--dsw-alias-label-primary)}.dlv-settings-card-body{display:grid;gap:18px;padding:8px 0 12px}.dlv-settings-subcard{min-width:0;margin:8px 0 0}.dlv-settings-subcard>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);border-radius:6px 6px 0 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 38%,transparent);color:var(--dsw-alias-label-secondary);font-size:14px;font-weight:500;line-height:20px}.dlv-settings-subcard>summary::-webkit-details-marker{display:none}.dlv-settings-subcard>summary:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.dlv-settings-subcard-icon{width:16px;height:16px;flex:none}.dlv-settings-subcard-chevron{width:16px;height:16px;margin-left:auto;flex:none;transition:transform .15s}.dlv-settings-subcard[open]>summary .dlv-settings-subcard-chevron{transform:rotate(180deg)}.dlv-settings-subcard-body{display:grid;gap:16px;padding:18px 0 6px}
|
|
5289
5312
|
.dlv-settings label{display:grid;gap:6px;font-size:14px}.dlv-settings label.dlv-check{display:flex;align-items:center;gap:8px}.dlv-settings label.dlv-check input{width:auto}
|
|
5290
5313
|
.dlv-settings :is(input,select,textarea){box-sizing:border-box;width:100%;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;font:inherit}
|
|
5291
|
-
.dlv-settings small,.dlv-settings p{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}
|
|
5314
|
+
.dlv-settings small,.dlv-settings p{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}.dlv-settings .dlv-setting-description{line-height:1.65;margin:3px 0 5px}.dlv-settings .dlv-conversation-fields{display:grid;gap:20px;padding:4px 0}
|
|
5292
5315
|
.dlv-preset-group{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.dlv-settings label.dlv-preset{display:flex;align-items:flex-start;gap:8px;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;cursor:pointer}.dlv-settings label.dlv-preset:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.dlv-settings label.dlv-preset input{width:auto;margin-top:3px}.dlv-preset span{display:grid;gap:3px}.dlv-vad-summary{font-weight:500}
|
|
5293
5316
|
.dlv-settings-actions{display:flex;flex-wrap:wrap;gap:8px}.dlv-settings-actions button{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;cursor:pointer}.dlv-settings-actions button:disabled{opacity:.45;cursor:default}
|
|
5294
5317
|
@media(max-width:480px){.dlv-settings-heading{gap:6px}.dlv-heading-divider{min-width:4px}.dlv-settings-heading .dlv-star-badge{padding:3px 6px;font-size:10px}.dlv-preset-group{grid-template-columns:1fr}.dlv-pill{flex-wrap:wrap}.dlv-wave{flex-basis:90px}.dlv-status{flex-basis:100px}}
|
package/lib/server.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Application boundary
|
|
2
|
+
|
|
3
|
+
Read [ARCHITECTURE.md](ARCHITECTURE.md) before changing composition roots, routes, slots, or language registration. Read the repository [AGENTS.md](../../AGENTS.md) for product, validation, and authorization rules.
|
|
4
|
+
|
|
5
|
+
- Inspect the actual DSH extension API before changing an integration point. Preserve established slot names, IDs, ordering, injected services, authenticated same-origin routes, and browser/host separation.
|
|
6
|
+
- Keep UI copy in the typed catalogs under `client/i18n/`; update every supported language in alphabetical key order. Use DSH `ctx.locale`, not an independent preference. UI locale is independent of recognition/synthesis languages and configured voice-command phrases.
|
|
7
|
+
- Treat `package.json` as the source of truth for plugin and tested-DSH versions. Never hard-code these in the client.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Application architecture
|
|
2
|
+
|
|
3
|
+
`src/app` is the composition boundary between DSH and the voice-domain modules. Client and server entry points remain separate; host-only Node dependencies must never enter the browser bundle.
|
|
4
|
+
|
|
5
|
+
- `client/apply.tsx` injects DSH client services, creates session controllers and voice ownership, wires engines and settings, and disposes resources. `client/slotDefinitions.ts` and `client/registerSlots.tsx` own the established slot IDs, order, and registration. Preserve those contracts when changing placement.
|
|
6
|
+
- `server/apply.ts` composes host engines and providers. `server/registerRoutes.ts` registers authenticated same-origin routes; do not bypass their validation or expose host access directly to browser code.
|
|
7
|
+
- `client/i18n` owns the only UI translation tree: typed, alphabetized catalogs for every supported locale, runtime selection synchronized with `ctx.locale`, DSH registration, and the boundary wrapping slot components. UI language must not change STT/TTS language or user-defined command phrases.
|
|
8
|
+
- Styles are assembled from `src/styles/index.ts`; design-system primitives live under `src/shared/design-system` rather than here.
|
|
9
|
+
|
|
10
|
+
Read [the repository overview](../../docs/ARCHITECTURE.md) for dependency direction and stable contracts, and the relevant module architecture before editing feature behavior. Read [application agent guidance](AGENTS.md) for operational constraints.
|
|
@@ -136,6 +136,7 @@ export interface LiveVoiceTranslation extends Translation {
|
|
|
136
136
|
'dsh-live-voice.settings.delivery.toggle': string;
|
|
137
137
|
'dsh-live-voice.settings.engine.refresh': string;
|
|
138
138
|
'dsh-live-voice.settings.filters.title': string;
|
|
139
|
+
'dsh-live-voice.settings.general.title': string;
|
|
139
140
|
'dsh-live-voice.settings.tabs.conversation': string;
|
|
140
141
|
'dsh-live-voice.settings.tabs.recognition': string;
|
|
141
142
|
'dsh-live-voice.settings.tabs.speak': string;
|
|
@@ -162,6 +162,7 @@ const en: LiveVoiceTranslation = {
|
|
|
162
162
|
'dsh-live-voice.settings.delivery.toggle': 'Automatic delivery mode',
|
|
163
163
|
'dsh-live-voice.settings.engine.refresh': 'Refresh available engines',
|
|
164
164
|
'dsh-live-voice.settings.filters.title': 'Filtering',
|
|
165
|
+
'dsh-live-voice.settings.general.title': 'General',
|
|
165
166
|
'dsh-live-voice.settings.tabs.conversation': 'Conversation',
|
|
166
167
|
'dsh-live-voice.settings.tabs.recognition': 'Speech recognition',
|
|
167
168
|
'dsh-live-voice.settings.tabs.speak': 'Speech',
|
|
@@ -175,6 +175,7 @@ const es: LiveVoiceTranslation = {
|
|
|
175
175
|
'dsh-live-voice.settings.delivery.toggle': 'Modo de envío automático',
|
|
176
176
|
'dsh-live-voice.settings.engine.refresh': 'Actualizar los motores disponibles',
|
|
177
177
|
'dsh-live-voice.settings.filters.title': 'Filtrado',
|
|
178
|
+
'dsh-live-voice.settings.general.title': 'General',
|
|
178
179
|
'dsh-live-voice.settings.tabs.conversation': 'Conversación',
|
|
179
180
|
'dsh-live-voice.settings.tabs.recognition': 'Reconocimiento de voz',
|
|
180
181
|
'dsh-live-voice.settings.tabs.speak': 'Síntesis de voz',
|
|
@@ -178,6 +178,7 @@ const fr: LiveVoiceTranslation = {
|
|
|
178
178
|
'dsh-live-voice.settings.delivery.toggle': 'Mode d’envoi automatique',
|
|
179
179
|
'dsh-live-voice.settings.engine.refresh': 'Actualiser les moteurs disponibles',
|
|
180
180
|
'dsh-live-voice.settings.filters.title': 'Filtrage',
|
|
181
|
+
'dsh-live-voice.settings.general.title': 'Général',
|
|
181
182
|
'dsh-live-voice.settings.tabs.conversation': 'Conversation',
|
|
182
183
|
'dsh-live-voice.settings.tabs.recognition': 'Reconnaissance vocale',
|
|
183
184
|
'dsh-live-voice.settings.tabs.speak': 'Synthèse vocale',
|
|
@@ -165,6 +165,7 @@ const hi: LiveVoiceTranslation = {
|
|
|
165
165
|
'dsh-live-voice.settings.delivery.toggle': 'स्वचालित भेजने का मोड',
|
|
166
166
|
'dsh-live-voice.settings.engine.refresh': 'उपलब्ध इंजन रीफ़्रेश करें',
|
|
167
167
|
'dsh-live-voice.settings.filters.title': 'फ़िल्टरिंग',
|
|
168
|
+
'dsh-live-voice.settings.general.title': 'सामान्य',
|
|
168
169
|
'dsh-live-voice.settings.tabs.conversation': 'बातचीत',
|
|
169
170
|
'dsh-live-voice.settings.tabs.recognition': 'वाक् पहचान',
|
|
170
171
|
'dsh-live-voice.settings.tabs.speak': 'वाचन',
|
|
@@ -169,6 +169,7 @@ const ptBR: LiveVoiceTranslation = {
|
|
|
169
169
|
'dsh-live-voice.settings.delivery.toggle': 'Modo de entrega automática',
|
|
170
170
|
'dsh-live-voice.settings.engine.refresh': 'Atualizar mecanismos disponíveis',
|
|
171
171
|
'dsh-live-voice.settings.filters.title': 'Filtragem',
|
|
172
|
+
'dsh-live-voice.settings.general.title': 'Gerais',
|
|
172
173
|
'dsh-live-voice.settings.tabs.conversation': 'Conversa',
|
|
173
174
|
'dsh-live-voice.settings.tabs.recognition': 'Reconhecimento de voz',
|
|
174
175
|
'dsh-live-voice.settings.tabs.speak': 'Fala',
|
|
@@ -156,6 +156,7 @@ const zh: LiveVoiceTranslation = {
|
|
|
156
156
|
'dsh-live-voice.settings.delivery.toggle': '自动发送模式',
|
|
157
157
|
'dsh-live-voice.settings.engine.refresh': '刷新可用引擎',
|
|
158
158
|
'dsh-live-voice.settings.filters.title': '过滤',
|
|
159
|
+
'dsh-live-voice.settings.general.title': '常规',
|
|
159
160
|
'dsh-live-voice.settings.tabs.conversation': '对话',
|
|
160
161
|
'dsh-live-voice.settings.tabs.recognition': '语音识别',
|
|
161
162
|
'dsh-live-voice.settings.tabs.speak': '语音合成',
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module guidance
|
|
2
|
+
|
|
3
|
+
Read the nearest module's `ARCHITECTURE.md` before changing its behavior. `src/modules` owns product policy, feature UI, models, providers, and services. Modules may use `src/shared` and the centralized `src/app/client/i18n` API, but must not register DSH slots or host routes; composition belongs to `src/app`.
|
|
4
|
+
|
|
5
|
+
Preserve local-first STT and TTS options without claiming DSH itself is offline. Track capture/recognition, synthesis/playback, and agent generation separately. Preserve pause, resume, cancel, ownership, interruption, and stale asynchronous result semantics. Avoid logging raw audio or transcripts by default. Consult [VOICE-LIFECYCLE.md](../../docs/VOICE-LIFECYCLE.md) for the behavioral contract and [root guidance](../../AGENTS.md) for testing and authorization.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Conversation module
|
|
2
|
+
|
|
3
|
+
`models/chat.ts` interprets DSH chat turns and pending questions. `hooks/useConversationController.ts` subscribes to session state; `hooks/useConversationActions.ts` maps user actions onto the controller. `components/` renders composer controls, microphone state, playback controls, status, and waveform; `createConversationComponents.tsx` composes them for the application slot boundary.
|
|
4
|
+
|
|
5
|
+
Keep UI components session-facing rather than importing host engines or registering slots. Preserve the DSH composer contract: `useInput` provides subscribed input and `inputActions.setDraft()` updates the composer; a recognition result must not be lost to stale interim updates. History pagination must not count as a new user turn. Do not treat sending, recognition, playback, or agent generation as one state. See [voice lifecycle](../../../docs/VOICE-LIFECYCLE.md) before changing interruption or turn-taking.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Core voice domain
|
|
2
|
+
|
|
3
|
+
`core` owns policy and reusable voice-domain state, not DSH slot/route registration or provider-specific presentation. `coordinator.ts` serializes input transitions, owns conversation state and speech queues, and invalidates stale async work with generations. `ownership.ts` arbitrates microphone and speaker use between sessions and Settings self-tests. `transcript.ts` manages draft/final recognition text, `microphone.ts` manages capture and activity, `filters.ts` handles voice commands and speech text filtering, and `settings.ts` normalizes persisted values and defaults.
|
|
4
|
+
|
|
5
|
+
The `qwen/` subtree contains shared Qwen host configuration and its settings UI; recognition and speaking adapters live in their respective modules. Keep the client/host dependency graphs separate. Preserve the persisted `dsh-live-voice.settings` key and normalization compatibility. Distinguish capture, recognition, playback, and generation; do not conflate pausing with cancellation or resume obsolete speech after a new turn. Consult [voice lifecycle](../../../docs/VOICE-LIFECYCLE.md) for expected transitions.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Recognition module
|
|
2
|
+
|
|
3
|
+
`engines/browser/BrowserRecognitionEngine.ts` adapts browser speech recognition and reports local-pack/browser capabilities. `engines/qwen/QwenRecognitionEngine.ts` and `engines/whisper/WhisperRecognitionEngine.ts` adapt local-first HTTP recognition; host bridges live beside the relevant adapters (Whisper here, shared Qwen configuration in `../core/qwen`). `components/RecognitionCapabilityStatus.tsx` and engine settings UI present capability and configuration states.
|
|
4
|
+
|
|
5
|
+
Distinguish microphone capture and permission from recognition engine availability. Do not infer feature support merely from operating system. Bound audio and host requests, keep host-only code out of the browser bundle, and abort/ignore stale results after cancellation. Recognition language and voice-command phrases are independent of the interface locale. Browser simulations are not a physical microphone or model acceptance test.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Settings module
|
|
2
|
+
|
|
3
|
+
`components/LiveVoiceSettings.tsx` owns the tabbed Settings shell; `sections/{conversation,recognition,speak}` own feature controls. `hooks/useLiveVoiceSettings.ts` subscribes to controller state and dispatches updates, while `hooks/useAudioDevices.ts` and `hooks/useReleaseStatus.ts` handle capability-adjacent data. `models/settingsStorage.ts` owns storage integration; `services/releases.ts` supports version metadata. Shared fields, layout, icons, and feedback live in `src/shared/design-system`.
|
|
4
|
+
|
|
5
|
+
Preserve existing setting names, default values, handlers, and normalization in `../core/settings.ts`; reorganizing presentation must not silently change policy. Keep transient capability/permission messages visible even when an options section is collapsed. User-facing copy and accessible names belong in all typed catalogs under `src/app/client/i18n`; UI locale does not select recognition or speech language. The preview in `__previewjs__/LiveVoicePreviews.tsx` exercises presentation with a fake controller, not a physical microphone or authenticated DSH runtime. Run `npm test` after changes and check the preview for layout and interaction.
|
|
@@ -17,9 +17,17 @@ export function LiveVoiceSettings({ controller, onClose }: { controller: any; on
|
|
|
17
17
|
const [activeTab, setActiveTab] = React.useState('conversation');
|
|
18
18
|
const tabsId = React.useId();
|
|
19
19
|
const tabs = [
|
|
20
|
-
{ id: 'speech', label: (settingsLanguage as any).tabs.speak() },
|
|
21
|
-
{
|
|
22
|
-
|
|
20
|
+
{ id: 'speech', label: (settingsLanguage as any).tabs.speak(), icon: 'speaker' as const },
|
|
21
|
+
{
|
|
22
|
+
id: 'recognition',
|
|
23
|
+
label: (settingsLanguage as any).tabs.recognition(),
|
|
24
|
+
icon: 'mic' as const,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'conversation',
|
|
28
|
+
label: (settingsLanguage as any).tabs.conversation(),
|
|
29
|
+
icon: 'send' as const,
|
|
30
|
+
},
|
|
23
31
|
];
|
|
24
32
|
const sectionProps = {
|
|
25
33
|
controller,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useLanguage } from '../../../../app/client/i18n/index.js';
|
|
3
|
+
import { SettingsCard, SettingsSubcard } from '../../../../shared/design-system/index.js';
|
|
3
4
|
import { DeliverySettings } from './DeliverySettings.js';
|
|
4
5
|
import { HoldToTalkSettings } from './HoldToTalkSettings.js';
|
|
5
6
|
import { VoiceModeSettings } from './VoiceModeSettings.js';
|
|
@@ -9,14 +10,19 @@ export function ConversationSettingsSection(props: {
|
|
|
9
10
|
settings: any;
|
|
10
11
|
updateSettings(value: any): void;
|
|
11
12
|
}) {
|
|
13
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
12
14
|
return (
|
|
13
15
|
<SettingsCard>
|
|
14
16
|
<div className="dlv-settings-card-body">
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<SettingsSubcard title={(settingsLanguage as any).general.title()} icon="send">
|
|
18
|
+
<div className="dlv-conversation-fields">
|
|
19
|
+
<DeliverySettings {...props} />
|
|
20
|
+
<HoldToTalkSettings {...props} />
|
|
21
|
+
<VoiceModeSettings {...props} />
|
|
22
|
+
<PlaybackPolicySettings {...props} />
|
|
23
|
+
<ConversationDelaySettings {...props} />
|
|
24
|
+
</div>
|
|
25
|
+
</SettingsSubcard>
|
|
20
26
|
</div>
|
|
21
27
|
</SettingsCard>
|
|
22
28
|
);
|
|
@@ -93,13 +93,13 @@ export function RecognitionEngineSettings({
|
|
|
93
93
|
</>
|
|
94
94
|
)}
|
|
95
95
|
{engine === 'whisper-http' && (
|
|
96
|
-
<SettingsSubcard title={(commons as any).connection.title()}>
|
|
96
|
+
<SettingsSubcard title={(commons as any).connection.title()} icon="link">
|
|
97
97
|
<p>{(recognition as any).whisper.captureHelp()}</p>
|
|
98
98
|
<WhisperSettings controller={controller} />
|
|
99
99
|
</SettingsSubcard>
|
|
100
100
|
)}
|
|
101
101
|
{engine === 'qwen-http' && (
|
|
102
|
-
<SettingsSubcard title={(commons as any).connection.title()}>
|
|
102
|
+
<SettingsSubcard title={(commons as any).connection.title()} icon="link">
|
|
103
103
|
<p>{(recognition as any).qwen.captureHelp()}</p>
|
|
104
104
|
{settings.engine !== 'qwen-http' && <QwenSettings controller={controller} />}
|
|
105
105
|
</SettingsSubcard>
|
|
@@ -9,7 +9,7 @@ export function RecognitionFilterSettings({ settings, updateSettings }: any) {
|
|
|
9
9
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
10
10
|
const enabled = settings.recognitionFilterEnabled !== false;
|
|
11
11
|
return (
|
|
12
|
-
<SettingsSubcard title={(recognition as any).minimumWords.label()}>
|
|
12
|
+
<SettingsSubcard title={(recognition as any).minimumWords.label()} icon="filter">
|
|
13
13
|
<CheckboxField
|
|
14
14
|
label={(recognition as any).minimumWords.enabled()}
|
|
15
15
|
description={(recognition as any).minimumWords.help()}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useLanguage } from '../../../../app/client/i18n/index.js';
|
|
3
|
+
import { SettingsCard, SettingsSubcard } from '../../../../shared/design-system/index.js';
|
|
3
4
|
import { RecognitionEngineSettings } from './RecognitionEngineSettings.js';
|
|
4
5
|
import { RecognitionFilterSettings } from './RecognitionFilterSettings.js';
|
|
5
6
|
import { SilenceDetectionSettings } from './SilenceDetectionSettings.js';
|
|
6
7
|
import { VoiceCommandSettings } from './VoiceCommandSettings.js';
|
|
7
8
|
import { RecognitionStatus } from './RecognitionStatus.js';
|
|
8
9
|
export function RecognitionSettingsSection(props: any) {
|
|
10
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
9
11
|
return (
|
|
10
12
|
<SettingsCard>
|
|
11
13
|
<div className="dlv-settings-card-body">
|
|
12
|
-
<
|
|
14
|
+
<RecognitionStatus {...props} />
|
|
15
|
+
<SettingsSubcard title={(settingsLanguage as any).general.title()} icon="mic">
|
|
16
|
+
<RecognitionEngineSettings {...props} />
|
|
17
|
+
</SettingsSubcard>
|
|
13
18
|
<VoiceCommandSettings {...props} />
|
|
14
19
|
<RecognitionFilterSettings {...props} />
|
|
15
20
|
<SilenceDetectionSettings {...props} />
|
|
16
|
-
<RecognitionStatus {...props} />
|
|
17
21
|
</div>
|
|
18
22
|
</SettingsCard>
|
|
19
23
|
);
|
|
@@ -10,7 +10,7 @@ export function SilenceDetectionSettings({ settings, updateSettings }: any) {
|
|
|
10
10
|
<SettingsSubcard
|
|
11
11
|
title={(recognition as any).silenceDetection.label()}
|
|
12
12
|
ariaLabel={(recognition as any).silenceDetection.title()}
|
|
13
|
-
|
|
13
|
+
icon="pause"
|
|
14
14
|
>
|
|
15
15
|
<p>{(recognition as any).silenceDetection.help()}</p>
|
|
16
16
|
<NumberField
|
|
@@ -18,7 +18,7 @@ export function VoiceCommandSettings({ settings, updateSettings }: any) {
|
|
|
18
18
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
19
19
|
const enabled = settings.voiceCommandsEnabled !== false;
|
|
20
20
|
return (
|
|
21
|
-
<SettingsSubcard title={(recognition as any).commands.title()}>
|
|
21
|
+
<SettingsSubcard title={(recognition as any).commands.title()} icon="send">
|
|
22
22
|
<CheckboxField
|
|
23
23
|
label={(recognition as any).commands.enabled()}
|
|
24
24
|
checked={enabled}
|
|
@@ -10,7 +10,7 @@ export function OutputFilterSettings({ settings, updateSettings }: any) {
|
|
|
10
10
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
11
11
|
const enabled = settings.outputCodeFilterEnabled !== false;
|
|
12
12
|
return (
|
|
13
|
-
<SettingsSubcard title={(speak as any).filters.code.enabled()}>
|
|
13
|
+
<SettingsSubcard title={(speak as any).filters.code.enabled()} icon="filter">
|
|
14
14
|
<CheckboxField
|
|
15
15
|
label={(speak as any).filters.code.enabled()}
|
|
16
16
|
checked={enabled}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useLanguage } from '../../../../app/client/i18n/index.js';
|
|
3
|
+
import { SettingsCard, SettingsSubcard } from '../../../../shared/design-system/index.js';
|
|
3
4
|
import { OutputFilterSettings } from './OutputFilterSettings.js';
|
|
4
5
|
import { SpeechEngineSettings } from './SpeechEngineSettings.js';
|
|
5
6
|
import { SpeechAdvancedSettings } from './SpeechAdvancedSettings.js';
|
|
6
7
|
export function SpeakSettingsSection(props: any) {
|
|
8
|
+
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
7
9
|
return (
|
|
8
10
|
<SettingsCard>
|
|
9
11
|
<div className="dlv-settings-card-body">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
+
<SettingsSubcard title={(settingsLanguage as any).general.title()} icon="speaker">
|
|
13
|
+
<SpeechEngineSettings {...props} />
|
|
14
|
+
<SpeechAdvancedSettings {...props} />
|
|
15
|
+
</SettingsSubcard>
|
|
12
16
|
<OutputFilterSettings {...props} />
|
|
13
17
|
</div>
|
|
14
18
|
</SettingsCard>
|
|
@@ -39,11 +39,11 @@ export function SpeechAdvancedSettings({
|
|
|
39
39
|
<p className="dlv-setting-description">{(speak as any).macos.outputHelp()}</p>
|
|
40
40
|
)}
|
|
41
41
|
{settings.engine === 'qwen-http' && (
|
|
42
|
-
<SettingsSubcard title={(speak as any).qwen.connection()}>
|
|
42
|
+
<SettingsSubcard title={(speak as any).qwen.connection()} icon="link">
|
|
43
43
|
<QwenSettings controller={controller} />
|
|
44
44
|
</SettingsSubcard>
|
|
45
45
|
)}
|
|
46
|
-
<SettingsSubcard title={(speak as any).agentContext.label()}>
|
|
46
|
+
<SettingsSubcard title={(speak as any).agentContext.label()} icon="settings">
|
|
47
47
|
<CheckboxField
|
|
48
48
|
label={(speak as any).agentContext.enabled()}
|
|
49
49
|
checked={settings.agentVoiceContextEnabled !== false}
|
|
@@ -13,7 +13,7 @@ export const RELEASE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
|
13
13
|
|
|
14
14
|
export const LIVE_VOICE_BADGE_URL = 'https://cdn.simpleicons.org/npm/white';
|
|
15
15
|
export const DSH_BADGE_URL = 'https://cdn.simpleicons.org/deepseek/white';
|
|
16
|
-
export const TESTED_DSH_RELEASE_URL = `https://github.com/deepseek-ai/deepseek-harness/releases/tag/v${TESTED_DSH_VERSION}`;
|
|
16
|
+
export const TESTED_DSH_RELEASE_URL = `https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v${TESTED_DSH_VERSION}`;
|
|
17
17
|
|
|
18
18
|
function versionParts(version) {
|
|
19
19
|
const normalized = String(version || '')
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Speaking module
|
|
2
|
+
|
|
3
|
+
`services/speechQueue.ts` schedules speech independently of capture and agent generation. `engines/browser/BrowserSpeakingEngine.ts` uses browser voices, `engines/qwen/QwenSpeakingEngine.ts` uses the Qwen HTTP bridge, and `engines/say/` uses the macOS host speaker. `engines/audio/` manages host audio playback and transcoding. `qwen/QwenSpeakingSettings.tsx` presents engine-specific settings.
|
|
4
|
+
|
|
5
|
+
Keep provider adapters separate from conversation policy and DSH composition. Maintain truthful pause/resume capability and distinguish pause from stop/cancel. Abort old operations and prevent stale synthesized audio from beginning after a newer turn or cancellation. Do not let a Settings speech test bypass page-level ownership. Local TTS is an option; it does not imply the DSH model runs offline.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Shared design system
|
|
2
|
+
|
|
3
|
+
`design-system/` contains domain-independent React primitives: buttons, form fields, feedback, layout, and SVG icons. It must never import feature modules or DSH integration. Modules supply translated strings and handlers; shared components only render and dispatch them. Icons use `currentColor` so they follow surrounding text across themes.
|
|
4
|
+
|
|
5
|
+
Settings layouts include accessible cards, tab navigation, and native disclosure sections. Keep `<details>/<summary>` keyboard behavior and ARIA semantics when changing a subsection; headings and icons should not become separate unlabeled controls. Shared primitives should not own persisted settings, product policy, or translation catalogs.
|
|
@@ -10,6 +10,10 @@ export const iconPaths = {
|
|
|
10
10
|
skipNext: 'M5 5l10 7-10 7V5M19 5v14',
|
|
11
11
|
send: 'M3 11.5L21 3l-8.5 18-2-7.5L3 11.5zm7.5 2L21 3',
|
|
12
12
|
queue: 'M5 6h14M5 12h10M5 18h6M18 15v6M15 18h6',
|
|
13
|
+
settings: 'M4 7h16M4 17h16M8 4v6M16 14v6',
|
|
14
|
+
filter: 'M3 5h18l-7 8v6l-4 2v-8L3 5z',
|
|
15
|
+
link: 'M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1.7 1.7M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1.7-1.7',
|
|
16
|
+
chevron: 'M6 9l6 6 6-6',
|
|
13
17
|
close: 'M6 6l12 12M18 6 6 18',
|
|
14
18
|
} as const;
|
|
15
19
|
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Icon, type IconName } from '../icons/index.js';
|
|
3
|
+
|
|
2
4
|
export function SettingsSubcard({
|
|
3
5
|
title,
|
|
4
6
|
children,
|
|
5
7
|
open = false,
|
|
6
8
|
ariaLabel,
|
|
7
|
-
|
|
9
|
+
icon = 'settings',
|
|
10
|
+
}: React.PropsWithChildren<{
|
|
11
|
+
title: string;
|
|
12
|
+
open?: boolean;
|
|
13
|
+
ariaLabel?: string;
|
|
14
|
+
icon?: IconName;
|
|
15
|
+
}>) {
|
|
8
16
|
return (
|
|
9
|
-
<details className="dlv-settings-subcard" open={open}
|
|
10
|
-
<summary
|
|
17
|
+
<details className="dlv-settings-subcard" open={open}>
|
|
18
|
+
<summary aria-label={ariaLabel}>
|
|
19
|
+
<Icon name={icon} className="dlv-settings-subcard-icon" />
|
|
20
|
+
<span>{title}</span>
|
|
21
|
+
<Icon name="chevron" className="dlv-settings-subcard-chevron" />
|
|
22
|
+
</summary>
|
|
11
23
|
<div className="dlv-settings-subcard-body">{children}</div>
|
|
12
24
|
</details>
|
|
13
25
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { Icon, type IconName } from '../icons/index.js';
|
|
2
3
|
|
|
3
|
-
export type SettingsTab = { id: string; label: string };
|
|
4
|
+
export type SettingsTab = { id: string; label: string; icon?: IconName };
|
|
4
5
|
export type SettingsTabsProps = {
|
|
5
6
|
label: string;
|
|
6
7
|
tabs: readonly SettingsTab[];
|
|
@@ -46,7 +47,8 @@ export function SettingsTabs({ label, tabs, active, idPrefix, onChange }: Settin
|
|
|
46
47
|
onClick={() => onChange(tab.id)}
|
|
47
48
|
onKeyDown={(event) => onKeyDown(event, index)}
|
|
48
49
|
>
|
|
49
|
-
{tab.
|
|
50
|
+
{tab.icon ? <Icon name={tab.icon} className="dlv-settings-tab-icon" /> : null}
|
|
51
|
+
<span>{tab.label}</span>
|
|
50
52
|
</button>
|
|
51
53
|
))}
|
|
52
54
|
</div>
|
package/src/styles/index.ts
CHANGED
|
@@ -31,10 +31,10 @@ export const styles = `
|
|
|
31
31
|
.dlv-settings h3,.dlv-settings p{margin:0}
|
|
32
32
|
.dlv-settings-heading{display:flex;align-items:center;gap:8px;min-width:0}.dlv-settings-heading h3{white-space:nowrap}.dlv-heading-divider{height:1px;min-width:12px;flex:1;background:var(--dsw-alias-border-l1)}.dlv-settings-heading .dlv-star-badge{flex:none}
|
|
33
33
|
.dlv-version-badges{display:flex;flex:none;gap:5px;align-items:center}.dlv-shields-badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:20px;gap:4px;padding:3px 5px;border:1px solid var(--dsw-alias-border-l1);border-radius:5px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:10px;font-weight:650;line-height:12px;text-decoration:none}.dlv-shields-badge img{display:block;width:12px;height:12px;max-width:100%}.dlv-shields-badge:focus-visible{border-radius:4px;outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.dlv-version-badge{display:inline-flex;align-items:center;min-height:24px;box-sizing:border-box;padding:3px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:650;line-height:16px;text-decoration:none}.dlv-version-badges a.dlv-version-badge:hover{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.dlv-star-badge{padding:4px 10px;color:var(--dsw-alias-label-primary)}.dlv-update-icon{margin-right:4px;font-size:14px;line-height:1}.dlv-star-icon{margin-right:4px;color:#f59e0b;font-size:14px;line-height:1;text-shadow:0 0 8px rgba(245,158,11,.28)}.dlv-version-badges a.dlv-star-badge:hover{border-color:#f59e0b;background:rgba(245,158,11,.08)}.dlv-update-badge{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}
|
|
34
|
-
.dlv-settings-tabs{border-bottom:.5px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;gap:22px;overflow-x:auto}.dlv-settings-tab{position:relative;flex:none;padding:7px 1px 9px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:
|
|
34
|
+
.dlv-settings-tabs{border-bottom:.5px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;gap:22px;overflow-x:auto}.dlv-settings-tab{position:relative;display:inline-flex;align-items:center;gap:6px;flex:none;padding:7px 1px 9px;border:0;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:16px;font-weight:600;line-height:22px;cursor:pointer}.dlv-settings-tab-icon{width:17px;height:17px;flex:none}.dlv-settings-tab:hover,.dlv-settings-tab[data-active=true]{color:var(--dsw-alias-label-primary)}.dlv-settings-tab[data-active=true]:after,.dlv-settings-tab:focus-visible:after{content:"";position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary)}.dlv-settings-tab:focus-visible{border-radius:2px;outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;color:var(--dsw-alias-label-primary)}.dlv-settings-panel{min-width:0;padding-top:2px}.dlv-settings-panel[hidden]{display:none}.dlv-settings-group{margin:0;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;min-width:0;display:grid;gap:14px;padding:16px}.dlv-settings-group legend{padding:0 6px;font-weight:600;color:var(--dsw-alias-label-primary)}.dlv-settings-card-body{display:grid;gap:18px;padding:8px 0 12px}.dlv-settings-subcard{min-width:0;margin:8px 0 0}.dlv-settings-subcard>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);border-radius:6px 6px 0 0;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 38%,transparent);color:var(--dsw-alias-label-secondary);font-size:14px;font-weight:500;line-height:20px}.dlv-settings-subcard>summary::-webkit-details-marker{display:none}.dlv-settings-subcard>summary:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.dlv-settings-subcard-icon{width:16px;height:16px;flex:none}.dlv-settings-subcard-chevron{width:16px;height:16px;margin-left:auto;flex:none;transition:transform .15s}.dlv-settings-subcard[open]>summary .dlv-settings-subcard-chevron{transform:rotate(180deg)}.dlv-settings-subcard-body{display:grid;gap:16px;padding:18px 0 6px}
|
|
35
35
|
.dlv-settings label{display:grid;gap:6px;font-size:14px}.dlv-settings label.dlv-check{display:flex;align-items:center;gap:8px}.dlv-settings label.dlv-check input{width:auto}
|
|
36
36
|
.dlv-settings :is(input,select,textarea){box-sizing:border-box;width:100%;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;font:inherit}
|
|
37
|
-
.dlv-settings small,.dlv-settings p{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}
|
|
37
|
+
.dlv-settings small,.dlv-settings p{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}.dlv-settings .dlv-setting-description{line-height:1.65;margin:3px 0 5px}.dlv-settings .dlv-conversation-fields{display:grid;gap:20px;padding:4px 0}
|
|
38
38
|
.dlv-preset-group{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.dlv-settings label.dlv-preset{display:flex;align-items:flex-start;gap:8px;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;cursor:pointer}.dlv-settings label.dlv-preset:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.dlv-settings label.dlv-preset input{width:auto;margin-top:3px}.dlv-preset span{display:grid;gap:3px}.dlv-vad-summary{font-weight:500}
|
|
39
39
|
.dlv-settings-actions{display:flex;flex-wrap:wrap;gap:8px}.dlv-settings-actions button{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;cursor:pointer}.dlv-settings-actions button:disabled{opacity:.45;cursor:default}
|
|
40
40
|
@media(max-width:480px){.dlv-settings-heading{gap:6px}.dlv-heading-divider{min-width:4px}.dlv-settings-heading .dlv-star-badge{padding:3px 6px;font-size:10px}.dlv-preset-group{grid-template-columns:1fr}.dlv-pill{flex-wrap:wrap}.dlv-wave{flex-basis:90px}.dlv-status{flex-basis:100px}}
|