@jingyi0605/codingns4dsh 0.2.0-rc3 → 0.2.1-beta.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/README.en.md +6 -6
- package/README.md +42 -29
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts.map +1 -1
- package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js +2 -2
- package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js.map +1 -1
- package/data/build/dist/bootstrap/index.d.ts +1 -1
- package/data/build/dist/bootstrap/index.d.ts.map +1 -1
- package/data/build/dist/client/bundle.js +3164 -181
- package/data/build/dist/client/bundle.js.map +1 -1
- package/data/build/dist/client/cli-catalog.d.ts +1 -1
- package/data/build/dist/client/cli-catalog.d.ts.map +1 -1
- package/data/build/dist/client/cli-catalog.js +3 -3
- package/data/build/dist/client/cli-catalog.js.map +1 -1
- package/data/build/dist/client/cli-slots.d.ts +4 -0
- package/data/build/dist/client/cli-slots.d.ts.map +1 -1
- package/data/build/dist/client/cli-slots.js +70 -9
- package/data/build/dist/client/cli-slots.js.map +1 -1
- package/data/build/dist/client/context-breakdown-dom.d.ts +1 -1
- package/data/build/dist/client/context-breakdown-dom.d.ts.map +1 -1
- package/data/build/dist/client/context-breakdown-dom.js +1 -1
- package/data/build/dist/client/context-breakdown-dom.js.map +1 -1
- package/data/build/dist/client/features/assistant-panel.d.ts +5 -0
- package/data/build/dist/client/features/assistant-panel.d.ts.map +1 -0
- package/data/build/dist/client/features/assistant-panel.js +127 -0
- package/data/build/dist/client/features/assistant-panel.js.map +1 -0
- package/data/build/dist/client/features/global-voice-assistant.d.ts +10 -0
- package/data/build/dist/client/features/global-voice-assistant.d.ts.map +1 -0
- package/data/build/dist/client/features/global-voice-assistant.js +330 -0
- package/data/build/dist/client/features/global-voice-assistant.js.map +1 -0
- package/data/build/dist/client/features/index.d.ts +2 -0
- package/data/build/dist/client/features/index.d.ts.map +1 -1
- package/data/build/dist/client/features/index.js +4 -0
- package/data/build/dist/client/features/index.js.map +1 -1
- package/data/build/dist/client/features/lan-access.d.ts.map +1 -1
- package/data/build/dist/client/features/lan-access.js +3 -1
- package/data/build/dist/client/features/lan-access.js.map +1 -1
- package/data/build/dist/client/features/types.d.ts +4 -1
- package/data/build/dist/client/features/types.d.ts.map +1 -1
- package/data/build/dist/client/features/voice-conversation-dialog.d.ts +20 -0
- package/data/build/dist/client/features/voice-conversation-dialog.d.ts.map +1 -0
- package/data/build/dist/client/features/voice-conversation-dialog.js +59 -0
- package/data/build/dist/client/features/voice-conversation-dialog.js.map +1 -0
- package/data/build/dist/client/features/voice-initialization-dialog.d.ts +10 -0
- package/data/build/dist/client/features/voice-initialization-dialog.d.ts.map +1 -0
- package/data/build/dist/client/features/voice-initialization-dialog.js +90 -0
- package/data/build/dist/client/features/voice-initialization-dialog.js.map +1 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts.map +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +57 -2
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js.map +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement.d.ts.map +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement.js +27 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js.map +1 -1
- package/data/build/dist/client/global-voice-runtime-registry.d.ts +27 -0
- package/data/build/dist/client/global-voice-runtime-registry.d.ts.map +1 -0
- package/data/build/dist/client/global-voice-runtime-registry.js +12 -0
- package/data/build/dist/client/global-voice-runtime-registry.js.map +1 -0
- package/data/build/dist/client/index.d.ts +1 -1
- package/data/build/dist/client/index.d.ts.map +1 -1
- package/data/build/dist/client/locale.d.ts.map +1 -1
- package/data/build/dist/client/locale.js +185 -1
- package/data/build/dist/client/locale.js.map +1 -1
- package/data/build/dist/client/locales/terminalAuth.d.ts.map +1 -1
- package/data/build/dist/client/locales/terminalAuth.js +2 -0
- package/data/build/dist/client/locales/terminalAuth.js.map +1 -1
- package/data/build/dist/client/locales/usage.d.ts.map +1 -1
- package/data/build/dist/client/locales/usage.js +4 -0
- package/data/build/dist/client/locales/usage.js.map +1 -1
- package/data/build/dist/client/mobile-session-interaction.d.ts +9 -0
- package/data/build/dist/client/mobile-session-interaction.d.ts.map +1 -1
- package/data/build/dist/client/mobile-session-interaction.js +63 -0
- package/data/build/dist/client/mobile-session-interaction.js.map +1 -1
- package/data/build/dist/client/model-catalog-cache.d.ts +23 -0
- package/data/build/dist/client/model-catalog-cache.d.ts.map +1 -1
- package/data/build/dist/client/model-catalog-cache.js +81 -9
- package/data/build/dist/client/model-catalog-cache.js.map +1 -1
- package/data/build/dist/client/service-tier.d.ts +11 -0
- package/data/build/dist/client/service-tier.d.ts.map +1 -1
- package/data/build/dist/client/service-tier.js +15 -0
- package/data/build/dist/client/service-tier.js.map +1 -1
- package/data/build/dist/client/sherpa-voice-adapter.d.ts +46 -0
- package/data/build/dist/client/sherpa-voice-adapter.d.ts.map +1 -0
- package/data/build/dist/client/sherpa-voice-adapter.js +252 -0
- package/data/build/dist/client/sherpa-voice-adapter.js.map +1 -0
- package/data/build/dist/client/skill-command.d.ts +10 -0
- package/data/build/dist/client/skill-command.d.ts.map +1 -0
- package/data/build/dist/client/skill-command.js +258 -0
- package/data/build/dist/client/skill-command.js.map +1 -0
- package/data/build/dist/client/skill-reference-dom.d.ts +38 -0
- package/data/build/dist/client/skill-reference-dom.d.ts.map +1 -0
- package/data/build/dist/client/skill-reference-dom.js +187 -0
- package/data/build/dist/client/skill-reference-dom.js.map +1 -0
- package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
- package/data/build/dist/client/subscription-slot.js +51 -3
- package/data/build/dist/client/subscription-slot.js.map +1 -1
- package/data/build/dist/client/terminal/model.d.ts +40 -2
- package/data/build/dist/client/terminal/model.d.ts.map +1 -1
- package/data/build/dist/client/terminal/model.js +151 -24
- package/data/build/dist/client/terminal/model.js.map +1 -1
- package/data/build/dist/client/terminal/styles.d.ts +8 -0
- package/data/build/dist/client/terminal/styles.d.ts.map +1 -1
- package/data/build/dist/client/terminal/styles.js +23 -2
- package/data/build/dist/client/terminal/styles.js.map +1 -1
- package/data/build/dist/client/terminal/ui.d.ts.map +1 -1
- package/data/build/dist/client/terminal/ui.js +161 -26
- package/data/build/dist/client/terminal/ui.js.map +1 -1
- package/data/build/dist/client/terminal/xterm-view.d.ts.map +1 -1
- package/data/build/dist/client/terminal/xterm-view.js +218 -42
- package/data/build/dist/client/terminal/xterm-view.js.map +1 -1
- package/data/build/dist/client/voice-capture.d.ts +32 -0
- package/data/build/dist/client/voice-capture.d.ts.map +1 -0
- package/data/build/dist/client/voice-capture.js +158 -0
- package/data/build/dist/client/voice-capture.js.map +1 -0
- package/data/build/dist/client/voice-device-manager.d.ts +24 -0
- package/data/build/dist/client/voice-device-manager.d.ts.map +1 -0
- package/data/build/dist/client/voice-device-manager.js +197 -0
- package/data/build/dist/client/voice-device-manager.js.map +1 -0
- package/data/build/dist/client/voice-output.d.ts +14 -0
- package/data/build/dist/client/voice-output.d.ts.map +1 -0
- package/data/build/dist/client/voice-output.js +32 -0
- package/data/build/dist/client/voice-output.js.map +1 -0
- package/data/build/dist/client/voice-security.d.ts +8 -0
- package/data/build/dist/client/voice-security.d.ts.map +1 -0
- package/data/build/dist/client/voice-security.js +26 -0
- package/data/build/dist/client/voice-security.js.map +1 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +12 -1
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +32 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js.map +1 -1
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js +39 -9
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +39 -2
- package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/antigravity-driver.js +198 -10
- package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/antigravity-subscription.d.ts +121 -0
- package/data/build/dist/host/cli-adapters/antigravity-subscription.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/antigravity-subscription.js +504 -0
- package/data/build/dist/host/cli-adapters/antigravity-subscription.js.map +1 -0
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +21 -0
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/claude-driver.js +173 -7
- package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/codebuddy-driver.js +4 -0
- package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +28 -2
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +234 -5
- package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-subscription.js +7 -4
- package/data/build/dist/host/cli-adapters/command-code-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/cursor-driver.d.ts +9 -0
- package/data/build/dist/host/cli-adapters/cursor-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/cursor-driver.js +72 -2
- package/data/build/dist/host/cli-adapters/cursor-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/driver.d.ts +21 -1
- package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +15 -3
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.js +18 -1
- package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
- package/data/build/dist/host/cli-adapters/gemini-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/gemini-driver.js +4 -0
- package/data/build/dist/host/cli-adapters/gemini-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/grok-driver.js +4 -0
- package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
- package/data/build/dist/host/cli-adapters/kimi-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/kimi-driver.js +6 -6
- package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/mcode-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/mcode-driver.js +2 -0
- package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +49 -0
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/model-catalog.js +126 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js.map +1 -1
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/opencode-driver.js +92 -8
- package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/pi-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/pi-driver.js +4 -0
- package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +10 -3
- package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +4 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.js +8 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/qoder-driver.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/qoder-driver.js +171 -3
- package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/qoder-subscription.js +3 -1
- package/data/build/dist/host/cli-adapters/qoder-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/reasoning-content.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/reasoning-content.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/reasoning-content.js +79 -0
- package/data/build/dist/host/cli-adapters/reasoning-content.js.map +1 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +24 -1
- package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/registry.js +109 -4
- package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +3 -2
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +8 -2
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -2
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +14 -2
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
- package/data/build/dist/host/cli-bridge/injections.d.ts +10 -1
- package/data/build/dist/host/cli-bridge/injections.d.ts.map +1 -1
- package/data/build/dist/host/cli-bridge/injections.js +28 -8
- package/data/build/dist/host/cli-bridge/injections.js.map +1 -1
- package/data/build/dist/host/features/assistant-dispatch.d.ts +45 -0
- package/data/build/dist/host/features/assistant-dispatch.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-dispatch.js +49 -0
- package/data/build/dist/host/features/assistant-dispatch.js.map +1 -0
- package/data/build/dist/host/features/assistant-intent.d.ts +46 -0
- package/data/build/dist/host/features/assistant-intent.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-intent.js +149 -0
- package/data/build/dist/host/features/assistant-intent.js.map +1 -0
- package/data/build/dist/host/features/assistant-scope-settings.d.ts +25 -0
- package/data/build/dist/host/features/assistant-scope-settings.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-scope-settings.js +33 -0
- package/data/build/dist/host/features/assistant-scope-settings.js.map +1 -0
- package/data/build/dist/host/features/assistant-scope.d.ts +79 -0
- package/data/build/dist/host/features/assistant-scope.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-scope.js +107 -0
- package/data/build/dist/host/features/assistant-scope.js.map +1 -0
- package/data/build/dist/host/features/assistant-session-index.d.ts +44 -0
- package/data/build/dist/host/features/assistant-session-index.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-session-index.js +109 -0
- package/data/build/dist/host/features/assistant-session-index.js.map +1 -0
- package/data/build/dist/host/features/assistant-summary.d.ts +28 -0
- package/data/build/dist/host/features/assistant-summary.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-summary.js +166 -0
- package/data/build/dist/host/features/assistant-summary.js.map +1 -0
- package/data/build/dist/host/features/assistant-voice-stream.d.ts +10 -0
- package/data/build/dist/host/features/assistant-voice-stream.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-voice-stream.js +106 -0
- package/data/build/dist/host/features/assistant-voice-stream.js.map +1 -0
- package/data/build/dist/host/features/assistant-voice-turn.d.ts +27 -0
- package/data/build/dist/host/features/assistant-voice-turn.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-voice-turn.js +54 -0
- package/data/build/dist/host/features/assistant-voice-turn.js.map +1 -0
- package/data/build/dist/host/features/assistant-waiting-state.d.ts +14 -0
- package/data/build/dist/host/features/assistant-waiting-state.d.ts.map +1 -0
- package/data/build/dist/host/features/assistant-waiting-state.js +17 -0
- package/data/build/dist/host/features/assistant-waiting-state.js.map +1 -0
- package/data/build/dist/host/features/global-voice-coordinator.d.ts +55 -0
- package/data/build/dist/host/features/global-voice-coordinator.d.ts.map +1 -0
- package/data/build/dist/host/features/global-voice-coordinator.js +221 -0
- package/data/build/dist/host/features/global-voice-coordinator.js.map +1 -0
- package/data/build/dist/host/features/global-voice-rpc.d.ts +25 -0
- package/data/build/dist/host/features/global-voice-rpc.d.ts.map +1 -0
- package/data/build/dist/host/features/global-voice-rpc.js +862 -0
- package/data/build/dist/host/features/global-voice-rpc.js.map +1 -0
- package/data/build/dist/host/features/index.d.ts +10 -0
- package/data/build/dist/host/features/index.d.ts.map +1 -1
- package/data/build/dist/host/features/index.js +10 -0
- package/data/build/dist/host/features/index.js.map +1 -1
- package/data/build/dist/host/features/peer-host.d.ts +5 -0
- package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
- package/data/build/dist/host/features/peer-host.js +232 -2
- package/data/build/dist/host/features/peer-host.js.map +1 -1
- package/data/build/dist/host/features/sherpa-voice-runtime.d.ts +42 -0
- package/data/build/dist/host/features/sherpa-voice-runtime.d.ts.map +1 -0
- package/data/build/dist/host/features/sherpa-voice-runtime.js +193 -0
- package/data/build/dist/host/features/sherpa-voice-runtime.js.map +1 -0
- package/data/build/dist/host/features/types.d.ts +26 -0
- package/data/build/dist/host/features/types.d.ts.map +1 -1
- package/data/build/dist/host/features/voice-agent-actions.d.ts +26 -0
- package/data/build/dist/host/features/voice-agent-actions.d.ts.map +1 -0
- package/data/build/dist/host/features/voice-agent-actions.js +128 -0
- package/data/build/dist/host/features/voice-agent-actions.js.map +1 -0
- package/data/build/dist/host/features/voice-agent-service.d.ts +117 -0
- package/data/build/dist/host/features/voice-agent-service.d.ts.map +1 -0
- package/data/build/dist/host/features/voice-agent-service.js +243 -0
- package/data/build/dist/host/features/voice-agent-service.js.map +1 -0
- package/data/build/dist/host/features/voice-model-setup.d.ts +20 -0
- package/data/build/dist/host/features/voice-model-setup.d.ts.map +1 -0
- package/data/build/dist/host/features/voice-model-setup.js +89 -0
- package/data/build/dist/host/features/voice-model-setup.js.map +1 -0
- package/data/build/dist/host/index.d.ts +2 -2
- package/data/build/dist/host/index.d.ts.map +1 -1
- package/data/build/dist/host/index.js +7 -1
- package/data/build/dist/host/index.js.map +1 -1
- package/data/build/dist/host/lan-access-dsh.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js +53 -2
- package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js +15 -1
- package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js.map +1 -1
- package/data/build/dist/host/native-session-bridge.d.ts +2 -0
- package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
- package/data/build/dist/host/native-session-bridge.js +1 -0
- package/data/build/dist/host/native-session-bridge.js.map +1 -1
- package/data/build/dist/host/rpc.d.ts.map +1 -1
- package/data/build/dist/host/rpc.js +9 -4
- package/data/build/dist/host/rpc.js.map +1 -1
- package/data/build/dist/host/settings.d.ts.map +1 -1
- package/data/build/dist/host/settings.js +19 -1
- package/data/build/dist/host/settings.js.map +1 -1
- package/data/build/dist/host/terminal/backends/conpty-backend.js.map +1 -1
- package/data/build/dist/host/terminal/terminal-controller.d.ts.map +1 -1
- package/data/build/dist/host/terminal/terminal-controller.js +4 -0
- package/data/build/dist/host/terminal/terminal-controller.js.map +1 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts +21 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts.map +1 -1
- package/data/build/dist/host/terminal/terminal-service.js +118 -7
- package/data/build/dist/host/terminal/terminal-service.js.map +1 -1
- package/data/build/dist/index.d.ts +2 -2
- package/data/build/dist/index.d.ts.map +1 -1
- package/data/build/dist/index.js +1 -1
- package/data/build/dist/index.js.map +1 -1
- package/data/build/dist/shared/contracts/assistant.d.ts +70 -0
- package/data/build/dist/shared/contracts/assistant.d.ts.map +1 -0
- package/data/build/dist/shared/contracts/assistant.js +2 -0
- package/data/build/dist/shared/contracts/assistant.js.map +1 -0
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +33 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/config.d.ts +30 -0
- package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/config.js +20 -0
- package/data/build/dist/shared/contracts/config.js.map +1 -1
- package/data/build/dist/shared/contracts/errors.d.ts +2 -2
- package/data/build/dist/shared/contracts/errors.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/subscription.d.ts +30 -0
- package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/subscription.js.map +1 -1
- package/data/build/dist/shared/contracts/terminal.d.ts +2 -0
- package/data/build/dist/shared/contracts/terminal.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/version.d.ts +12 -5
- package/data/build/dist/shared/contracts/version.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/version.js +11 -4
- package/data/build/dist/shared/contracts/version.js.map +1 -1
- package/data/build/dist/shared/contracts/voice-runtime.d.ts +90 -0
- package/data/build/dist/shared/contracts/voice-runtime.d.ts.map +1 -0
- package/data/build/dist/shared/contracts/voice-runtime.js +16 -0
- package/data/build/dist/shared/contracts/voice-runtime.js.map +1 -0
- package/data/build/dist/shared/index.d.ts +8 -2
- package/data/build/dist/shared/index.d.ts.map +1 -1
- package/data/build/dist/shared/index.js +5 -1
- package/data/build/dist/shared/index.js.map +1 -1
- package/data/build/dist/shared/voice-models.d.ts +21 -0
- package/data/build/dist/shared/voice-models.d.ts.map +1 -0
- package/data/build/dist/shared/voice-models.js +27 -0
- package/data/build/dist/shared/voice-models.js.map +1 -0
- package/data/build/dist/shared/voice-stream.d.ts +42 -0
- package/data/build/dist/shared/voice-stream.d.ts.map +1 -0
- package/data/build/dist/shared/voice-stream.js +99 -0
- package/data/build/dist/shared/voice-stream.js.map +1 -0
- package/data/build/dist/shared/voice-text.d.ts +3 -0
- package/data/build/dist/shared/voice-text.d.ts.map +1 -0
- package/data/build/dist/shared/voice-text.js +14 -0
- package/data/build/dist/shared/voice-text.js.map +1 -0
- package/data/build/dist/typert.host.d.ts.map +1 -1
- package/data/build/dist/typert.host.js +1 -0
- package/data/build/dist/typert.host.js.map +1 -1
- package/package.json +56 -26
- package/profile/README.md +8 -5
- package/profile/package.json +5 -3
- package/profile/version.json +3 -3
- package/version.json +3 -3
|
@@ -877,6 +877,7 @@ window.__ModuleLoader__.load({
|
|
|
877
877
|
"terminalView.readOnly": "This page is currently read-only.",
|
|
878
878
|
"terminalView.reconnect": "Reconnect",
|
|
879
879
|
"terminalView.readingEnvironment": "Reading the terminal environment…",
|
|
880
|
+
"terminalView.loadTimeout": "The terminal service did not respond in time. Please retry.",
|
|
880
881
|
"terminalView.starting": "Starting…",
|
|
881
882
|
"terminalView.connecting": "Connecting…",
|
|
882
883
|
"terminalView.disconnected": "Connection lost.",
|
|
@@ -909,6 +910,7 @@ window.__ModuleLoader__.load({
|
|
|
909
910
|
"terminalView.readOnly": "此页面当前只读。",
|
|
910
911
|
"terminalView.reconnect": "重新连接",
|
|
911
912
|
"terminalView.readingEnvironment": "正在读取终端环境…",
|
|
913
|
+
"terminalView.loadTimeout": "终端服务响应超时,请重试。",
|
|
912
914
|
"terminalView.starting": "正在启动…",
|
|
913
915
|
"terminalView.connecting": "正在连接…",
|
|
914
916
|
"terminalView.disconnected": "连接已断开。",
|
|
@@ -958,6 +960,8 @@ window.__ModuleLoader__.load({
|
|
|
958
960
|
"usage.windowFiveHour": "5-hour quota",
|
|
959
961
|
"usage.windowWeekly": "Weekly quota",
|
|
960
962
|
"usage.windowMonthly": "Monthly quota",
|
|
963
|
+
"usage.quotaGroupGemini": "Gemini models",
|
|
964
|
+
"usage.quotaGroupThirdParty": "Claude and GPT models",
|
|
961
965
|
"usage.popoverSubscriptionUsage": "{provider} subscription usage",
|
|
962
966
|
"usage.popoverSubscriptionTitle": "{provider} subscription",
|
|
963
967
|
"usage.windowRemaining": "{label} remaining {percent}%",
|
|
@@ -1062,6 +1066,8 @@ window.__ModuleLoader__.load({
|
|
|
1062
1066
|
"usage.windowFiveHour": "5 小时额度",
|
|
1063
1067
|
"usage.windowWeekly": "周额度",
|
|
1064
1068
|
"usage.windowMonthly": "月额度",
|
|
1069
|
+
"usage.quotaGroupGemini": "Gemini 模型",
|
|
1070
|
+
"usage.quotaGroupThirdParty": "Claude 与 GPT 模型",
|
|
1065
1071
|
"usage.popoverSubscriptionUsage": "{provider} 订阅使用情况",
|
|
1066
1072
|
"usage.popoverSubscriptionTitle": "{provider} 订阅",
|
|
1067
1073
|
"usage.windowRemaining": "{label}剩余 {percent}%",
|
|
@@ -1231,6 +1237,70 @@ window.__ModuleLoader__.load({
|
|
|
1231
1237
|
"feature.cliAdapters.description": "View installed external Agents, versions, command paths, available models, and enable them independently. External Agent context panels keep only the occupancy percentage and totals.",
|
|
1232
1238
|
"feature.workspaceSession.label": "Workspace session enhancement",
|
|
1233
1239
|
"feature.workspaceSession.description": "Show Agent logos, archived-session entries, workspace hiding controls, subscription or usage information, local quick sessions, and an optional remembered conversation/right-sidebar width ratio.",
|
|
1240
|
+
"feature.assistant.label": "Global smart assistant (Testing)",
|
|
1241
|
+
"feature.assistant.description": "Summarize progress across selected workspaces and route voice or text instructions to an explicit target session.",
|
|
1242
|
+
"voice.globalAssistant": "Global voice assistant (Testing)",
|
|
1243
|
+
"voice.audioNotice": "Audio is processed by the configured local or cloud voice provider.",
|
|
1244
|
+
"voice.emptyTranscript": "No usable speech was recognized; nothing was submitted. Please try again.",
|
|
1245
|
+
"voice.invalidTranscript": "The transcription was not valid, so nothing was submitted. Please say it again.",
|
|
1246
|
+
"voice.secureContextRequired": "Microphone access requires a secure browser context. Use HTTPS, WSS, or localhost and try again.",
|
|
1247
|
+
"voice.microphoneNotice": "Starting this control uses the microphone in this browser.",
|
|
1248
|
+
"voice.microphoneUpdated": "Microphone updated.",
|
|
1249
|
+
"voice.inputDevice": "Input device",
|
|
1250
|
+
"voice.defaultMicrophone": "Browser default microphone",
|
|
1251
|
+
"voice.unnamedMicrophone": "Unnamed microphone",
|
|
1252
|
+
"voice.outputDevice": "Output device",
|
|
1253
|
+
"voice.defaultOutput": "Browser/OS default output",
|
|
1254
|
+
"voice.unnamedOutput": "Unnamed output",
|
|
1255
|
+
"voice.outputUpdated": "Output device updated.",
|
|
1256
|
+
"voice.dialog.title": "Global voice assistant (Testing)",
|
|
1257
|
+
"voice.dialog.description": "Open this window first. Start voice when you are ready; recognized text appears here in real time.",
|
|
1258
|
+
"voice.dialog.realtimeHint": "Realtime Sherpa-ONNX mode. Partial recognition appears while you speak.",
|
|
1259
|
+
"voice.dialog.unavailable": "Realtime Sherpa-ONNX is unavailable. Check the local runtime and model configuration; fallback transcription is disabled.",
|
|
1260
|
+
"voice.dialog.liveText": "Live transcription",
|
|
1261
|
+
"voice.dialog.empty": "Nothing recognized yet.",
|
|
1262
|
+
"voice.dialog.start": "Start realtime conversation",
|
|
1263
|
+
"voice.dialog.stop": "Stop conversation",
|
|
1264
|
+
"voice.dialog.close": "Close",
|
|
1265
|
+
"voice.dialog.clear": "Clear text",
|
|
1266
|
+
"voice.dialog.status.loading": "Preparing microphone…",
|
|
1267
|
+
"voice.dialog.status.listening": "Listening",
|
|
1268
|
+
"voice.dialog.status.recording": "Recording",
|
|
1269
|
+
"voice.dialog.status.error": "Error",
|
|
1270
|
+
"voice.dialog.status.active": "Active",
|
|
1271
|
+
"voice.dialog.status.idle": "Idle",
|
|
1272
|
+
"voice.setup.open": "Configure voice assistant (Testing)",
|
|
1273
|
+
"voice.setup.title": "Initialize global voice assistant (Testing)",
|
|
1274
|
+
"voice.setup.description": "Choose a recommended realtime model. CodingNS will download and initialize it automatically.",
|
|
1275
|
+
"voice.setup.onlyRealtime": "Only streaming models that support realtime conversation are shown. Files are stored on the current Host and reused after download.",
|
|
1276
|
+
"voice.setup.model": "Realtime voice model",
|
|
1277
|
+
"voice.setup.modelRequired": "Choose a realtime voice model.",
|
|
1278
|
+
"voice.setup.downloadHint": "The first setup downloads the model and configures the runtime. You will not need to enter file paths.",
|
|
1279
|
+
"voice.setup.downloading": "Downloading and initializing the model. Keep this window open…",
|
|
1280
|
+
"voice.setup.provider": "Voice provider",
|
|
1281
|
+
"voice.setup.provider.dsh": "DSH speechToText(browser hold-to-speak)",
|
|
1282
|
+
"voice.setup.provider.sherpa": "Sherpa-ONNX(local realtime)",
|
|
1283
|
+
"voice.setup.providerDshHint": "This mode uses the Host speechToText provider for sentence transcription and browser speech synthesis for replies. No local Sherpa model is required.",
|
|
1284
|
+
"voice.setup.modelHint": "Enter absolute paths to the Sherpa-ONNX model files on the Host machine.",
|
|
1285
|
+
"voice.setup.asrEncoder": "ASR encoder model",
|
|
1286
|
+
"voice.setup.asrDecoder": "ASR decoder model",
|
|
1287
|
+
"voice.setup.asrJoiner": "ASR joiner model",
|
|
1288
|
+
"voice.setup.asrTokens": "ASR tokens file",
|
|
1289
|
+
"voice.setup.vadModel": "VAD model(optional)",
|
|
1290
|
+
"voice.setup.ttsModel": "TTS model(optional)",
|
|
1291
|
+
"voice.setup.ttsTokens": "TTS tokens(optional)",
|
|
1292
|
+
"voice.setup.ttsLexicon": "TTS lexicon(optional)",
|
|
1293
|
+
"voice.setup.optional": "Optional; leave empty to disable",
|
|
1294
|
+
"voice.setup.required": "Sherpa-ONNX requires encoder, decoder, joiner, and tokens paths.",
|
|
1295
|
+
"voice.setup.cancel": "Cancel",
|
|
1296
|
+
"voice.setup.save": "Download and initialize",
|
|
1297
|
+
"voice.setup.reconfigure": "Configure",
|
|
1298
|
+
"voice.setup.status.uninitialized": "Not initialized; the assistant button opens setup instead of starting voice.",
|
|
1299
|
+
"voice.setup.status.dsh": "DSH speechToText sentence transcription with browser speech synthesis.",
|
|
1300
|
+
"voice.setup.status.sherpa": "Sherpa-ONNX local realtime mode.",
|
|
1301
|
+
"assistant.scopeDescription": "Only selected workspaces are readable by the global assistant. Archived sessions are always excluded.",
|
|
1302
|
+
"assistant.noWorkspaces": "No workspaces are available in this Host.",
|
|
1303
|
+
"assistant.saved": "Assistant scope saved.",
|
|
1234
1304
|
"feature.mobileAccess.label": "Mobile access enhancements",
|
|
1235
1305
|
"feature.mobileAccess.description": "On phones or narrow viewports, hide the left sidebar entirely and keep only the top-left logo as the way back in.",
|
|
1236
1306
|
"mobile.hideSidebarOnMobile": "Fully hide the left sidebar on mobile",
|
|
@@ -1255,6 +1325,26 @@ window.__ModuleLoader__.load({
|
|
|
1255
1325
|
"terminal.title": "Terminal",
|
|
1256
1326
|
"terminal.new": "New terminal",
|
|
1257
1327
|
"terminal.close": "Close terminal",
|
|
1328
|
+
"terminal.refresh": "Refresh terminal",
|
|
1329
|
+
"terminal.tools": "Terminal shortcuts",
|
|
1330
|
+
"terminal.holdCtrl": "Hold Ctrl",
|
|
1331
|
+
"terminal.holdAlt": "Hold Alt",
|
|
1332
|
+
"terminal.holdWin": "Hold Win",
|
|
1333
|
+
"terminal.holdOption": "Hold Option",
|
|
1334
|
+
"terminal.holdCommand": "Hold Command",
|
|
1335
|
+
"terminal.holdSuper": "Hold Super",
|
|
1336
|
+
"terminal.shortcutTab": "Tab",
|
|
1337
|
+
"terminal.shortcutEscape": "Escape",
|
|
1338
|
+
"terminal.shortcutEnter": "Enter",
|
|
1339
|
+
"terminal.shortcutBackspace": "Backspace",
|
|
1340
|
+
"terminal.shortcutUp": "Arrow up",
|
|
1341
|
+
"terminal.shortcutDown": "Arrow down",
|
|
1342
|
+
"terminal.shortcutLeft": "Arrow left",
|
|
1343
|
+
"terminal.shortcutRight": "Arrow right",
|
|
1344
|
+
"terminal.shortcutC": "C (Ctrl+C when Ctrl is held)",
|
|
1345
|
+
"terminal.shortcutD": "D (Ctrl+D when Ctrl is held)",
|
|
1346
|
+
"terminal.shortcutL": "L (Ctrl+L when Ctrl is held)",
|
|
1347
|
+
"terminal.shortcutZ": "Z (Ctrl+Z when Ctrl is held)",
|
|
1258
1348
|
"terminal.description": "Run commands in the session workspace",
|
|
1259
1349
|
"terminal.loadingShell": "Reading shells…",
|
|
1260
1350
|
"terminal.retry": "Retry",
|
|
@@ -1458,6 +1548,12 @@ window.__ModuleLoader__.load({
|
|
|
1458
1548
|
"delegate.cardTitle": "Delegation task",
|
|
1459
1549
|
"delegate.cardStatus": "Delegating",
|
|
1460
1550
|
"delegate.cardTarget": "{label} ({adapter}{model})",
|
|
1551
|
+
"skills.label": "Skills",
|
|
1552
|
+
"skills.description": "Browse and explicitly invoke Skills provided by the current Agent",
|
|
1553
|
+
"skills.searchPlaceholder": "Search Skills",
|
|
1554
|
+
"skills.searchEmpty": "The current Agent has no available Skills",
|
|
1555
|
+
"skills.searchNoResults": "No matching Skill",
|
|
1556
|
+
"skills.draftUnsupported": "This DSH version cannot write a Skill mention. Please upgrade DSH.",
|
|
1461
1557
|
"cli.loadingModel": "Loading models…",
|
|
1462
1558
|
"cli.noModelsAvailable": "No models available",
|
|
1463
1559
|
"cli.model": "Model",
|
|
@@ -1503,6 +1599,8 @@ window.__ModuleLoader__.load({
|
|
|
1503
1599
|
"subscriptionUsage.saved": "Usage query settings saved",
|
|
1504
1600
|
"workspace.showQuickPhrases": "Show quick sessions",
|
|
1505
1601
|
"workspace.quickPhrasesDescription": "Keep reusable session instructions locally in Codingns4DSH and show the quick sessions button inside the composer.",
|
|
1602
|
+
"workspace.showSkillQuickReference": "Index the selected Agent Skills for quick command references",
|
|
1603
|
+
"workspace.skillQuickReferenceDescription": "Load Skills from the selected Agent, insert them as one reference chip, and render them as a compact element in the message list.",
|
|
1506
1604
|
"workspace.rememberConversationRightbarRatio": "Remember conversation and right sidebar width ratio",
|
|
1507
1605
|
"workspace.rememberConversationRightbarRatioDescription": "Restore the right sidebar proportion after refreshing this browser; the ratio is stored locally.",
|
|
1508
1606
|
"mobile.sidebarGestures": "Swipe to open or close sidebars",
|
|
@@ -1561,6 +1659,70 @@ window.__ModuleLoader__.load({
|
|
|
1561
1659
|
"feature.cliAdapters.description": "管理外部 Agent",
|
|
1562
1660
|
"feature.workspaceSession.label": "工作区会话增强",
|
|
1563
1661
|
"feature.workspaceSession.description": "增强工作区会话体验",
|
|
1662
|
+
"feature.assistant.label": "全局智能助理(测试中)",
|
|
1663
|
+
"feature.assistant.description": "汇总受管工作区进展,并把语音或文字指令派发到明确的目标会话。",
|
|
1664
|
+
"voice.globalAssistant": "全局语音助理(测试中)",
|
|
1665
|
+
"voice.audioNotice": "音频会交给当前配置的本地或云端语音提供方处理。",
|
|
1666
|
+
"voice.emptyTranscript": "未识别到有效语音,未提交内容;请重试。",
|
|
1667
|
+
"voice.invalidTranscript": "转写结果无效,未提交内容;请重新说一遍。",
|
|
1668
|
+
"voice.secureContextRequired": "麦克风需要浏览器安全上下文,请改用 HTTPS、WSS 或 localhost 后重试。",
|
|
1669
|
+
"voice.microphoneNotice": "启动此入口会使用当前浏览器的麦克风。",
|
|
1670
|
+
"voice.microphoneUpdated": "麦克风已更新。",
|
|
1671
|
+
"voice.inputDevice": "输入设备",
|
|
1672
|
+
"voice.defaultMicrophone": "浏览器默认麦克风",
|
|
1673
|
+
"voice.unnamedMicrophone": "未命名麦克风",
|
|
1674
|
+
"voice.outputDevice": "输出设备",
|
|
1675
|
+
"voice.defaultOutput": "浏览器/系统默认输出",
|
|
1676
|
+
"voice.unnamedOutput": "未命名输出设备",
|
|
1677
|
+
"voice.outputUpdated": "输出设备已更新。",
|
|
1678
|
+
"voice.dialog.title": "全局语音助理(测试中)",
|
|
1679
|
+
"voice.dialog.description": "先打开此窗口,再按需要开始语音;识别文字会实时显示在这里。",
|
|
1680
|
+
"voice.dialog.realtimeHint": "Sherpa-ONNX 实时模式,说话时会显示实时识别结果。",
|
|
1681
|
+
"voice.dialog.unavailable": "Sherpa-ONNX 实时模式不可用,请检查本地运行时和模型配置;已禁用回退转写。",
|
|
1682
|
+
"voice.dialog.liveText": "实时文字转换",
|
|
1683
|
+
"voice.dialog.empty": "暂时还没有识别文字。",
|
|
1684
|
+
"voice.dialog.start": "开始实时对话",
|
|
1685
|
+
"voice.dialog.stop": "停止对话",
|
|
1686
|
+
"voice.dialog.close": "关闭",
|
|
1687
|
+
"voice.dialog.clear": "清空文字",
|
|
1688
|
+
"voice.dialog.status.loading": "正在准备麦克风……",
|
|
1689
|
+
"voice.dialog.status.listening": "正在聆听",
|
|
1690
|
+
"voice.dialog.status.recording": "正在录音",
|
|
1691
|
+
"voice.dialog.status.error": "出错",
|
|
1692
|
+
"voice.dialog.status.active": "运行中",
|
|
1693
|
+
"voice.dialog.status.idle": "未启动",
|
|
1694
|
+
"voice.setup.open": "配置语音助理(测试中)",
|
|
1695
|
+
"voice.setup.title": "初始化全局语音助理(测试中)",
|
|
1696
|
+
"voice.setup.description": "选择一个推荐的实时模型,系统会自动下载并完成初始化。",
|
|
1697
|
+
"voice.setup.onlyRealtime": "这里只显示支持实时对话的流式语音模型。模型文件保存在当前 Host,已下载的文件会自动复用。",
|
|
1698
|
+
"voice.setup.model": "实时语音模型",
|
|
1699
|
+
"voice.setup.modelRequired": "请选择一个实时语音模型。",
|
|
1700
|
+
"voice.setup.downloadHint": "首次使用会自动下载模型并配置运行时,之后无需再次填写路径。",
|
|
1701
|
+
"voice.setup.downloading": "正在下载并初始化模型,请保持此窗口打开……",
|
|
1702
|
+
"voice.setup.provider": "语音提供商",
|
|
1703
|
+
"voice.setup.provider.dsh": "DSH speechToText(浏览器按住说话)",
|
|
1704
|
+
"voice.setup.provider.sherpa": "Sherpa-ONNX(本地实时模式)",
|
|
1705
|
+
"voice.setup.providerDshHint": "此模式使用 Host 的 speechToText 进行句级转写,并使用浏览器 speechSynthesis 播报回复,不需要本地 Sherpa 模型。",
|
|
1706
|
+
"voice.setup.modelHint": "请输入 Host 机器上的 Sherpa-ONNX 模型文件绝对路径。",
|
|
1707
|
+
"voice.setup.asrEncoder": "ASR encoder 模型",
|
|
1708
|
+
"voice.setup.asrDecoder": "ASR decoder 模型",
|
|
1709
|
+
"voice.setup.asrJoiner": "ASR joiner 模型",
|
|
1710
|
+
"voice.setup.asrTokens": "ASR tokens 文件",
|
|
1711
|
+
"voice.setup.vadModel": "VAD 模型(可选)",
|
|
1712
|
+
"voice.setup.ttsModel": "TTS 模型(可选)",
|
|
1713
|
+
"voice.setup.ttsTokens": "TTS tokens(可选)",
|
|
1714
|
+
"voice.setup.ttsLexicon": "TTS lexicon(可选)",
|
|
1715
|
+
"voice.setup.optional": "可选;留空表示禁用",
|
|
1716
|
+
"voice.setup.required": "Sherpa-ONNX 必须填写 encoder、decoder、joiner 和 tokens 路径。",
|
|
1717
|
+
"voice.setup.cancel": "取消",
|
|
1718
|
+
"voice.setup.save": "下载并初始化",
|
|
1719
|
+
"voice.setup.reconfigure": "配置",
|
|
1720
|
+
"voice.setup.status.uninitialized": "尚未初始化;点击助手按钮只打开配置页,不会直接启动语音。",
|
|
1721
|
+
"voice.setup.status.dsh": "使用 DSH speechToText 句级转写和浏览器语音播报。",
|
|
1722
|
+
"voice.setup.status.sherpa": "使用 Sherpa-ONNX 本地实时模式。",
|
|
1723
|
+
"assistant.scopeDescription": "全局助理只读取已勾选的工作区,已归档会话始终排除。",
|
|
1724
|
+
"assistant.noWorkspaces": "当前 Host 没有可选工作区。",
|
|
1725
|
+
"assistant.saved": "助理受管范围已保存。",
|
|
1564
1726
|
"feature.mobileAccess.label": "移动端访问增强",
|
|
1565
1727
|
"feature.mobileAccess.description": "优化移动端访问布局",
|
|
1566
1728
|
"mobile.hideSidebarOnMobile": "移动端彻底隐藏左侧边栏",
|
|
@@ -1585,6 +1747,26 @@ window.__ModuleLoader__.load({
|
|
|
1585
1747
|
"terminal.title": "终端",
|
|
1586
1748
|
"terminal.new": "新建终端",
|
|
1587
1749
|
"terminal.close": "关闭终端",
|
|
1750
|
+
"terminal.refresh": "刷新终端",
|
|
1751
|
+
"terminal.tools": "终端快捷操作",
|
|
1752
|
+
"terminal.holdCtrl": "按住 Ctrl",
|
|
1753
|
+
"terminal.holdAlt": "按住 Alt",
|
|
1754
|
+
"terminal.holdWin": "按住 Win",
|
|
1755
|
+
"terminal.holdOption": "按住 Option",
|
|
1756
|
+
"terminal.holdCommand": "按住 Command",
|
|
1757
|
+
"terminal.holdSuper": "按住 Super",
|
|
1758
|
+
"terminal.shortcutTab": "Tab 制表键",
|
|
1759
|
+
"terminal.shortcutEscape": "Esc 退出键",
|
|
1760
|
+
"terminal.shortcutEnter": "Enter 回车键",
|
|
1761
|
+
"terminal.shortcutBackspace": "Backspace 退格键",
|
|
1762
|
+
"terminal.shortcutUp": "向上箭头",
|
|
1763
|
+
"terminal.shortcutDown": "向下箭头",
|
|
1764
|
+
"terminal.shortcutLeft": "向左箭头",
|
|
1765
|
+
"terminal.shortcutRight": "向右箭头",
|
|
1766
|
+
"terminal.shortcutC": "C(按住 Ctrl 时发送 Ctrl+C)",
|
|
1767
|
+
"terminal.shortcutD": "D(按住 Ctrl 时发送 Ctrl+D)",
|
|
1768
|
+
"terminal.shortcutL": "L(按住 Ctrl 时发送 Ctrl+L)",
|
|
1769
|
+
"terminal.shortcutZ": "Z(按住 Ctrl 时发送 Ctrl+Z)",
|
|
1588
1770
|
"terminal.description": "在会话工作区运行命令",
|
|
1589
1771
|
"terminal.loadingShell": "正在读取 Shell…",
|
|
1590
1772
|
"terminal.retry": "重试",
|
|
@@ -1788,6 +1970,12 @@ window.__ModuleLoader__.load({
|
|
|
1788
1970
|
"delegate.cardTitle": "委派任务",
|
|
1789
1971
|
"delegate.cardStatus": "委派中",
|
|
1790
1972
|
"delegate.cardTarget": "{label}({adapter}{model})",
|
|
1973
|
+
"skills.label": "技能",
|
|
1974
|
+
"skills.description": "浏览并显式调用当前 Agent 提供的技能",
|
|
1975
|
+
"skills.searchPlaceholder": "搜索技能",
|
|
1976
|
+
"skills.searchEmpty": "当前 Agent 没有可用技能",
|
|
1977
|
+
"skills.searchNoResults": "没有匹配的技能",
|
|
1978
|
+
"skills.draftUnsupported": "当前 DSH 不支持写入技能引用,请先升级 DSH。",
|
|
1791
1979
|
"cli.loadingModel": "加载模型…",
|
|
1792
1980
|
"cli.noModelsAvailable": "无可用模型",
|
|
1793
1981
|
"cli.model": "模型",
|
|
@@ -1831,8 +2019,10 @@ window.__ModuleLoader__.load({
|
|
|
1831
2019
|
"subscriptionUsage.cancel": "取消",
|
|
1832
2020
|
"subscriptionUsage.save": "保存",
|
|
1833
2021
|
"subscriptionUsage.saved": "用量查询设置已保存",
|
|
1834
|
-
"workspace.showQuickPhrases": "
|
|
2022
|
+
"workspace.showQuickPhrases": "在对话框中显示快捷短语组件",
|
|
1835
2023
|
"workspace.quickPhrasesDescription": "在 Codingns4DSH 内本地保存可复用的会话指令,并将快捷会话按钮显示在输入工具区。",
|
|
2024
|
+
"workspace.showSkillQuickReference": "索引指定 Agent 的 Skill 列表并通过指令快速引用",
|
|
2025
|
+
"workspace.skillQuickReferenceDescription": "加载本次会话所选 Agent 的 Skill,将其作为整体引用元素插入聊天框,并在消息列表中美化显示。",
|
|
1836
2026
|
"workspace.rememberConversationRightbarRatio": "记忆对话窗口与右侧栏的宽度比例",
|
|
1837
2027
|
"workspace.rememberConversationRightbarRatioDescription": "刷新后恢复右侧栏比例;比例仅保存在当前浏览器中。",
|
|
1838
2028
|
"mobile.sidebarGestures": "横滑开合侧栏",
|
|
@@ -2571,9 +2761,16 @@ window.__ModuleLoader__.load({
|
|
|
2571
2761
|
//#endregion
|
|
2572
2762
|
//#region src/shared/contracts/version.ts
|
|
2573
2763
|
/** 当前经过完整验证的 DSH 版本;源文件由根目录 version.json 同步。 */
|
|
2574
|
-
const DSH_VERSION = "0.2.
|
|
2575
|
-
/**
|
|
2576
|
-
|
|
2764
|
+
const DSH_VERSION = "0.2.1-alpha.1";
|
|
2765
|
+
/**
|
|
2766
|
+
* 插件正式验证的 DSH 版本范围,覆盖 rc.2 到当前验证的 alpha.1。
|
|
2767
|
+
*
|
|
2768
|
+
* 0.2.0-rc.2 与 0.2.1-alpha.1 属于同一 API 世代:插件消费的 7 个宿主包接口面
|
|
2769
|
+
* 未变(仅 dsh-client-ui-primitives 新增 InlineEditor),会话格式仍为 v4。因此
|
|
2770
|
+
* 这里保留明确上界,是为了让 npm/DSH 安装期也能识别 0.2.1-alpha.1 这个跨 patch
|
|
2771
|
+
* 预发布版本;出现新的 API 世代或结构变化时,应重新验证并更新范围与能力路由。
|
|
2772
|
+
*/
|
|
2773
|
+
const DSH_COMPATIBILITY = ">=0.2.0-rc.2 <=0.2.1-alpha.1";
|
|
2577
2774
|
/**
|
|
2578
2775
|
* 兼容范围下界,即插件仍然接受的最低 DSH 版本。
|
|
2579
2776
|
*
|
|
@@ -2582,7 +2779,7 @@ window.__ModuleLoader__.load({
|
|
|
2582
2779
|
* “不支持的 DSH 版本”,把仅仅缺少启动页注入的页面误判为宿主不兼容。
|
|
2583
2780
|
*/
|
|
2584
2781
|
function minimumSupportedDshVersion() {
|
|
2585
|
-
return /^>=([^ ]+)/u.exec(">=0.2.0-rc.2 <=0.2.
|
|
2782
|
+
return /^>=([^ ]+)/u.exec(">=0.2.0-rc.2 <=0.2.1-alpha.1")?.[1] ?? "0.2.1-alpha.1";
|
|
2586
2783
|
}
|
|
2587
2784
|
/** Host 注入到浏览器页面的真实 DSH 版本全局字段。 */
|
|
2588
2785
|
const CODINGNS_DSH_VERSION_GLOBAL = "__CODINGNS_DSH_VERSION__";
|
|
@@ -2591,7 +2788,7 @@ window.__ModuleLoader__.load({
|
|
|
2591
2788
|
return /^0\.1\.5(?:-|$)/u.test(version);
|
|
2592
2789
|
}
|
|
2593
2790
|
/** Codingns4DSH 插件自身的 npm 版本。 */
|
|
2594
|
-
const CODINGNS_VERSION = "0.2.
|
|
2791
|
+
const CODINGNS_VERSION = "0.2.1-beta.1";
|
|
2595
2792
|
/** 判断宿主版本是否落在当前插件声明的 DSH 兼容范围内;范围省略上界时不校验上限。 */
|
|
2596
2793
|
function isDshVersionCompatible(version) {
|
|
2597
2794
|
const match = /^>=([^ ]+)(?: <=([^ ]+))?$/u.exec(DSH_COMPATIBILITY);
|
|
@@ -2642,6 +2839,23 @@ window.__ModuleLoader__.load({
|
|
|
2642
2839
|
}
|
|
2643
2840
|
//#endregion
|
|
2644
2841
|
//#region src/shared/contracts/config.ts
|
|
2842
|
+
const DEFAULT_ASSISTANT_VOICE_SETTINGS = {
|
|
2843
|
+
initialized: false,
|
|
2844
|
+
provider: "dsh-speech-to-text",
|
|
2845
|
+
modelId: "",
|
|
2846
|
+
asrEncoder: "",
|
|
2847
|
+
asrDecoder: "",
|
|
2848
|
+
asrJoiner: "",
|
|
2849
|
+
asrTokens: "",
|
|
2850
|
+
vadModel: "",
|
|
2851
|
+
ttsModel: "",
|
|
2852
|
+
ttsTokens: "",
|
|
2853
|
+
ttsLexicon: ""
|
|
2854
|
+
};
|
|
2855
|
+
const DEFAULT_ASSISTANT_SETTINGS = {
|
|
2856
|
+
managedWorkspaceIds: [],
|
|
2857
|
+
voice: DEFAULT_ASSISTANT_VOICE_SETTINGS
|
|
2858
|
+
};
|
|
2645
2859
|
/** 并发子代理数上限:下限 1 保证至少能派发,上限避免一次性压垮本机。 */
|
|
2646
2860
|
const SUBAGENT_BRIDGE_MAX_CONCURRENT_LIMITS = {
|
|
2647
2861
|
min: 1,
|
|
@@ -2809,6 +3023,7 @@ window.__ModuleLoader__.load({
|
|
|
2809
3023
|
const CODINGNS_FILE_MANAGEMENT_FIELD = "fileManagement";
|
|
2810
3024
|
const CODINGNS_MOBILE_ACCESS_FIELD = "mobileAccess";
|
|
2811
3025
|
const CODINGNS_SUBSCRIPTION_USAGE_FIELD = "subscriptionUsage";
|
|
3026
|
+
const CODINGNS_ASSISTANT_FIELD = "assistant";
|
|
2812
3027
|
const DEFAULT_CODINGNS_CONTROL_BASE_URLS = ["https://channel.codingns.com:1443"];
|
|
2813
3028
|
/** 独立 H5 登录页面地址;登录控制站后可从设置页复制给其他设备。 */
|
|
2814
3029
|
const CODINGNS_H5_LOGIN_URL = "https://dsh.codingns.com";
|
|
@@ -2835,6 +3050,7 @@ window.__ModuleLoader__.load({
|
|
|
2835
3050
|
hiddenWorkspaceIds: [],
|
|
2836
3051
|
showSubscriptionUsage: true,
|
|
2837
3052
|
showQuickPhrases: true,
|
|
3053
|
+
showSkillQuickReference: true,
|
|
2838
3054
|
rememberConversationRightbarRatio: false,
|
|
2839
3055
|
quickPhrases: DEFAULT_QUICK_PHRASES.map((phrase) => ({ ...phrase })),
|
|
2840
3056
|
quickPhrasesSeeded: true
|
|
@@ -3108,6 +3324,8 @@ window.__ModuleLoader__.load({
|
|
|
3108
3324
|
let userActivation;
|
|
3109
3325
|
let suppressedClick;
|
|
3110
3326
|
let dispatchingSyntheticClick = false;
|
|
3327
|
+
let mountedUnsubscribe;
|
|
3328
|
+
let collapseTimers = [];
|
|
3111
3329
|
const now = () => Date.now();
|
|
3112
3330
|
const blurComposer = () => {
|
|
3113
3331
|
const activeElement = hostDocument?.activeElement;
|
|
@@ -3161,6 +3379,7 @@ window.__ModuleLoader__.load({
|
|
|
3161
3379
|
if (!isShortMobileTap(start, end)) return;
|
|
3162
3380
|
const row = sessionRow(point.target);
|
|
3163
3381
|
if (row === null || isRowControl(point.target, row)) return;
|
|
3382
|
+
collapseRightbar();
|
|
3164
3383
|
preventDefault$1(event);
|
|
3165
3384
|
suppressedClick = {
|
|
3166
3385
|
row,
|
|
@@ -3185,6 +3404,7 @@ window.__ModuleLoader__.load({
|
|
|
3185
3404
|
preventDefault$1(event);
|
|
3186
3405
|
stopPropagation(event);
|
|
3187
3406
|
}
|
|
3407
|
+
if (row !== null && !isRowControl(eventTarget(event), row)) collapseRightbar();
|
|
3188
3408
|
suppressedClick = void 0;
|
|
3189
3409
|
};
|
|
3190
3410
|
const onDoubleClickCapture = (event) => {
|
|
@@ -3207,6 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
3207
3427
|
hostDocument.addEventListener("focusin", onFocusIn, capture);
|
|
3208
3428
|
hostDocument.addEventListener("click", onClickCapture, capture);
|
|
3209
3429
|
hostDocument.addEventListener("dblclick", onDoubleClickCapture, capture);
|
|
3430
|
+
subscribeMountedSession();
|
|
3210
3431
|
};
|
|
3211
3432
|
const removeListeners = () => {
|
|
3212
3433
|
if (!active || hostDocument === void 0) return;
|
|
@@ -3218,10 +3439,53 @@ window.__ModuleLoader__.load({
|
|
|
3218
3439
|
hostDocument.removeEventListener("focusin", onFocusIn, true);
|
|
3219
3440
|
hostDocument.removeEventListener("click", onClickCapture, true);
|
|
3220
3441
|
hostDocument.removeEventListener("dblclick", onDoubleClickCapture, true);
|
|
3442
|
+
mountedUnsubscribe?.();
|
|
3443
|
+
mountedUnsubscribe = void 0;
|
|
3444
|
+
for (const timer of collapseTimers) clearTimeout(timer);
|
|
3445
|
+
collapseTimers = [];
|
|
3221
3446
|
touchStart = void 0;
|
|
3222
3447
|
userActivation = void 0;
|
|
3223
3448
|
suppressedClick = void 0;
|
|
3224
3449
|
};
|
|
3450
|
+
/**
|
|
3451
|
+
* DSH 的右栏布局按会话保存。移动端切换到新会话时,宿主会先挂载会话,
|
|
3452
|
+
* 随后恢复该会话上次的展开状态;这里仅在移动端把这次自动恢复收回,
|
|
3453
|
+
* 让主界面稳定停留在消息区。用户之后仍可通过右滑或原生按钮主动打开。
|
|
3454
|
+
*/
|
|
3455
|
+
const collapseRightbar = () => {
|
|
3456
|
+
const sidebarRight = options.sidebarRight;
|
|
3457
|
+
if (sidebarRight === void 0) return false;
|
|
3458
|
+
try {
|
|
3459
|
+
if (!sidebarRight.isExpanded()) return false;
|
|
3460
|
+
sidebarRight.toggleExpanded();
|
|
3461
|
+
for (const timer of collapseTimers) clearTimeout(timer);
|
|
3462
|
+
collapseTimers = [];
|
|
3463
|
+
return true;
|
|
3464
|
+
} catch {
|
|
3465
|
+
return false;
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
const scheduleRightbarCollapse = () => {
|
|
3469
|
+
if (!active || options.sidebarRight === void 0) return;
|
|
3470
|
+
for (const timer of collapseTimers) clearTimeout(timer);
|
|
3471
|
+
collapseTimers = [];
|
|
3472
|
+
if (collapseRightbar()) return;
|
|
3473
|
+
collapseTimers = [
|
|
3474
|
+
setTimeout(collapseRightbar, 0),
|
|
3475
|
+
setTimeout(collapseRightbar, 60),
|
|
3476
|
+
setTimeout(collapseRightbar, 240)
|
|
3477
|
+
];
|
|
3478
|
+
};
|
|
3479
|
+
const subscribeMountedSession = () => {
|
|
3480
|
+
const subscribe = options.sidebarRight?.mounted?.subscribe;
|
|
3481
|
+
if (typeof subscribe !== "function") return;
|
|
3482
|
+
try {
|
|
3483
|
+
mountedUnsubscribe = subscribe(scheduleRightbarCollapse);
|
|
3484
|
+
} catch {
|
|
3485
|
+
mountedUnsubscribe = void 0;
|
|
3486
|
+
}
|
|
3487
|
+
scheduleRightbarCollapse();
|
|
3488
|
+
};
|
|
3225
3489
|
const refresh = () => {
|
|
3226
3490
|
if (disposed) return false;
|
|
3227
3491
|
if (isMobileTouchViewport$1(hostWindow, viewportMaxPx)) addListeners();
|
|
@@ -4080,7 +4344,7 @@ window.__ModuleLoader__.load({
|
|
|
4080
4344
|
start: (context) => {
|
|
4081
4345
|
ensureCryptoRandomUUID();
|
|
4082
4346
|
let prompt;
|
|
4083
|
-
const mobileSessionInteraction = startMobileSessionInteractionDom();
|
|
4347
|
+
const mobileSessionInteraction = startMobileSessionInteractionDom({ sidebarRight: context.services.sidebarRight });
|
|
4084
4348
|
const sync = () => {
|
|
4085
4349
|
const pwa = context.services.settings.getSnapshot().value?.lanAccessDsh.pwa ?? DEFAULT_LAN_ACCESS_DSH_PWA_SETTINGS;
|
|
4086
4350
|
if (!pwa.enabled || !pwa.installPrompt) {
|
|
@@ -5001,6 +5265,54 @@ window.__ModuleLoader__.load({
|
|
|
5001
5265
|
return age >= 0 && age < refreshIntervalMins * 6e4;
|
|
5002
5266
|
}
|
|
5003
5267
|
//#endregion
|
|
5268
|
+
//#region src/shared/voice-stream.ts
|
|
5269
|
+
function encodeVoiceStreamMessage(message) {
|
|
5270
|
+
return new TextEncoder().encode(`${JSON.stringify(message)}\n`);
|
|
5271
|
+
}
|
|
5272
|
+
function encodeVoiceStreamOpen(message) {
|
|
5273
|
+
return encodeVoiceStreamMessage({
|
|
5274
|
+
type: "open",
|
|
5275
|
+
...message
|
|
5276
|
+
});
|
|
5277
|
+
}
|
|
5278
|
+
//#endregion
|
|
5279
|
+
//#region src/shared/voice-models.ts
|
|
5280
|
+
const STREAMING_FILES = [
|
|
5281
|
+
{
|
|
5282
|
+
setting: "asrEncoder",
|
|
5283
|
+
name: "encoder-epoch-99-avg-1.int8.onnx"
|
|
5284
|
+
},
|
|
5285
|
+
{
|
|
5286
|
+
setting: "asrDecoder",
|
|
5287
|
+
name: "decoder-epoch-99-avg-1.int8.onnx"
|
|
5288
|
+
},
|
|
5289
|
+
{
|
|
5290
|
+
setting: "asrJoiner",
|
|
5291
|
+
name: "joiner-epoch-99-avg-1.int8.onnx"
|
|
5292
|
+
},
|
|
5293
|
+
{
|
|
5294
|
+
setting: "asrTokens",
|
|
5295
|
+
name: "tokens.txt"
|
|
5296
|
+
}
|
|
5297
|
+
];
|
|
5298
|
+
/** 只暴露支持实时输入的推荐模型;模型地址由 Host 根据此白名单解析。 */
|
|
5299
|
+
const ASSISTANT_VOICE_MODEL_CATALOG = [{
|
|
5300
|
+
id: "sherpa-onnx-streaming-zh-14m",
|
|
5301
|
+
label: "中文实时模型(推荐)",
|
|
5302
|
+
description: "本地运行,约 27 MB,适合中文语音对话。",
|
|
5303
|
+
repository: "csukuangfj/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23",
|
|
5304
|
+
files: STREAMING_FILES
|
|
5305
|
+
}, {
|
|
5306
|
+
id: "sherpa-onnx-streaming-zh-en",
|
|
5307
|
+
label: "中英双语实时模型",
|
|
5308
|
+
description: "本地运行,约 200 MB,同时识别中文和英文。",
|
|
5309
|
+
repository: "csukuangfj/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20",
|
|
5310
|
+
files: STREAMING_FILES
|
|
5311
|
+
}];
|
|
5312
|
+
function findAssistantVoiceModel(modelId) {
|
|
5313
|
+
return ASSISTANT_VOICE_MODEL_CATALOG.find((model) => model.id === modelId);
|
|
5314
|
+
}
|
|
5315
|
+
//#endregion
|
|
5004
5316
|
//#region src/transport/debug.ts
|
|
5005
5317
|
init_debug();
|
|
5006
5318
|
/** 创建一个可注入测试 sink 的调试 logger。默认开关由当前运行环境决定。 */
|
|
@@ -5034,12 +5346,12 @@ window.__ModuleLoader__.load({
|
|
|
5034
5346
|
if (typeof fields.status === "number" && fields.status >= 400) return true;
|
|
5035
5347
|
if (typeof fields.state === "string" && /^(failed|disconnected|closed|degraded)$/iu.test(fields.state)) return true;
|
|
5036
5348
|
if (typeof fields.event === "string" && fields.event !== event) {
|
|
5037
|
-
const nestedFields = isRecord$
|
|
5349
|
+
const nestedFields = isRecord$10(fields.fields) ? fields.fields : {};
|
|
5038
5350
|
return isDshTransportWarningEvent(fields.event, nestedFields);
|
|
5039
5351
|
}
|
|
5040
5352
|
return false;
|
|
5041
5353
|
}
|
|
5042
|
-
function isRecord$
|
|
5354
|
+
function isRecord$10(value) {
|
|
5043
5355
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5044
5356
|
}
|
|
5045
5357
|
/**
|
|
@@ -5859,15 +6171,15 @@ window.__ModuleLoader__.load({
|
|
|
5859
6171
|
sendHello() {
|
|
5860
6172
|
this.send(this.createEnvelope("session.hello", "session", {
|
|
5861
6173
|
protocol: this.options.protocol ?? "dsh-transport-v1",
|
|
5862
|
-
dshVersion: this.options.dshVersion ?? "0.2.
|
|
6174
|
+
dshVersion: this.options.dshVersion ?? "0.2.1-alpha.1",
|
|
5863
6175
|
capabilities: [...this.options.capabilities ?? []]
|
|
5864
6176
|
}));
|
|
5865
6177
|
}
|
|
5866
6178
|
sendReady(capabilities, peerDshVersion) {
|
|
5867
6179
|
this.send(this.createEnvelope("session.ready", "session", {
|
|
5868
6180
|
protocol: this.options.protocol ?? "dsh-transport-v1",
|
|
5869
|
-
dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.
|
|
5870
|
-
hostDshVersion: this.options.dshVersion ?? "0.2.
|
|
6181
|
+
dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.1-alpha.1",
|
|
6182
|
+
hostDshVersion: this.options.dshVersion ?? "0.2.1-alpha.1",
|
|
5871
6183
|
capabilities: [...capabilities],
|
|
5872
6184
|
byteCredit: 65536,
|
|
5873
6185
|
messageCredit: 32
|
|
@@ -6993,7 +7305,7 @@ window.__ModuleLoader__.load({
|
|
|
6993
7305
|
async onMessage(event) {
|
|
6994
7306
|
const sourceWindow = event.source;
|
|
6995
7307
|
if (this.disposed || sourceWindow === null || !this.iframeWindows.has(sourceWindow)) return;
|
|
6996
|
-
if (!isRecord$
|
|
7308
|
+
if (!isRecord$9(event.data) || typeof event.data.kind !== "string") return;
|
|
6997
7309
|
const message = event.data;
|
|
6998
7310
|
if (message.kind !== "dsh-web-debug" && typeof message.id !== "string") return;
|
|
6999
7311
|
if (typeof message.id === "string") this.messageWindows.set(message.id, sourceWindow);
|
|
@@ -7004,7 +7316,7 @@ window.__ModuleLoader__.load({
|
|
|
7004
7316
|
try {
|
|
7005
7317
|
if (message.kind === "dsh-web-debug") {
|
|
7006
7318
|
const eventName = typeof message.event === "string" ? message.event : "unknown";
|
|
7007
|
-
const fields = isRecord$
|
|
7319
|
+
const fields = isRecord$9(message.fields) ? message.fields : {};
|
|
7008
7320
|
this.debug.log(`iframe.${eventName}`, fields);
|
|
7009
7321
|
if (/^(bridge\.ws\.|client\.connection\.)/u.test(eventName)) await this.options.transport.webRequest("web.debug", {
|
|
7010
7322
|
event: eventName,
|
|
@@ -7013,7 +7325,7 @@ window.__ModuleLoader__.load({
|
|
|
7013
7325
|
return;
|
|
7014
7326
|
}
|
|
7015
7327
|
if (message.kind === "fetch") {
|
|
7016
|
-
const input = isRecord$
|
|
7328
|
+
const input = isRecord$9(message.input) ? message.input : {};
|
|
7017
7329
|
const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
|
|
7018
7330
|
const body = typeof message.body === "string" ? message.body : typeof input.body === "string" ? input.body : void 0;
|
|
7019
7331
|
const response = path.startsWith("/assets/") || path.startsWith("/plugins/") ? {
|
|
@@ -7039,7 +7351,7 @@ window.__ModuleLoader__.load({
|
|
|
7039
7351
|
return;
|
|
7040
7352
|
}
|
|
7041
7353
|
if (message.kind === "script") {
|
|
7042
|
-
const input = isRecord$
|
|
7354
|
+
const input = isRecord$9(message.input) ? message.input : {};
|
|
7043
7355
|
const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
|
|
7044
7356
|
const url = await this.loadScript(path, void 0);
|
|
7045
7357
|
this.postResponse(message.id, {
|
|
@@ -7049,7 +7361,7 @@ window.__ModuleLoader__.load({
|
|
|
7049
7361
|
return;
|
|
7050
7362
|
}
|
|
7051
7363
|
if (message.kind === "style") {
|
|
7052
|
-
const input = isRecord$
|
|
7364
|
+
const input = isRecord$9(message.input) ? message.input : {};
|
|
7053
7365
|
const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
|
|
7054
7366
|
const url = await this.loadStyle(path, void 0);
|
|
7055
7367
|
this.postResponse(message.id, {
|
|
@@ -7059,7 +7371,7 @@ window.__ModuleLoader__.load({
|
|
|
7059
7371
|
return;
|
|
7060
7372
|
}
|
|
7061
7373
|
if (message.kind === "ws.open") {
|
|
7062
|
-
const input = isRecord$
|
|
7374
|
+
const input = isRecord$9(message.input) ? message.input : {};
|
|
7063
7375
|
const opened = this.options.transport.openWebStreamWithId("web.ws.open", {
|
|
7064
7376
|
sessionId: this.sessionIdValue,
|
|
7065
7377
|
path: resolveRemotePath(typeof input.path === "string" ? input.path : "/")
|
|
@@ -7151,7 +7463,7 @@ window.__ModuleLoader__.load({
|
|
|
7151
7463
|
for await (const value of stream) {
|
|
7152
7464
|
if (!opened) {
|
|
7153
7465
|
opened = true;
|
|
7154
|
-
if (isRecord$
|
|
7466
|
+
if (isRecord$9(value) && value.opened === true) {
|
|
7155
7467
|
this.socketReady.get(id)?.resolve();
|
|
7156
7468
|
this.socketReady.delete(id);
|
|
7157
7469
|
continue;
|
|
@@ -7800,7 +8112,7 @@ window.__ModuleLoader__.load({
|
|
|
7800
8112
|
const policy = "default-src 'self' blob: data:; script-src 'self' blob: data: 'unsafe-inline' 'unsafe-eval'; style-src 'self' blob: data: 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self' blob: data:; media-src 'self' blob: data:; worker-src 'self' blob:; connect-src 'none'; frame-src 'self' blob: data:";
|
|
7801
8113
|
for (const node of documentValue.querySelectorAll("meta[http-equiv]")) if ((node.getAttribute("http-equiv") ?? "").toLowerCase() === "content-security-policy") node.setAttribute("content", policy);
|
|
7802
8114
|
}
|
|
7803
|
-
function isRecord$
|
|
8115
|
+
function isRecord$9(value) {
|
|
7804
8116
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7805
8117
|
}
|
|
7806
8118
|
function toBytes(value) {
|
|
@@ -8917,14 +9229,14 @@ window.__ModuleLoader__.load({
|
|
|
8917
9229
|
//#endregion
|
|
8918
9230
|
//#region src/client/cli-catalog.ts
|
|
8919
9231
|
/** Client 侧访问 Host CLI 命名空间的统一入口。 */
|
|
8920
|
-
async function callCliRpc(rpc, action, payload) {
|
|
9232
|
+
async function callCliRpc(rpc, action, payload, signal) {
|
|
8921
9233
|
let response;
|
|
8922
9234
|
try {
|
|
8923
9235
|
debugInfo("codingns4dsh: cli rpc request", {
|
|
8924
9236
|
channel: CODINGNS_RPC_CHANNEL,
|
|
8925
9237
|
action
|
|
8926
9238
|
});
|
|
8927
|
-
response = await rpc.call(CODINGNS_RPC_CHANNEL, `cli/${action}`, payload);
|
|
9239
|
+
response = await rpc.call(CODINGNS_RPC_CHANNEL, `cli/${action}`, payload, signal);
|
|
8928
9240
|
} catch (error) {
|
|
8929
9241
|
const message = error instanceof Error ? error.message : String(error);
|
|
8930
9242
|
if (!/HTTP (?:404|405)\b/u.test(message)) throw error;
|
|
@@ -8933,7 +9245,7 @@ window.__ModuleLoader__.load({
|
|
|
8933
9245
|
error: message
|
|
8934
9246
|
});
|
|
8935
9247
|
try {
|
|
8936
|
-
response = await rpc.call("/api", `codingns/cli/${action}`, payload);
|
|
9248
|
+
response = await rpc.call("/api", `codingns/cli/${action}`, payload, signal);
|
|
8937
9249
|
} catch (fallbackError) {
|
|
8938
9250
|
console.error("codingns4dsh: cli rpc fallback failed", {
|
|
8939
9251
|
action,
|
|
@@ -9101,11 +9413,11 @@ window.__ModuleLoader__.load({
|
|
|
9101
9413
|
};
|
|
9102
9414
|
}
|
|
9103
9415
|
function readMarker(event) {
|
|
9104
|
-
if (!isRecord$
|
|
9416
|
+
if (!isRecord$8(event)) return null;
|
|
9105
9417
|
const data = event.data;
|
|
9106
|
-
if (!isRecord$
|
|
9107
|
-
const candidate = event.type === "assistant/live-chunk" ? isRecord$
|
|
9108
|
-
if (!isRecord$
|
|
9418
|
+
if (!isRecord$8(data)) return null;
|
|
9419
|
+
const candidate = event.type === "assistant/live-chunk" ? isRecord$8(data.chunk) ? data.chunk.codingnsExternalTool : void 0 : event.type === "assistant/attempt" ? data.codingnsExternalTool : void 0;
|
|
9420
|
+
if (!isRecord$8(candidate) || candidate.source !== "codingns-external-tool") return null;
|
|
9109
9421
|
if (typeof candidate.callId !== "string" || candidate.callId === "") return null;
|
|
9110
9422
|
if (typeof candidate.name !== "string" || typeof candidate.arguments !== "string") return null;
|
|
9111
9423
|
if (candidate.phase !== "start" && candidate.phase !== "update") return null;
|
|
@@ -9121,7 +9433,7 @@ window.__ModuleLoader__.load({
|
|
|
9121
9433
|
...typeof candidate.error === "string" ? { error: candidate.error } : {}
|
|
9122
9434
|
};
|
|
9123
9435
|
}
|
|
9124
|
-
function isRecord$
|
|
9436
|
+
function isRecord$8(value) {
|
|
9125
9437
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9126
9438
|
}
|
|
9127
9439
|
//#endregion
|
|
@@ -9199,7 +9511,11 @@ window.__ModuleLoader__.load({
|
|
|
9199
9511
|
/** 对话根节点上承载当前会话 id 的 DSH 属性。 */
|
|
9200
9512
|
const CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE = "data-conversation-session";
|
|
9201
9513
|
/** 默认隐藏构成明细的适配器:它们自己组装请求,启发式估算与真实上下文不同源。 */
|
|
9202
|
-
const DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = [
|
|
9514
|
+
const DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = [
|
|
9515
|
+
"codex",
|
|
9516
|
+
"qoder",
|
|
9517
|
+
"qoder-cn"
|
|
9518
|
+
];
|
|
9203
9519
|
/** 适配器未知时请求刷新映射的最小间隔,避免面板长时间停留时反复查询。 */
|
|
9204
9520
|
const ADAPTER_REFRESH_INTERVAL_MS = 5e3;
|
|
9205
9521
|
const CONTEXT_BREAKDOWN_STYLE_TEXT = `[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>dl{display:none!important}[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>div:nth-of-type(2){display:none!important}`;
|
|
@@ -9493,7 +9809,7 @@ window.__ModuleLoader__.load({
|
|
|
9493
9809
|
decoratePopupRows(listboxes, options.popupPlaceholder(), iconUrl);
|
|
9494
9810
|
}
|
|
9495
9811
|
decorateReferenceChips(dom, iconUrl);
|
|
9496
|
-
decorateTimelineMessages(dom, iconUrl, options);
|
|
9812
|
+
decorateTimelineMessages$1(dom, iconUrl, options);
|
|
9497
9813
|
decorateComposerPlaceholders(dom, options.composerPlaceholder);
|
|
9498
9814
|
};
|
|
9499
9815
|
const scan = () => {
|
|
@@ -9726,23 +10042,23 @@ window.__ModuleLoader__.load({
|
|
|
9726
10042
|
* `user/message` 也会携带这段传输文本。这里仅处理 DSH 的 userRow/bubble,
|
|
9727
10043
|
* 不触碰 assistant、代码块和普通页面文本,避免把模型内容中的 carrier 改写。
|
|
9728
10044
|
*/
|
|
9729
|
-
function decorateTimelineMessages(dom, iconUrl, options) {
|
|
10045
|
+
function decorateTimelineMessages$1(dom, iconUrl, options) {
|
|
9730
10046
|
const rows = [...dom.querySelectorAll("[class*=\"userRow\"]")];
|
|
9731
10047
|
for (const row of dom.querySelectorAll("[data-submission-echo]")) if (!rows.includes(row)) rows.push(row);
|
|
9732
10048
|
for (const row of rows) {
|
|
9733
10049
|
const bubble = row.querySelector("[class*=\"bubble\"]") ?? row.querySelector("[class*=\"preview\"]");
|
|
9734
10050
|
if (bubble === null) continue;
|
|
9735
|
-
decorateTimelineNode(bubble, dom, iconUrl, options);
|
|
10051
|
+
decorateTimelineNode$1(bubble, dom, iconUrl, options);
|
|
9736
10052
|
}
|
|
9737
10053
|
}
|
|
9738
|
-
function decorateTimelineNode(node, dom, iconUrl, options) {
|
|
10054
|
+
function decorateTimelineNode$1(node, dom, iconUrl, options) {
|
|
9739
10055
|
if (node.nodeType === 3) {
|
|
9740
10056
|
const value = node.nodeValue ?? "";
|
|
9741
10057
|
if (value.includes("<!--codingns:delegate:") || value.includes(DELEGATION_INSTRUCTION_MARKER)) replaceTimelineTextNode(node, value, dom, iconUrl, options);
|
|
9742
10058
|
return;
|
|
9743
10059
|
}
|
|
9744
10060
|
if (node.nodeType !== 1 || node.hasAttribute("data-codingns-delegate-timeline-chip") || node.hasAttribute("data-codingns-delegation-card")) return;
|
|
9745
|
-
for (const child of [...node.childNodes]) decorateTimelineNode(child, dom, iconUrl, options);
|
|
10061
|
+
for (const child of [...node.childNodes]) decorateTimelineNode$1(child, dom, iconUrl, options);
|
|
9746
10062
|
}
|
|
9747
10063
|
/** 解析 Host 改写后的委派指令,提取用户真正关心的任务和目标 Agent。 */
|
|
9748
10064
|
function parseDelegationTimelineCard(value) {
|
|
@@ -9956,6 +10272,51 @@ window.__ModuleLoader__.load({
|
|
|
9956
10272
|
}));
|
|
9957
10273
|
//#endregion
|
|
9958
10274
|
//#region src/client/model-catalog-cache.ts
|
|
10275
|
+
function cacheEntry(rpc, adapterId) {
|
|
10276
|
+
return cacheTimesFor(rpc).get(adapterId);
|
|
10277
|
+
}
|
|
10278
|
+
function cacheTimesFor(rpc) {
|
|
10279
|
+
const key = rpc;
|
|
10280
|
+
let times = modelCatalogCacheTimes.get(key);
|
|
10281
|
+
if (times === void 0) {
|
|
10282
|
+
times = /* @__PURE__ */ new Map();
|
|
10283
|
+
modelCatalogCacheTimes.set(key, times);
|
|
10284
|
+
}
|
|
10285
|
+
return times;
|
|
10286
|
+
}
|
|
10287
|
+
function storeCatalog(rpc, adapterId, value) {
|
|
10288
|
+
getModelCatalogCache(rpc).set(adapterId, value);
|
|
10289
|
+
cacheTimesFor(rpc).set(adapterId, {
|
|
10290
|
+
value,
|
|
10291
|
+
storedAt: Date.now()
|
|
10292
|
+
});
|
|
10293
|
+
}
|
|
10294
|
+
/** 作废某个适配器(或全部适配器)的 Client 目录缓存。 */
|
|
10295
|
+
function invalidateModelCatalogCache(rpc, adapterId) {
|
|
10296
|
+
const cache = getModelCatalogCache(rpc);
|
|
10297
|
+
const times = cacheTimesFor(rpc);
|
|
10298
|
+
if (adapterId === void 0) {
|
|
10299
|
+
cache.clear();
|
|
10300
|
+
times.clear();
|
|
10301
|
+
return;
|
|
10302
|
+
}
|
|
10303
|
+
cache.delete(adapterId);
|
|
10304
|
+
times.delete(adapterId);
|
|
10305
|
+
}
|
|
10306
|
+
function shouldRevalidateModelCatalog(rpc, adapterId, needsRevalidation) {
|
|
10307
|
+
if (!needsRevalidation) return false;
|
|
10308
|
+
const key = rpc;
|
|
10309
|
+
let times = modelCatalogRevalidations.get(key);
|
|
10310
|
+
if (times === void 0) {
|
|
10311
|
+
times = /* @__PURE__ */ new Map();
|
|
10312
|
+
modelCatalogRevalidations.set(key, times);
|
|
10313
|
+
}
|
|
10314
|
+
const last = times.get(adapterId);
|
|
10315
|
+
const now = Date.now();
|
|
10316
|
+
if (last !== void 0 && now - last < 6e4) return false;
|
|
10317
|
+
times.set(adapterId, now);
|
|
10318
|
+
return true;
|
|
10319
|
+
}
|
|
9959
10320
|
function getModelCatalogCache(rpc) {
|
|
9960
10321
|
const key = rpc;
|
|
9961
10322
|
let cache = modelCatalogCaches.get(key);
|
|
@@ -9967,8 +10328,8 @@ window.__ModuleLoader__.load({
|
|
|
9967
10328
|
}
|
|
9968
10329
|
function loadModelCatalog(rpc, adapterId, sessionId) {
|
|
9969
10330
|
const cached = getModelCatalogCache(rpc).get(adapterId);
|
|
9970
|
-
if (cached !== void 0 && cached.fallback !== true) return Promise.resolve(cached);
|
|
9971
|
-
if (cached
|
|
10331
|
+
if (cached !== void 0 && cached.fallback !== true && isFreshCacheEntry(rpc, adapterId, cached)) return Promise.resolve(cached);
|
|
10332
|
+
if (cached !== void 0) invalidateModelCatalogCache(rpc, adapterId);
|
|
9972
10333
|
const loads = getModelCatalogLoadCache(rpc);
|
|
9973
10334
|
const running = loads.get(adapterId);
|
|
9974
10335
|
if (running !== void 0) return running;
|
|
@@ -9976,8 +10337,8 @@ window.__ModuleLoader__.load({
|
|
|
9976
10337
|
adapterId,
|
|
9977
10338
|
...sessionId === void 0 ? {} : { sessionId }
|
|
9978
10339
|
}).then((value) => {
|
|
9979
|
-
if (value.fallback === true)
|
|
9980
|
-
else
|
|
10340
|
+
if (value.fallback === true) invalidateModelCatalogCache(rpc, adapterId);
|
|
10341
|
+
else storeCatalog(rpc, adapterId, value);
|
|
9981
10342
|
return value;
|
|
9982
10343
|
}).finally(() => {
|
|
9983
10344
|
if (loads.get(adapterId) === request) loads.delete(adapterId);
|
|
@@ -9985,6 +10346,12 @@ window.__ModuleLoader__.load({
|
|
|
9985
10346
|
loads.set(adapterId, request);
|
|
9986
10347
|
return request;
|
|
9987
10348
|
}
|
|
10349
|
+
/** 缓存条目是否仍在有效期内;没有时间戳的旧条目按过期处理并触发一次重新读取。 */
|
|
10350
|
+
function isFreshCacheEntry(rpc, adapterId, cached) {
|
|
10351
|
+
const entry = cacheEntry(rpc, adapterId);
|
|
10352
|
+
if (entry === void 0 || entry.value !== cached) return false;
|
|
10353
|
+
return Date.now() - entry.storedAt < MODEL_CATALOG_CACHE_TTL_MS;
|
|
10354
|
+
}
|
|
9988
10355
|
function getModelCatalogLoadCache(rpc) {
|
|
9989
10356
|
const key = rpc;
|
|
9990
10357
|
let cache = modelCatalogLoads.get(key);
|
|
@@ -9994,11 +10361,15 @@ window.__ModuleLoader__.load({
|
|
|
9994
10361
|
}
|
|
9995
10362
|
return cache;
|
|
9996
10363
|
}
|
|
9997
|
-
var modelCatalogCaches, modelCatalogLoads;
|
|
10364
|
+
var modelCatalogCaches, modelCatalogLoads, MODEL_CATALOG_CACHE_TTL_MS, modelCatalogCacheTimes, MODEL_CATALOG_REVALIDATE_INTERVAL_MS, modelCatalogRevalidations;
|
|
9998
10365
|
var init_model_catalog_cache = __esmMin((() => {
|
|
9999
10366
|
init_cli_catalog();
|
|
10000
10367
|
modelCatalogCaches = /* @__PURE__ */ new WeakMap();
|
|
10001
10368
|
modelCatalogLoads = /* @__PURE__ */ new WeakMap();
|
|
10369
|
+
MODEL_CATALOG_CACHE_TTL_MS = 6e4;
|
|
10370
|
+
modelCatalogCacheTimes = /* @__PURE__ */ new WeakMap();
|
|
10371
|
+
MODEL_CATALOG_REVALIDATE_INTERVAL_MS = 6e4;
|
|
10372
|
+
modelCatalogRevalidations = /* @__PURE__ */ new WeakMap();
|
|
10002
10373
|
}));
|
|
10003
10374
|
//#endregion
|
|
10004
10375
|
//#region src/dsh-capabilities/client/primitives-adapter.ts
|
|
@@ -10014,6 +10385,28 @@ window.__ModuleLoader__.load({
|
|
|
10014
10385
|
const value = _deepseek_ai_dsh_client_ui_primitives;
|
|
10015
10386
|
return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? value.IconChevronDownOutline14 ?? unavailableIcon;
|
|
10016
10387
|
}
|
|
10388
|
+
/** 终端工具栏使用的原生刷新图标。 */
|
|
10389
|
+
function resolveRefreshIcon() {
|
|
10390
|
+
const value = _deepseek_ai_dsh_client_ui_primitives;
|
|
10391
|
+
return value.IconRefreshOutlineRegular ?? value.IconRefreshOutlineMedium ?? unavailableIcon;
|
|
10392
|
+
}
|
|
10393
|
+
/** 终端工具栏入口使用的原生工具图标。 */
|
|
10394
|
+
function resolveToolIcon() {
|
|
10395
|
+
const value = _deepseek_ai_dsh_client_ui_primitives;
|
|
10396
|
+
return value.IconSlidersTwoOutlineRegular ?? value.IconSlidersTwoOutlineMedium ?? value.IconSettingsOutlineRegular ?? value.IconSettingsOutlineMedium ?? unavailableIcon;
|
|
10397
|
+
}
|
|
10398
|
+
/** 终端快捷操作的方向图标。 */
|
|
10399
|
+
function resolveTerminalArrowIcon(direction) {
|
|
10400
|
+
const value = _deepseek_ai_dsh_client_ui_primitives;
|
|
10401
|
+
if (direction === "up") return value.IconChevronUpOutlineRegular ?? value.IconChevronUpOutlineMedium ?? unavailableIcon;
|
|
10402
|
+
if (direction === "down") return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? unavailableIcon;
|
|
10403
|
+
if (direction === "left") return value.IconChevronLeftOutlineRegular ?? value.IconChevronLeftOutlineMedium ?? unavailableIcon;
|
|
10404
|
+
return value.IconChevronRightOutlineRegular ?? value.IconChevronRightOutlineMedium ?? unavailableIcon;
|
|
10405
|
+
}
|
|
10406
|
+
/** 终端按键标签使用 DSH 原生 ShortcutKeys,旧版本缺失时退回纯文本。 */
|
|
10407
|
+
function resolveShortcutKeys() {
|
|
10408
|
+
return _deepseek_ai_dsh_client_ui_primitives.ShortcutKeys ?? fallbackShortcutKeys;
|
|
10409
|
+
}
|
|
10017
10410
|
/**
|
|
10018
10411
|
* 模型图标:DSH 原生模型选择器在紧凑状态显示的 data 图标(0.1.7 起为 Regular/Medium,
|
|
10019
10412
|
* 0.1.6 为 16 后缀),插件模型选择器收起时与之保持一致。
|
|
@@ -10030,9 +10423,10 @@ window.__ModuleLoader__.load({
|
|
|
10030
10423
|
const value = _deepseek_ai_dsh_client_ui_primitives;
|
|
10031
10424
|
return value.IconShareOutlineRegular ?? value.IconShareOutlineMedium ?? value.IconSendOutlineRegular ?? value.IconSendOutlineMedium ?? resolveDataIcon();
|
|
10032
10425
|
}
|
|
10033
|
-
var unavailableIcon;
|
|
10426
|
+
var unavailableIcon, fallbackShortcutKeys;
|
|
10034
10427
|
var init_primitives_adapter = __esmMin((() => {
|
|
10035
10428
|
unavailableIcon = () => null;
|
|
10429
|
+
fallbackShortcutKeys = ({ keys }) => (0, react.createElement)("span", void 0, keys.join(" + "));
|
|
10036
10430
|
}));
|
|
10037
10431
|
//#endregion
|
|
10038
10432
|
//#region src/client/delegate-command.ts
|
|
@@ -10071,7 +10465,7 @@ window.__ModuleLoader__.load({
|
|
|
10071
10465
|
}
|
|
10072
10466
|
/** 在 commandUi 就绪的作用域内注册 `/委派`;失败只降级为不注册。 */
|
|
10073
10467
|
function registerDelegateCommandIn(ctx, options) {
|
|
10074
|
-
const commandUi = readCommandUi(ctx);
|
|
10468
|
+
const commandUi = readCommandUi$1(ctx);
|
|
10075
10469
|
if (commandUi === void 0) {
|
|
10076
10470
|
debugWarn("codingns4dsh: /委派 未注册,当前 DSH 未提供 commandUi 服务");
|
|
10077
10471
|
return;
|
|
@@ -10101,7 +10495,7 @@ window.__ModuleLoader__.load({
|
|
|
10101
10495
|
return list;
|
|
10102
10496
|
},
|
|
10103
10497
|
async onSelect(option, session) {
|
|
10104
|
-
const input = readSessionInput(ctx, session.sessionId);
|
|
10498
|
+
const input = readSessionInput$1(ctx, session.sessionId);
|
|
10105
10499
|
if (input === void 0) return;
|
|
10106
10500
|
const snapshot = input.state.getSnapshot();
|
|
10107
10501
|
const draft = snapshot.draft ?? "";
|
|
@@ -10159,7 +10553,7 @@ window.__ModuleLoader__.load({
|
|
|
10159
10553
|
}
|
|
10160
10554
|
/** 注册结构化 Agent 引用源:显示层使用 chip,提交层再序列化为 Host carrier。 */
|
|
10161
10555
|
function registerDelegateReferenceSource(ctx, options) {
|
|
10162
|
-
const inputTriggers = readService(ctx, "inputTriggers");
|
|
10556
|
+
const inputTriggers = readService$1(ctx, "inputTriggers");
|
|
10163
10557
|
const source = {
|
|
10164
10558
|
trigger: "@",
|
|
10165
10559
|
name: DELEGATE_REFERENCE_SOURCE,
|
|
@@ -10262,8 +10656,8 @@ window.__ModuleLoader__.load({
|
|
|
10262
10656
|
};
|
|
10263
10657
|
const inputFor = (sessionId) => {
|
|
10264
10658
|
try {
|
|
10265
|
-
const conversation = readConversation(ctx);
|
|
10266
|
-
const actx = readSessions(ctx)?.scope(sessionId);
|
|
10659
|
+
const conversation = readConversation$1(ctx);
|
|
10660
|
+
const actx = readSessions$1(ctx)?.scope(sessionId);
|
|
10267
10661
|
if (actx !== void 0 && conversation !== void 0) return conversation.input.for(actx);
|
|
10268
10662
|
return conversation?.input.shell?.(sessionId);
|
|
10269
10663
|
} catch {
|
|
@@ -10627,29 +11021,29 @@ window.__ModuleLoader__.load({
|
|
|
10627
11021
|
query: before.slice(hash + 1)
|
|
10628
11022
|
};
|
|
10629
11023
|
}
|
|
10630
|
-
function readSessionInput(ctx, sessionId) {
|
|
11024
|
+
function readSessionInput$1(ctx, sessionId) {
|
|
10631
11025
|
try {
|
|
10632
|
-
const actx = readSessions(ctx)?.scope(sessionId);
|
|
11026
|
+
const actx = readSessions$1(ctx)?.scope(sessionId);
|
|
10633
11027
|
if (actx === void 0) return void 0;
|
|
10634
|
-
return readConversation(ctx)?.input.for(actx);
|
|
11028
|
+
return readConversation$1(ctx)?.input.for(actx);
|
|
10635
11029
|
} catch {
|
|
10636
11030
|
return;
|
|
10637
11031
|
}
|
|
10638
11032
|
}
|
|
10639
|
-
function readCommandUi(ctx) {
|
|
10640
|
-
const service = readService(ctx, "commandUi");
|
|
11033
|
+
function readCommandUi$1(ctx) {
|
|
11034
|
+
const service = readService$1(ctx, "commandUi");
|
|
10641
11035
|
return service !== void 0 && typeof service.register === "function" ? service : void 0;
|
|
10642
11036
|
}
|
|
10643
|
-
function readSessions(ctx) {
|
|
10644
|
-
const service = readService(ctx, "sessions");
|
|
11037
|
+
function readSessions$1(ctx) {
|
|
11038
|
+
const service = readService$1(ctx, "sessions");
|
|
10645
11039
|
return service !== void 0 && typeof service.scope === "function" ? service : void 0;
|
|
10646
11040
|
}
|
|
10647
|
-
function readConversation(ctx) {
|
|
10648
|
-
const service = readService(ctx, "conversation");
|
|
11041
|
+
function readConversation$1(ctx) {
|
|
11042
|
+
const service = readService$1(ctx, "conversation");
|
|
10649
11043
|
return service !== void 0 && typeof service.input?.for === "function" ? service : void 0;
|
|
10650
11044
|
}
|
|
10651
11045
|
/** Cordis 直接读取未声明的服务会抛 without-inject;探测统一走这里。 */
|
|
10652
|
-
function readService(ctx, name) {
|
|
11046
|
+
function readService$1(ctx, name) {
|
|
10653
11047
|
try {
|
|
10654
11048
|
return ctx.get(name);
|
|
10655
11049
|
} catch {
|
|
@@ -10721,6 +11115,20 @@ window.__ModuleLoader__.load({
|
|
|
10721
11115
|
return modelServiceTiers(model).find((tier) => tier.id === id);
|
|
10722
11116
|
}
|
|
10723
11117
|
/**
|
|
11118
|
+
* 目录是否「声明了档位、但未确认官方订阅」——即档位开关缺席的原因可能只是判定过期。
|
|
11119
|
+
*
|
|
11120
|
+
* 这一状态有两种来源,界面上无法区分:
|
|
11121
|
+
* - 真实第三方中转/纯 API key:档位确实不会生效,应当持续不展示;
|
|
11122
|
+
* - 判定过期:用户在外部工具(cc-switch 等)里刚切回官方订阅,而页面还持有旧目录。
|
|
11123
|
+
*
|
|
11124
|
+
* 因此它只用来决定「值得再探测一次」,不能用来决定「应该展示开关」。重新探测由
|
|
11125
|
+
* Host 的 Provider 配置指纹保证廉价:配置没变时 Host 直接返回缓存,不会重启 CLI。
|
|
11126
|
+
*/
|
|
11127
|
+
function needsServiceTierRevalidation(catalog) {
|
|
11128
|
+
if (catalog === null || catalog.officialSubscription === true) return false;
|
|
11129
|
+
return catalog.groups.some((group) => group.models.some((model) => modelServiceTiers(model).length > 0));
|
|
11130
|
+
}
|
|
11131
|
+
/**
|
|
10724
11132
|
* 在“只改了其它字段”的部分更新里保留当前档位选择。
|
|
10725
11133
|
*
|
|
10726
11134
|
* 这是本功能最容易被写坏的地方:档位与模型/思考等级共用同一份会话配置,任何一次
|
|
@@ -10748,7 +11156,9 @@ window.__ModuleLoader__.load({
|
|
|
10748
11156
|
var cli_slots_exports = /* @__PURE__ */ __exportAll({
|
|
10749
11157
|
AgentSlot: () => AgentSlot,
|
|
10750
11158
|
ModelSlot: () => ModelSlot,
|
|
10751
|
-
|
|
11159
|
+
cliSessionSelectionRevision: () => cliSessionSelectionRevision,
|
|
11160
|
+
registerCliConversationSlots: () => registerCliConversationSlots,
|
|
11161
|
+
waitForCliSessionSelection: () => waitForCliSessionSelection
|
|
10752
11162
|
});
|
|
10753
11163
|
function installComposerStyles() {
|
|
10754
11164
|
if (typeof document === "undefined" || document.querySelector(`style[data-plugin-css="${CLI_STYLE_ID}"]`) !== null) return;
|
|
@@ -10781,6 +11191,20 @@ window.__ModuleLoader__.load({
|
|
|
10781
11191
|
].join("");
|
|
10782
11192
|
document.head.appendChild(style);
|
|
10783
11193
|
}
|
|
11194
|
+
/** 等待会话 Agent 选择写入 Host,避免切换后立即打开命令菜单仍读到旧 Agent。 */
|
|
11195
|
+
async function waitForCliSessionSelection(sessionId) {
|
|
11196
|
+
if (sessionId.trim() === "") return;
|
|
11197
|
+
for (;;) {
|
|
11198
|
+
const pending = selectionUpdates.get(sessionId);
|
|
11199
|
+
if (pending === void 0) return;
|
|
11200
|
+
await pending.promise;
|
|
11201
|
+
if (selectionUpdates.get(sessionId) === pending) return;
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
11204
|
+
/** 读取当前会话 Agent 选择版本,供按会话缓存的目录在切换 Agent 后失效。 */
|
|
11205
|
+
function cliSessionSelectionRevision(sessionId) {
|
|
11206
|
+
return selectionRevisions.get(sessionId) ?? 0;
|
|
11207
|
+
}
|
|
10784
11208
|
/** 在 Agent 和模型两个 Slot 之间共享当前会话选择。 */
|
|
10785
11209
|
function useSelection(sessionId, rpc) {
|
|
10786
11210
|
const [selection, setSelection] = (0, react.useState)(() => sessionId ? selections.get(sessionId) ?? DEFAULT_SELECTION : DEFAULT_SELECTION);
|
|
@@ -11084,6 +11508,7 @@ window.__ModuleLoader__.load({
|
|
|
11084
11508
|
});
|
|
11085
11509
|
const catalog = catalogState?.adapterId === selection.adapterId ? catalogState.value : null;
|
|
11086
11510
|
const loading = selection.adapterId !== "dsh" && catalog === null && refreshingAdapterId === selection.adapterId;
|
|
11511
|
+
const serviceTierRevalidation = needsServiceTierRevalidation(catalog);
|
|
11087
11512
|
(0, react.useEffect)(() => {
|
|
11088
11513
|
if (selection.adapterId === "dsh") {
|
|
11089
11514
|
setCatalogState(null);
|
|
@@ -11099,6 +11524,7 @@ window.__ModuleLoader__.load({
|
|
|
11099
11524
|
value: cached
|
|
11100
11525
|
});
|
|
11101
11526
|
setRefreshingAdapterId(adapterId);
|
|
11527
|
+
if (shouldRevalidateModelCatalog(props.rpc, adapterId, needsServiceTierRevalidation(cached ?? null))) invalidateModelCatalogCache(props.rpc, adapterId);
|
|
11102
11528
|
loadModelCatalog(props.rpc, adapterId, sessionId).then((value) => {
|
|
11103
11529
|
if (!active) return;
|
|
11104
11530
|
setCatalogState({
|
|
@@ -11119,7 +11545,7 @@ window.__ModuleLoader__.load({
|
|
|
11119
11545
|
if (model.id !== currentSelection.modelId || effort !== currentSelection.effortId || tier !== currentSelection.serviceTierId) update({
|
|
11120
11546
|
adapterId,
|
|
11121
11547
|
modelId: model.id,
|
|
11122
|
-
|
|
11548
|
+
effortId: effort ?? "default",
|
|
11123
11549
|
...tier ? { serviceTierId: tier } : {}
|
|
11124
11550
|
});
|
|
11125
11551
|
};
|
|
@@ -11149,13 +11575,33 @@ window.__ModuleLoader__.load({
|
|
|
11149
11575
|
sessionId,
|
|
11150
11576
|
catalogRetry
|
|
11151
11577
|
]);
|
|
11578
|
+
/**
|
|
11579
|
+
* 档位判定处于「未确认官方订阅」时周期复检。
|
|
11580
|
+
*
|
|
11581
|
+
* 用户在 cc-switch 之类的工具里切回官方订阅不会触发页面事件,唯一能发现变化的
|
|
11582
|
+
* 方式就是再探测一次。这里只负责到点触发一次目录重读,是否真正作废缓存交给主
|
|
11583
|
+
* effect 的节流判定——若在本 effect 里调用节流函数,会先消耗掉窗口,紧接着主
|
|
11584
|
+
* effect 的判定就被挡下,反而不会重新请求。
|
|
11585
|
+
*
|
|
11586
|
+
* 探测本身很廉价:Host 比对配置指纹后,配置没变就直接返回缓存,不会重启 CLI。
|
|
11587
|
+
* 真实第三方接入会长期停在该状态,因此轮询持续存在,代价是每分钟一次缓存命中的
|
|
11588
|
+
* RPC;这换取的是用户切换供应商后无需刷新页面。
|
|
11589
|
+
*/
|
|
11590
|
+
(0, react.useEffect)(() => {
|
|
11591
|
+
if (!serviceTierRevalidation) return;
|
|
11592
|
+
const timer = setInterval(() => {
|
|
11593
|
+
setCatalogRetry((value) => value + 1);
|
|
11594
|
+
}, MODEL_CATALOG_REVALIDATE_INTERVAL_MS + 5e3);
|
|
11595
|
+
return () => clearInterval(timer);
|
|
11596
|
+
}, [selection.adapterId, serviceTierRevalidation]);
|
|
11152
11597
|
(0, react.useEffect)(() => {
|
|
11153
11598
|
if (selection.adapterId === "dsh") setOpen(false);
|
|
11154
11599
|
}, [selection.adapterId]);
|
|
11155
11600
|
if (selection.adapterId === "dsh") return null;
|
|
11156
11601
|
const model = catalog === null ? void 0 : findModel(catalog, selection.modelId) ?? firstModel(catalog);
|
|
11157
11602
|
const efforts = model?.efforts ?? [];
|
|
11158
|
-
const
|
|
11603
|
+
const storedEffort = selection.effortId;
|
|
11604
|
+
const effortValue = storedEffort !== void 0 && (model === void 0 || efforts.includes(storedEffort)) ? storedEffort : defaultEffort(efforts) ?? "default";
|
|
11159
11605
|
const modelLabel = model?.name ?? (loading ? t("cli.loadingModel") : t("cli.noModelsAvailable"));
|
|
11160
11606
|
const effortLabel = effortDisplayName(model, effortValue, t("cli.defaultEffort"));
|
|
11161
11607
|
const modelUnavailable = model === void 0;
|
|
@@ -11166,7 +11612,7 @@ window.__ModuleLoader__.load({
|
|
|
11166
11612
|
update({
|
|
11167
11613
|
adapterId: selection.adapterId,
|
|
11168
11614
|
modelId: next.id,
|
|
11169
|
-
|
|
11615
|
+
effortId: nextEffort ?? "default",
|
|
11170
11616
|
...nextTier ? { serviceTierId: nextTier } : {}
|
|
11171
11617
|
});
|
|
11172
11618
|
setOpen(false);
|
|
@@ -11501,6 +11947,7 @@ window.__ModuleLoader__.load({
|
|
|
11501
11947
|
minWidth: 240,
|
|
11502
11948
|
maxWidth: "min(420px, calc(100vw - 32px))",
|
|
11503
11949
|
maxHeight: "min(360px, calc(100vh - 96px))",
|
|
11950
|
+
overflowX: "hidden",
|
|
11504
11951
|
overflowY: "auto",
|
|
11505
11952
|
padding: 4,
|
|
11506
11953
|
border: 0,
|
|
@@ -11509,6 +11956,7 @@ window.__ModuleLoader__.load({
|
|
|
11509
11956
|
nativeMenuCellStyle = {
|
|
11510
11957
|
width: "100%",
|
|
11511
11958
|
minHeight: 40,
|
|
11959
|
+
boxSizing: "border-box",
|
|
11512
11960
|
color: "inherit",
|
|
11513
11961
|
cursor: "pointer",
|
|
11514
11962
|
background: "transparent",
|
|
@@ -11525,6 +11973,7 @@ window.__ModuleLoader__.load({
|
|
|
11525
11973
|
serviceTierRowStyle = {
|
|
11526
11974
|
width: "100%",
|
|
11527
11975
|
minHeight: 40,
|
|
11976
|
+
boxSizing: "border-box",
|
|
11528
11977
|
color: "inherit",
|
|
11529
11978
|
borderRadius: 10,
|
|
11530
11979
|
padding: "0 10px",
|
|
@@ -12229,7 +12678,7 @@ window.__ModuleLoader__.load({
|
|
|
12229
12678
|
...value,
|
|
12230
12679
|
defaultProfile: event.currentTarget.value
|
|
12231
12680
|
}),
|
|
12232
|
-
style: fieldStyle$
|
|
12681
|
+
style: fieldStyle$2
|
|
12233
12682
|
}, ...profileOptions(value.defaultProfile, hostStatus, t).map((profile) => (0, react.createElement)("option", {
|
|
12234
12683
|
key: profile.profileId,
|
|
12235
12684
|
value: profile.profileId,
|
|
@@ -12244,7 +12693,7 @@ window.__ModuleLoader__.load({
|
|
|
12244
12693
|
...value,
|
|
12245
12694
|
bindingScope: event.currentTarget.value === "session" ? "session" : "workspace"
|
|
12246
12695
|
}),
|
|
12247
|
-
style: fieldStyle$
|
|
12696
|
+
style: fieldStyle$2
|
|
12248
12697
|
}, (0, react.createElement)("option", { value: "workspace" }, t("terminal.workspaceBinding")), (0, react.createElement)("option", { value: "session" }, t("terminal.sessionBinding")))), (0, react.createElement)("div", { style: dshSettingsSectionHeaderStyle }, (0, react.createElement)("h3", { style: {
|
|
12249
12698
|
margin: 0,
|
|
12250
12699
|
color: dshThemeColor.labelPrimary,
|
|
@@ -12306,7 +12755,7 @@ window.__ModuleLoader__.load({
|
|
|
12306
12755
|
value: appearance.cursorStyle,
|
|
12307
12756
|
disabled,
|
|
12308
12757
|
onChange: (event) => updateAppearance({ cursorStyle: parseCursorStyle(event.currentTarget.value) }),
|
|
12309
|
-
style: fieldStyle$
|
|
12758
|
+
style: fieldStyle$2
|
|
12310
12759
|
}, (0, react.createElement)("option", { value: "block" }, t("terminal.cursorBlock")), (0, react.createElement)("option", { value: "bar" }, t("terminal.cursorBar")), (0, react.createElement)("option", { value: "underline" }, t("terminal.cursorUnderline")))), (0, react.createElement)(NumberField, {
|
|
12311
12760
|
label: t("terminal.scrollback"),
|
|
12312
12761
|
value: appearance.scrollback,
|
|
@@ -12355,7 +12804,7 @@ window.__ModuleLoader__.load({
|
|
|
12355
12804
|
disabled,
|
|
12356
12805
|
onChange: (event) => onChange(event.currentTarget.value === "" ? null : Number(event.currentTarget.value)),
|
|
12357
12806
|
style: {
|
|
12358
|
-
...fieldStyle$
|
|
12807
|
+
...fieldStyle$2,
|
|
12359
12808
|
flex: 1,
|
|
12360
12809
|
minWidth: 0
|
|
12361
12810
|
}
|
|
@@ -12419,7 +12868,7 @@ window.__ModuleLoader__.load({
|
|
|
12419
12868
|
function parseCursorStyle(value) {
|
|
12420
12869
|
return value === "block" || value === "bar" || value === "underline" ? value : null;
|
|
12421
12870
|
}
|
|
12422
|
-
const fieldStyle$
|
|
12871
|
+
const fieldStyle$2 = dshSettingsFieldStyle;
|
|
12423
12872
|
const buttonStyle = {
|
|
12424
12873
|
...dshSettingsButtonStyle,
|
|
12425
12874
|
flex: "0 0 auto",
|
|
@@ -12483,17 +12932,17 @@ window.__ModuleLoader__.load({
|
|
|
12483
12932
|
*/
|
|
12484
12933
|
function readNativeWorkspaceListStore(uiContext) {
|
|
12485
12934
|
const store = readNativeService(uiContext, "workspaces", "list");
|
|
12486
|
-
if (!isRecord$
|
|
12935
|
+
if (!isRecord$7(store) || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") return void 0;
|
|
12487
12936
|
return store;
|
|
12488
12937
|
}
|
|
12489
12938
|
/** 读取原生 Workspace 快照;形状不符或服务缺失时返回 undefined,调用方退回原生 Remote。 */
|
|
12490
12939
|
function readNativeWorkspaceSnapshot(uiContext) {
|
|
12491
12940
|
const snapshot = readNativeWorkspaceListStore(uiContext)?.getSnapshot();
|
|
12492
|
-
const record = isRecord$
|
|
12941
|
+
const record = isRecord$7(snapshot) ? snapshot : void 0;
|
|
12493
12942
|
if (record === void 0 || !Array.isArray(record.items)) return void 0;
|
|
12494
12943
|
return {
|
|
12495
12944
|
items: record.items.flatMap((item) => {
|
|
12496
|
-
const value = isRecord$
|
|
12945
|
+
const value = isRecord$7(item) ? item : void 0;
|
|
12497
12946
|
const workspaceId = readText(value?.workspaceId);
|
|
12498
12947
|
if (workspaceId === void 0) return [];
|
|
12499
12948
|
const path = readText(value?.path);
|
|
@@ -12516,7 +12965,7 @@ window.__ModuleLoader__.load({
|
|
|
12516
12965
|
if (typeof getter !== "function") return void 0;
|
|
12517
12966
|
const service = getter.call(uiContext, name);
|
|
12518
12967
|
if (field === void 0) return service;
|
|
12519
|
-
return isRecord$
|
|
12968
|
+
return isRecord$7(service) ? service[field] : void 0;
|
|
12520
12969
|
} catch {
|
|
12521
12970
|
return;
|
|
12522
12971
|
}
|
|
@@ -12530,7 +12979,7 @@ window.__ModuleLoader__.load({
|
|
|
12530
12979
|
return text === void 0 ? [] : [text];
|
|
12531
12980
|
}) : [];
|
|
12532
12981
|
}
|
|
12533
|
-
function isRecord$
|
|
12982
|
+
function isRecord$7(value) {
|
|
12534
12983
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12535
12984
|
}
|
|
12536
12985
|
//#endregion
|
|
@@ -12564,7 +13013,7 @@ window.__ModuleLoader__.load({
|
|
|
12564
13013
|
const fiberKey = Object.getOwnPropertyNames(row).find((key) => key.startsWith("__reactFiber$"));
|
|
12565
13014
|
if (fiberKey === void 0) return void 0;
|
|
12566
13015
|
let current = carrier[fiberKey];
|
|
12567
|
-
for (let depth = 0; depth < 32 && isRecord$
|
|
13016
|
+
for (let depth = 0; depth < 32 && isRecord$6(current); depth += 1) {
|
|
12568
13017
|
const fiber = current;
|
|
12569
13018
|
const sessionId = sessionIdFromProps(fiber.memoizedProps) ?? sessionIdFromProps(fiber.pendingProps);
|
|
12570
13019
|
if (sessionId !== void 0) return sessionId;
|
|
@@ -12572,15 +13021,15 @@ window.__ModuleLoader__.load({
|
|
|
12572
13021
|
}
|
|
12573
13022
|
}
|
|
12574
13023
|
function sessionIdFromProps(value) {
|
|
12575
|
-
if (!isRecord$
|
|
13024
|
+
if (!isRecord$6(value)) return void 0;
|
|
12576
13025
|
for (const key of ["node", "result"]) {
|
|
12577
13026
|
const candidate = value[key];
|
|
12578
|
-
if (!isRecord$
|
|
13027
|
+
if (!isRecord$6(candidate) || typeof candidate.id !== "string") continue;
|
|
12579
13028
|
const sessionId = candidate.id.trim();
|
|
12580
13029
|
if (sessionId !== "") return sessionId;
|
|
12581
13030
|
}
|
|
12582
13031
|
}
|
|
12583
|
-
function isRecord$
|
|
13032
|
+
function isRecord$6(value) {
|
|
12584
13033
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12585
13034
|
}
|
|
12586
13035
|
//#endregion
|
|
@@ -12947,7 +13396,7 @@ window.__ModuleLoader__.load({
|
|
|
12947
13396
|
}
|
|
12948
13397
|
function findWorkspaceIdInFiber(value) {
|
|
12949
13398
|
let current = value;
|
|
12950
|
-
for (let depth = 0; depth < 24 && isRecord$
|
|
13399
|
+
for (let depth = 0; depth < 24 && isRecord$5(current); depth += 1) {
|
|
12951
13400
|
for (const props of [current.memoizedProps, current.pendingProps]) {
|
|
12952
13401
|
const id = findWorkspaceIdInProps(props);
|
|
12953
13402
|
if (id !== void 0) return id;
|
|
@@ -12956,7 +13405,7 @@ window.__ModuleLoader__.load({
|
|
|
12956
13405
|
}
|
|
12957
13406
|
}
|
|
12958
13407
|
function findWorkspaceIdInProps(value) {
|
|
12959
|
-
if (!isRecord$
|
|
13408
|
+
if (!isRecord$5(value)) return void 0;
|
|
12960
13409
|
for (const key of ["workspaceId", "workspaceID"]) {
|
|
12961
13410
|
const id = readString$1(value[key]);
|
|
12962
13411
|
if (id !== void 0) return id;
|
|
@@ -13355,7 +13804,7 @@ window.__ModuleLoader__.load({
|
|
|
13355
13804
|
dom.querySelector(`[${WORKSPACE_SESSION_ARCHIVE_MODAL_ATTRIBUTE}]`)?.remove();
|
|
13356
13805
|
}
|
|
13357
13806
|
function normalizeRemote(value) {
|
|
13358
|
-
if (!isRecord$
|
|
13807
|
+
if (!isRecord$5(value)) return {};
|
|
13359
13808
|
const workspace = asRecord$4(readRemoteProperty(value, "workspace")) ?? asRecord$4(readRemoteProperty(value, "workspaceController"));
|
|
13360
13809
|
const session = asRecord$4(readRemoteProperty(value, "session")) ?? asRecord$4(readRemoteProperty(value, "sessionController"));
|
|
13361
13810
|
return {
|
|
@@ -13381,7 +13830,7 @@ window.__ModuleLoader__.load({
|
|
|
13381
13830
|
}) : [];
|
|
13382
13831
|
}
|
|
13383
13832
|
function asRecord$4(value) {
|
|
13384
|
-
return isRecord$
|
|
13833
|
+
return isRecord$5(value) ? value : void 0;
|
|
13385
13834
|
}
|
|
13386
13835
|
/** DSH Remote 的直接调用返回 RemoteResult;测试桩和旧宿主可能直接返回 value。 */
|
|
13387
13836
|
function unwrapRemoteValue$1(value) {
|
|
@@ -13389,7 +13838,7 @@ window.__ModuleLoader__.load({
|
|
|
13389
13838
|
if (record === void 0 || typeof record.ok !== "boolean") return value;
|
|
13390
13839
|
return record.ok ? record.value : void 0;
|
|
13391
13840
|
}
|
|
13392
|
-
function isRecord$
|
|
13841
|
+
function isRecord$5(value) {
|
|
13393
13842
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13394
13843
|
}
|
|
13395
13844
|
//#endregion
|
|
@@ -14110,6 +14559,19 @@ window.__ModuleLoader__.load({
|
|
|
14110
14559
|
//#region src/client/features/workspace-session-enhancement-panel.ts
|
|
14111
14560
|
init_theme();
|
|
14112
14561
|
init_locale();
|
|
14562
|
+
/** 用量查询设置入口的图标按钮:沿用共享按钮表面,只收成方形并居中图标。 */
|
|
14563
|
+
const usageSettingsIconButtonStyle = {
|
|
14564
|
+
...dshSettingsButtonStyle,
|
|
14565
|
+
flex: "0 0 auto",
|
|
14566
|
+
width: 36,
|
|
14567
|
+
height: 36,
|
|
14568
|
+
minHeight: 36,
|
|
14569
|
+
padding: 0,
|
|
14570
|
+
boxSizing: "border-box",
|
|
14571
|
+
display: "inline-flex",
|
|
14572
|
+
alignItems: "center",
|
|
14573
|
+
justifyContent: "center"
|
|
14574
|
+
};
|
|
14113
14575
|
/** 工作区会话增强的单列设置面板。 */
|
|
14114
14576
|
function WorkspaceSessionEnhancementPanel({ services, enabled, snapshot, notify }) {
|
|
14115
14577
|
const t = useCodingNsTranslator(services.locale);
|
|
@@ -14176,6 +14638,25 @@ window.__ModuleLoader__.load({
|
|
|
14176
14638
|
display: "block",
|
|
14177
14639
|
fontSize: 13,
|
|
14178
14640
|
lineHeight: 1.4
|
|
14641
|
+
} }, t("workspace.showSkillQuickReference")), (0, react.createElement)("span", { style: {
|
|
14642
|
+
display: "block",
|
|
14643
|
+
marginTop: 3,
|
|
14644
|
+
...dshSettingsHelpStyle
|
|
14645
|
+
} }, t("workspace.skillQuickReferenceDescription"))), (0, react.createElement)("input", {
|
|
14646
|
+
type: "checkbox",
|
|
14647
|
+
role: "switch",
|
|
14648
|
+
"aria-label": t("workspace.showSkillQuickReference"),
|
|
14649
|
+
checked: value.showSkillQuickReference,
|
|
14650
|
+
disabled,
|
|
14651
|
+
onChange: (event) => updateSetting("showSkillQuickReference", event.currentTarget.checked),
|
|
14652
|
+
style: {
|
|
14653
|
+
flex: "0 0 auto",
|
|
14654
|
+
accentColor: dshThemeColor.accent
|
|
14655
|
+
}
|
|
14656
|
+
})), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
|
|
14657
|
+
display: "block",
|
|
14658
|
+
fontSize: 13,
|
|
14659
|
+
lineHeight: 1.4
|
|
14179
14660
|
} }, t("workspace.showWorkspaceHiding")), (0, react.createElement)("span", { style: {
|
|
14180
14661
|
display: "block",
|
|
14181
14662
|
marginTop: 3,
|
|
@@ -14222,10 +14703,11 @@ window.__ModuleLoader__.load({
|
|
|
14222
14703
|
"aria-haspopup": "dialog",
|
|
14223
14704
|
"aria-expanded": usageSettingsOpen,
|
|
14224
14705
|
"aria-label": t("workspace.subscriptionUsageSettings"),
|
|
14706
|
+
title: t("workspace.subscriptionUsageSettings"),
|
|
14225
14707
|
disabled,
|
|
14226
14708
|
onClick: () => setUsageSettingsOpen(true),
|
|
14227
|
-
style:
|
|
14228
|
-
},
|
|
14709
|
+
style: usageSettingsIconButtonStyle
|
|
14710
|
+
}, createUsageSettingsGearIcon())), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
|
|
14229
14711
|
display: "block",
|
|
14230
14712
|
fontSize: 13,
|
|
14231
14713
|
lineHeight: 1.4
|
|
@@ -14289,6 +14771,40 @@ window.__ModuleLoader__.load({
|
|
|
14289
14771
|
onClose: () => setUsageSettingsOpen(false)
|
|
14290
14772
|
}));
|
|
14291
14773
|
}
|
|
14774
|
+
/**
|
|
14775
|
+
* 用量查询设置入口的齿轮图标。
|
|
14776
|
+
*
|
|
14777
|
+
* 文字收成图形后,可访问名称由按钮自身的 aria-label/title 保留;图标只做装饰,
|
|
14778
|
+
* 对读屏和指针都隐藏,避免重复朗读和吞掉点击。
|
|
14779
|
+
*/
|
|
14780
|
+
function createUsageSettingsGearIcon() {
|
|
14781
|
+
return (0, react.createElement)("svg", {
|
|
14782
|
+
width: 17,
|
|
14783
|
+
height: 17,
|
|
14784
|
+
viewBox: "0 0 24 24",
|
|
14785
|
+
fill: "none",
|
|
14786
|
+
focusable: false,
|
|
14787
|
+
"aria-hidden": true,
|
|
14788
|
+
style: {
|
|
14789
|
+
display: "block",
|
|
14790
|
+
pointerEvents: "none"
|
|
14791
|
+
}
|
|
14792
|
+
}, (0, react.createElement)("path", {
|
|
14793
|
+
d: "M19.43 12.98c.04-.32.07-.65.07-.98s-.02-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1a7.4 7.4 0 0 0-1.7-.98L14.5 2.42A.49.49 0 0 0 14.01 2h-4a.49.49 0 0 0-.49.42L9.14 5.07c-.61.25-1.18.58-1.7.98l-2.49-1a.5.5 0 0 0-.61.22l-2 3.46a.5.5 0 0 0 .12.64l2.11 1.65c-.04.32-.08.65-.08.98s.03.66.08.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46c.12.21.37.3.61.22l2.49-1c.52.4 1.09.73 1.7.98l.38 2.65c.04.24.25.42.49.42h4c.24 0 .45-.18.49-.42l.38-2.65c.61-.25 1.18-.58 1.7-.98l2.49 1c.24.09.49-.01.61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65Z",
|
|
14794
|
+
fill: "currentColor",
|
|
14795
|
+
fillOpacity: .22,
|
|
14796
|
+
stroke: "currentColor",
|
|
14797
|
+
strokeWidth: 1.35,
|
|
14798
|
+
strokeLinejoin: "round"
|
|
14799
|
+
}), (0, react.createElement)("circle", {
|
|
14800
|
+
cx: 12,
|
|
14801
|
+
cy: 12,
|
|
14802
|
+
r: 3.1,
|
|
14803
|
+
fill: dshThemeColor.accent,
|
|
14804
|
+
stroke: "currentColor",
|
|
14805
|
+
strokeWidth: 1.15
|
|
14806
|
+
}));
|
|
14807
|
+
}
|
|
14292
14808
|
//#endregion
|
|
14293
14809
|
//#region src/client/subscription-slot.ts
|
|
14294
14810
|
init_cli_catalog();
|
|
@@ -14404,7 +14920,7 @@ window.__ModuleLoader__.load({
|
|
|
14404
14920
|
setProviderId(selection.providerId ?? null);
|
|
14405
14921
|
}
|
|
14406
14922
|
const intervalMins = props.getRefreshIntervalMins?.() ?? DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.refreshIntervalMins;
|
|
14407
|
-
const cacheKey = `${adapterId}|${selection.providerId ?? ""}`;
|
|
14923
|
+
const cacheKey = `${adapterId}|${selection.providerId ?? ""}|${selection.modelId ?? ""}`;
|
|
14408
14924
|
const cached = subscriptionUsageCache.get(cacheKey);
|
|
14409
14925
|
if (cached !== void 0 && cached.usage.provider?.capability !== "new-api" && isSubscriptionUsageFresh(cached.capturedAt, Date.now(), intervalMins)) {
|
|
14410
14926
|
if (active) setUsage(cached.usage);
|
|
@@ -14412,7 +14928,8 @@ window.__ModuleLoader__.load({
|
|
|
14412
14928
|
}
|
|
14413
14929
|
const next = await callCliRpc(props.rpc, "subscription", {
|
|
14414
14930
|
adapterId,
|
|
14415
|
-
...selection.providerId ? { providerId: selection.providerId } : {}
|
|
14931
|
+
...selection.providerId ? { providerId: selection.providerId } : {},
|
|
14932
|
+
...selection.modelId ? { modelId: selection.modelId } : {}
|
|
14416
14933
|
});
|
|
14417
14934
|
if (next !== null) subscriptionUsageCache.set(cacheKey, {
|
|
14418
14935
|
usage: next,
|
|
@@ -14649,16 +15166,8 @@ window.__ModuleLoader__.load({
|
|
|
14649
15166
|
window: usage.monthly
|
|
14650
15167
|
}
|
|
14651
15168
|
];
|
|
14652
|
-
const
|
|
14653
|
-
|
|
14654
|
-
const expiries = usage.resetCredits === null ? [] : usage.resetCredits.credits.map((credit) => credit.expiresAt).filter((expiry) => expiry !== null).sort((left, right) => left - right);
|
|
14655
|
-
return (0, react.createElement)("div", {
|
|
14656
|
-
className: "codingns4dsh-subscription-popover",
|
|
14657
|
-
role: "dialog",
|
|
14658
|
-
"aria-label": t("usage.popoverSubscriptionUsage", { provider: providerName }),
|
|
14659
|
-
style: subscriptionPopoverStyle
|
|
14660
|
-
}, (0, react.createElement)("div", { style: popoverHeadingStyle }, (0, react.createElement)("strong", void 0, t("usage.popoverSubscriptionTitle", { provider: providerName })), usage.planType && (0, react.createElement)("span", { style: { color: dshThemeColor.labelTertiary } }, formatPlanType(usage.planType))), ...windows.map(({ id, label, window }) => window === null ? null : (0, react.createElement)("section", {
|
|
14661
|
-
key: id,
|
|
15169
|
+
const renderWindow = (key, label, window) => window === null ? null : (0, react.createElement)("section", {
|
|
15170
|
+
key,
|
|
14662
15171
|
style: windowStyle
|
|
14663
15172
|
}, (0, react.createElement)("div", { style: windowHeadingStyle }, (0, react.createElement)("span", void 0, label), (0, react.createElement)("span", void 0, `${formatPercent$1(window.remainingPercent)}%`)), (0, react.createElement)("div", {
|
|
14664
15173
|
role: "progressbar",
|
|
@@ -14673,7 +15182,25 @@ window.__ModuleLoader__.load({
|
|
|
14673
15182
|
}, (0, react.createElement)("span", { style: {
|
|
14674
15183
|
...barFillStyle,
|
|
14675
15184
|
width: `${window.remainingPercent}%`
|
|
14676
|
-
} })), window.resetsAt !== null && (0, react.createElement)("div", { style: resetStyle }, t("usage.resetsIn", { time: formatCountdown(window.resetsAt, t) })))
|
|
15185
|
+
} })), window.resetsAt !== null && (0, react.createElement)("div", { style: resetStyle }, t("usage.resetsIn", { time: formatCountdown(window.resetsAt, t) })));
|
|
15186
|
+
const quotaGroups = usage.groups ?? [];
|
|
15187
|
+
const windowSections = quotaGroups.length > 0 ? quotaGroups.flatMap((group) => [(0, react.createElement)("div", {
|
|
15188
|
+
key: `group:${group.id}`,
|
|
15189
|
+
style: quotaGroupTitleStyle
|
|
15190
|
+
}, formatSubscriptionGroupLabel(group, t)), ...group.windows.map((entry) => renderWindow(`group:${group.id}:${entry.kind}:${entry.label ?? ""}`, formatSubscriptionGroupWindowLabel(entry, t), entry.window))]) : windows.map(({ id, label, window }) => renderWindow(id, label, window));
|
|
15191
|
+
const credits = usage.credits ?? null;
|
|
15192
|
+
const creditText = credits === null ? null : credits.unlimited ? t("usage.creditUnlimited") : credits.balance !== null ? formatCreditBalance(credits.balance) : credits.hasCredits ? "0.00" : "--";
|
|
15193
|
+
const expiries = usage.resetCredits === null ? [] : usage.resetCredits.credits.map((credit) => credit.expiresAt).filter((expiry) => expiry !== null).sort((left, right) => left - right);
|
|
15194
|
+
const accountName = usage.accountName?.trim() || null;
|
|
15195
|
+
const planLabel = usage.planType === null ? null : formatPlanType(usage.planType);
|
|
15196
|
+
const paidPlanLabel = usage.paidPlanType?.trim() || null;
|
|
15197
|
+
const planText = planLabel === null ? paidPlanLabel : paidPlanLabel === null || paidPlanLabel === planLabel ? planLabel : `${planLabel} · ${paidPlanLabel}`;
|
|
15198
|
+
return (0, react.createElement)("div", {
|
|
15199
|
+
className: "codingns4dsh-subscription-popover",
|
|
15200
|
+
role: "dialog",
|
|
15201
|
+
"aria-label": t("usage.popoverSubscriptionUsage", { provider: providerName }),
|
|
15202
|
+
style: subscriptionPopoverStyle
|
|
15203
|
+
}, (0, react.createElement)("div", { style: popoverHeadingStyle }, (0, react.createElement)("div", { style: popoverHeadingTextStyle }, (0, react.createElement)("strong", void 0, t("usage.popoverSubscriptionTitle", { provider: providerName })), accountName !== null && (0, react.createElement)("span", { style: accountMetaStyle }, accountName)), planText !== null && (0, react.createElement)("span", { style: { color: dshThemeColor.labelTertiary } }, planText)), ...windowSections, (creditText !== null || usage.resetCredits !== null) && (0, react.createElement)("section", { style: resetCreditsSectionStyle }, creditText !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.creditBalanceLabel")), (0, react.createElement)("span", { style: resetCreditsValueStyle }, creditText)), usage.resetCredits !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.resetCreditsLabel")), (0, react.createElement)("span", { style: resetCreditsValueGroupStyle }, (0, react.createElement)("span", { style: resetCreditsValueStyle }, t("usage.resetCreditsCount", { count: usage.resetCredits.availableCount })), reset !== null && (0, react.createElement)("button", {
|
|
14677
15204
|
type: "button",
|
|
14678
15205
|
className: "codingns4dsh-subscription-reset",
|
|
14679
15206
|
disabled: !reset.enabled,
|
|
@@ -14768,6 +15295,19 @@ window.__ModuleLoader__.load({
|
|
|
14768
15295
|
if (durationMins % 60 === 0) return t("usage.windowHours", { count: durationMins / 60 });
|
|
14769
15296
|
return t("usage.windowMinutes", { count: durationMins });
|
|
14770
15297
|
}
|
|
15298
|
+
/** 上游分组名本地化:只有已知分组用词条,其余原样显示上游名称。 */
|
|
15299
|
+
function formatSubscriptionGroupLabel(group, t) {
|
|
15300
|
+
if (group.id === "gemini") return t("usage.quotaGroupGemini");
|
|
15301
|
+
if (group.id === "third-party") return t("usage.quotaGroupThirdParty");
|
|
15302
|
+
return group.displayName;
|
|
15303
|
+
}
|
|
15304
|
+
/** 窗口文案按类型取词条,未知类型回退到上游标签。 */
|
|
15305
|
+
function formatSubscriptionGroupWindowLabel(entry, t) {
|
|
15306
|
+
if (entry.kind === "five-hour") return t("usage.windowFiveHour");
|
|
15307
|
+
if (entry.kind === "weekly") return t("usage.windowWeekly");
|
|
15308
|
+
if (entry.kind === "monthly") return t("usage.windowMonthly");
|
|
15309
|
+
return entry.label ?? "";
|
|
15310
|
+
}
|
|
14771
15311
|
/**
|
|
14772
15312
|
* New-API 的普通 Key 没有 Sub2API 那种统一的完整统计对象。
|
|
14773
15313
|
* 这里只投影读取器明确拿到的真实数值,隐藏内部 quota、Key 状态和占位文本,
|
|
@@ -15047,7 +15587,7 @@ window.__ModuleLoader__.load({
|
|
|
15047
15587
|
}
|
|
15048
15588
|
/** 已具备用量读取契约的适配器才挂载底部订阅入口。 */
|
|
15049
15589
|
function isSubscriptionAdapter(adapterId) {
|
|
15050
|
-
return adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "codebuddy" || adapterId === "codebuddy-cn" || adapterId === "dsh" || adapterId === "grok" || adapterId === "kimi" || adapterId === "opencode" || adapterId === "workbuddy" || adapterId === "zcode";
|
|
15590
|
+
return adapterId === "antigravity" || adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "codebuddy" || adapterId === "codebuddy-cn" || adapterId === "dsh" || adapterId === "grok" || adapterId === "kimi" || adapterId === "opencode" || adapterId === "qoder" || adapterId === "qoder-cn" || adapterId === "workbuddy" || adapterId === "zcode";
|
|
15051
15591
|
}
|
|
15052
15592
|
function isRemoteWebContext() {
|
|
15053
15593
|
return globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ === true;
|
|
@@ -15067,6 +15607,8 @@ window.__ModuleLoader__.load({
|
|
|
15067
15607
|
case "grok": return "Grok";
|
|
15068
15608
|
case "kimi": return "Kimi Code";
|
|
15069
15609
|
case "opencode": return "OpenCode";
|
|
15610
|
+
case "qoder": return "Qoder";
|
|
15611
|
+
case "qoder-cn": return "Qoder CN";
|
|
15070
15612
|
case "zcode": return "ZCode";
|
|
15071
15613
|
case "codebuddy": return "CodeBuddy";
|
|
15072
15614
|
case "codebuddy-cn": return "CodeBuddy";
|
|
@@ -15390,6 +15932,19 @@ window.__ModuleLoader__.load({
|
|
|
15390
15932
|
borderBottom: `1px solid ${dshThemeColor.border}`,
|
|
15391
15933
|
fontSize: 14
|
|
15392
15934
|
};
|
|
15935
|
+
const popoverHeadingTextStyle = {
|
|
15936
|
+
display: "grid",
|
|
15937
|
+
gap: 2,
|
|
15938
|
+
minWidth: 0
|
|
15939
|
+
};
|
|
15940
|
+
const accountMetaStyle = {
|
|
15941
|
+
overflow: "hidden",
|
|
15942
|
+
color: dshThemeColor.labelTertiary,
|
|
15943
|
+
fontSize: 11,
|
|
15944
|
+
lineHeight: "16px",
|
|
15945
|
+
textOverflow: "ellipsis",
|
|
15946
|
+
whiteSpace: "nowrap"
|
|
15947
|
+
};
|
|
15393
15948
|
const upstreamMetaStyle = {
|
|
15394
15949
|
display: "flex",
|
|
15395
15950
|
alignItems: "center",
|
|
@@ -15446,6 +16001,13 @@ window.__ModuleLoader__.load({
|
|
|
15446
16001
|
color: dshThemeColor.labelTertiary,
|
|
15447
16002
|
fontSize: 12
|
|
15448
16003
|
};
|
|
16004
|
+
const quotaGroupTitleStyle = {
|
|
16005
|
+
paddingTop: 12,
|
|
16006
|
+
color: dshThemeColor.labelSecondary,
|
|
16007
|
+
fontSize: 12,
|
|
16008
|
+
fontWeight: 600,
|
|
16009
|
+
letterSpacing: ".02em"
|
|
16010
|
+
};
|
|
15449
16011
|
const subscriptionPopoverStyle = {
|
|
15450
16012
|
...dshPopupSurfaceStyle,
|
|
15451
16013
|
position: "absolute",
|
|
@@ -16515,6 +17077,415 @@ window.__ModuleLoader__.load({
|
|
|
16515
17077
|
return Math.abs(left - right) < .002;
|
|
16516
17078
|
}
|
|
16517
17079
|
//#endregion
|
|
17080
|
+
//#region src/client/skill-reference-dom.ts
|
|
17081
|
+
init_cli_slots();
|
|
17082
|
+
/**
|
|
17083
|
+
* Skill 引用的视觉投影。
|
|
17084
|
+
*
|
|
17085
|
+
* 输入框使用 DSH 原生 ReferenceChip;已经提交到时间线的用户消息仍然是普通文本,
|
|
17086
|
+
* 这里把其中有效的 `/skill-name` 投影成同一套原子元素。只扫描用户气泡和提交回显,
|
|
17087
|
+
* 不改写助手正文、代码块或普通页面文本。
|
|
17088
|
+
*/
|
|
17089
|
+
const SKILL_REFERENCE_ATTRIBUTE = "data-codingns-skill-reference";
|
|
17090
|
+
const SKILL_REFERENCE_RAW_ATTRIBUTE = "data-codingns-skill-reference-raw";
|
|
17091
|
+
const SKILL_REFERENCE_STYLE_ID = "codingns4dsh-skill-reference-style";
|
|
17092
|
+
const sessionSkillNames = /* @__PURE__ */ new Map();
|
|
17093
|
+
const catalogListeners = /* @__PURE__ */ new Set();
|
|
17094
|
+
/** 把一个会话最新的 Skill 目录发布给时间线投影和输入框预热使用。 */
|
|
17095
|
+
function publishSkillCatalog(sessionId, catalog) {
|
|
17096
|
+
sessionSkillNames.set(sessionId, new Set(catalog.filter((skill) => skill.enabled).map((skill) => skill.name)));
|
|
17097
|
+
for (const listener of [...catalogListeners]) listener();
|
|
17098
|
+
}
|
|
17099
|
+
/** 返回所有已加载会话的 Skill 名称并集;名称本身区分大小写,匹配时另行兼容大小写。 */
|
|
17100
|
+
function getSkillNames() {
|
|
17101
|
+
const names = /* @__PURE__ */ new Set();
|
|
17102
|
+
for (const sessionNames of sessionSkillNames.values()) for (const name of sessionNames) names.add(name);
|
|
17103
|
+
return names;
|
|
17104
|
+
}
|
|
17105
|
+
function subscribeSkillCatalog(listener) {
|
|
17106
|
+
catalogListeners.add(listener);
|
|
17107
|
+
return () => catalogListeners.delete(listener);
|
|
17108
|
+
}
|
|
17109
|
+
/** 从用户消息中提取有效 Skill 指令;URL、路径中间片段和未索引名称都会被忽略。 */
|
|
17110
|
+
function findSkillReferenceMentions(value, names) {
|
|
17111
|
+
const byLowerName = new Map([...names].map((name) => [name.toLocaleLowerCase(), name]));
|
|
17112
|
+
const matches = [];
|
|
17113
|
+
for (const match of value.matchAll(/\/[A-Za-z0-9][A-Za-z0-9._-]*(?=\s|$)/gu)) {
|
|
17114
|
+
const token = match[0] ?? "";
|
|
17115
|
+
const start = match.index ?? -1;
|
|
17116
|
+
const before = start > 0 ? value[start - 1] : void 0;
|
|
17117
|
+
const name = byLowerName.get(token.slice(1).toLocaleLowerCase());
|
|
17118
|
+
if (start < 0 || name === void 0 || before !== void 0 && !/\s/u.test(before)) continue;
|
|
17119
|
+
matches.push({
|
|
17120
|
+
raw: token,
|
|
17121
|
+
name,
|
|
17122
|
+
start,
|
|
17123
|
+
end: start + token.length
|
|
17124
|
+
});
|
|
17125
|
+
}
|
|
17126
|
+
return matches;
|
|
17127
|
+
}
|
|
17128
|
+
/** 安装 Skill chip 的消息列表投影与统一样式。 */
|
|
17129
|
+
function startSkillReferenceDom(options = {}) {
|
|
17130
|
+
const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
|
|
17131
|
+
const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
|
|
17132
|
+
const getNames = options.getSkillNames ?? getSkillNames;
|
|
17133
|
+
let disposed = false;
|
|
17134
|
+
let scanQueued = false;
|
|
17135
|
+
let observer;
|
|
17136
|
+
const observe = () => {
|
|
17137
|
+
if (disposed || dom === void 0 || observer === void 0) return;
|
|
17138
|
+
observer.observe(dom.documentElement, {
|
|
17139
|
+
childList: true,
|
|
17140
|
+
subtree: true
|
|
17141
|
+
});
|
|
17142
|
+
};
|
|
17143
|
+
const scan = () => {
|
|
17144
|
+
if (disposed || dom === void 0) return;
|
|
17145
|
+
observer?.disconnect();
|
|
17146
|
+
try {
|
|
17147
|
+
decorateTimelineMessages(dom, getNames());
|
|
17148
|
+
} finally {
|
|
17149
|
+
observe();
|
|
17150
|
+
}
|
|
17151
|
+
};
|
|
17152
|
+
const scheduleScan = () => {
|
|
17153
|
+
if (disposed || scanQueued) return;
|
|
17154
|
+
scanQueued = true;
|
|
17155
|
+
queueMicrotask(() => {
|
|
17156
|
+
scanQueued = false;
|
|
17157
|
+
scan();
|
|
17158
|
+
});
|
|
17159
|
+
};
|
|
17160
|
+
if (dom !== void 0) installSkillReferenceStyles(dom);
|
|
17161
|
+
observer = dom === void 0 || Observer === void 0 ? void 0 : new Observer(scheduleScan);
|
|
17162
|
+
observe();
|
|
17163
|
+
const unsubscribeCatalog = subscribeSkillCatalog(scheduleScan);
|
|
17164
|
+
scan();
|
|
17165
|
+
return {
|
|
17166
|
+
refresh: scheduleScan,
|
|
17167
|
+
dispose() {
|
|
17168
|
+
if (disposed) return;
|
|
17169
|
+
disposed = true;
|
|
17170
|
+
observer?.disconnect();
|
|
17171
|
+
unsubscribeCatalog();
|
|
17172
|
+
if (dom !== void 0) removeSkillReferenceNodes(dom);
|
|
17173
|
+
dom?.querySelector(`style[data-plugin-css="${SKILL_REFERENCE_STYLE_ID}"]`)?.remove();
|
|
17174
|
+
}
|
|
17175
|
+
};
|
|
17176
|
+
}
|
|
17177
|
+
function decorateTimelineMessages(dom, names) {
|
|
17178
|
+
const rows = [...dom.querySelectorAll("[class*=\"userRow\"]")];
|
|
17179
|
+
for (const row of dom.querySelectorAll("[data-submission-echo]")) if (!rows.includes(row)) rows.push(row);
|
|
17180
|
+
const byLowerName = new Map([...names].map((name) => [name.toLocaleLowerCase(), name]));
|
|
17181
|
+
if (byLowerName.size === 0) return;
|
|
17182
|
+
for (const row of rows) {
|
|
17183
|
+
const bubble = row.querySelector("[class*=\"bubble\"]") ?? row.querySelector("[class*=\"preview\"]");
|
|
17184
|
+
if (bubble !== null) decorateTimelineNode(bubble, dom, byLowerName);
|
|
17185
|
+
}
|
|
17186
|
+
}
|
|
17187
|
+
function decorateTimelineNode(node, dom, names) {
|
|
17188
|
+
if (node.nodeType === 3) {
|
|
17189
|
+
replaceSkillTextNode(node, dom, names);
|
|
17190
|
+
return;
|
|
17191
|
+
}
|
|
17192
|
+
if (node.nodeType !== 1 || node.hasAttribute("data-codingns-skill-reference") || node.closest("code,pre,[contenteditable=\"true\"]") !== null) return;
|
|
17193
|
+
for (const child of [...node.childNodes]) decorateTimelineNode(child, dom, names);
|
|
17194
|
+
}
|
|
17195
|
+
function replaceSkillTextNode(node, dom, names) {
|
|
17196
|
+
const value = node.nodeValue ?? "";
|
|
17197
|
+
if (value === "" || node.parentElement?.closest("code,pre,[contenteditable=\"true\"]") !== null) return;
|
|
17198
|
+
const matches = findSkillReferenceMentions(value, new Set(names.values()));
|
|
17199
|
+
if (matches.length === 0 || node.parentNode === null) return;
|
|
17200
|
+
const fragment = dom.createDocumentFragment();
|
|
17201
|
+
let cursor = 0;
|
|
17202
|
+
for (const match of matches) {
|
|
17203
|
+
if (match.start < cursor) continue;
|
|
17204
|
+
if (match.start > cursor) fragment.append(dom.createTextNode(value.slice(cursor, match.start)));
|
|
17205
|
+
fragment.append(buildSkillChip(dom, value.slice(match.start, match.end), match.name));
|
|
17206
|
+
cursor = match.end;
|
|
17207
|
+
}
|
|
17208
|
+
if (cursor < value.length) fragment.append(dom.createTextNode(value.slice(cursor)));
|
|
17209
|
+
node.parentNode.replaceChild(fragment, node);
|
|
17210
|
+
}
|
|
17211
|
+
function buildSkillChip(dom, raw, name) {
|
|
17212
|
+
const chip = dom.createElement("span");
|
|
17213
|
+
chip.setAttribute(SKILL_REFERENCE_ATTRIBUTE, name);
|
|
17214
|
+
chip.setAttribute(SKILL_REFERENCE_RAW_ATTRIBUTE, raw);
|
|
17215
|
+
chip.setAttribute("title", `/${name}`);
|
|
17216
|
+
chip.setAttribute("aria-label", `/${name}`);
|
|
17217
|
+
const icon = dom.createElement("span");
|
|
17218
|
+
icon.setAttribute("aria-hidden", "true");
|
|
17219
|
+
icon.textContent = "✦";
|
|
17220
|
+
const label = dom.createElement("span");
|
|
17221
|
+
label.textContent = raw;
|
|
17222
|
+
chip.append(icon, label);
|
|
17223
|
+
return chip;
|
|
17224
|
+
}
|
|
17225
|
+
function removeSkillReferenceNodes(dom) {
|
|
17226
|
+
for (const chip of dom.querySelectorAll(`[${SKILL_REFERENCE_ATTRIBUTE}]`)) {
|
|
17227
|
+
const raw = chip.getAttribute(SKILL_REFERENCE_RAW_ATTRIBUTE);
|
|
17228
|
+
const parent = chip.parentNode;
|
|
17229
|
+
if (raw !== null && parent !== null) parent.replaceChild(dom.createTextNode(raw), chip);
|
|
17230
|
+
}
|
|
17231
|
+
}
|
|
17232
|
+
function installSkillReferenceStyles(dom) {
|
|
17233
|
+
if (dom.querySelector(`style[data-plugin-css="codingns4dsh-skill-reference-style"]`) !== null) return;
|
|
17234
|
+
const style = dom.createElement("style");
|
|
17235
|
+
style.setAttribute("data-plugin", "codingns4dsh");
|
|
17236
|
+
style.setAttribute("data-plugin-css", SKILL_REFERENCE_STYLE_ID);
|
|
17237
|
+
style.textContent = [
|
|
17238
|
+
`[${SKILL_REFERENCE_ATTRIBUTE}], [data-composer-chip="codingns-skills"]{display:inline-flex;align-items:center;vertical-align:baseline;gap:4px;padding:1px 7px;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.22));border-radius:999px;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary);white-space:nowrap;font-size:.94em;line-height:1.45;user-select:all}`,
|
|
17239
|
+
`[${SKILL_REFERENCE_ATTRIBUTE}], [data-composer-chip="codingns-skills"]{border-color:color-mix(in srgb,var(--dsw-alias-label-primary,#888) 18%,transparent);background:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)) 82%,transparent)}`,
|
|
17240
|
+
`[${SKILL_REFERENCE_ATTRIBUTE}]>span:first-child{color:var(--dsw-alias-brand-primary,var(--dsw-alias-label-primary));font-size:.8em}`,
|
|
17241
|
+
`[${SKILL_REFERENCE_ATTRIBUTE}]{cursor:default}`,
|
|
17242
|
+
`[data-composer-chip="codingns-skills"]>svg{color:var(--dsw-alias-brand-primary,var(--dsw-alias-label-primary))}`
|
|
17243
|
+
].join("");
|
|
17244
|
+
dom.head.appendChild(style);
|
|
17245
|
+
}
|
|
17246
|
+
//#endregion
|
|
17247
|
+
//#region src/client/skill-command.ts
|
|
17248
|
+
init_cli_catalog();
|
|
17249
|
+
init_debug();
|
|
17250
|
+
const SKILL_COMMAND_NAME = "skills";
|
|
17251
|
+
const SKILL_INPUT_SOURCE = "codingns-skills";
|
|
17252
|
+
const SKILL_CATALOG_CACHE_TTL_MS = 3e4;
|
|
17253
|
+
const skillCatalogCache = /* @__PURE__ */ new Map();
|
|
17254
|
+
/** 把 Codex 原生 Skill 目录接入 DSH `/` 菜单。 */
|
|
17255
|
+
function registerSkillCommand(ctx, options) {
|
|
17256
|
+
const state = {
|
|
17257
|
+
catalogReady: false,
|
|
17258
|
+
disposeFallback: void 0
|
|
17259
|
+
};
|
|
17260
|
+
const markCatalogReady = (catalog) => {
|
|
17261
|
+
if (!catalog.some((skill) => skill.enabled) || state.catalogReady) return;
|
|
17262
|
+
state.catalogReady = true;
|
|
17263
|
+
state.disposeFallback?.();
|
|
17264
|
+
state.disposeFallback = void 0;
|
|
17265
|
+
};
|
|
17266
|
+
const commandFiber = ctx.inject(["commandUi"], (scope) => {
|
|
17267
|
+
if (state.catalogReady) return;
|
|
17268
|
+
const dispose = registerSkillCommandFallback(scope, options, markCatalogReady);
|
|
17269
|
+
state.disposeFallback = dispose;
|
|
17270
|
+
});
|
|
17271
|
+
const triggerFiber = ctx.inject(["inputTriggers"], (scope) => {
|
|
17272
|
+
registerSkillInputTriggerSource(scope, options, markCatalogReady);
|
|
17273
|
+
});
|
|
17274
|
+
return () => {
|
|
17275
|
+
state.disposeFallback?.();
|
|
17276
|
+
commandFiber.dispose();
|
|
17277
|
+
triggerFiber.dispose();
|
|
17278
|
+
};
|
|
17279
|
+
}
|
|
17280
|
+
/** 在没有原生 inputTriggers 时保留旧版 DSH 的可用 Skill 菜单。 */
|
|
17281
|
+
function registerSkillCommandFallback(ctx, options, onCatalogReady) {
|
|
17282
|
+
const commandUi = readCommandUi(ctx);
|
|
17283
|
+
if (commandUi === void 0) {
|
|
17284
|
+
debugWarn("codingns4dsh: /skills 未注册,当前 DSH 未提供 commandUi 服务");
|
|
17285
|
+
return;
|
|
17286
|
+
}
|
|
17287
|
+
const t = options.locale.bind("codingns");
|
|
17288
|
+
try {
|
|
17289
|
+
return ctx.effect(() => commandUi.register({
|
|
17290
|
+
name: SKILL_COMMAND_NAME,
|
|
17291
|
+
label: () => t("skills.label"),
|
|
17292
|
+
description: () => t("skills.description"),
|
|
17293
|
+
available: () => true,
|
|
17294
|
+
ui: {
|
|
17295
|
+
kind: "popupSelect",
|
|
17296
|
+
searchMode: "fuzzy-label",
|
|
17297
|
+
searchLabels: () => ({
|
|
17298
|
+
placeholder: t("skills.searchPlaceholder"),
|
|
17299
|
+
empty: t("skills.searchEmpty"),
|
|
17300
|
+
noResults: t("skills.searchNoResults")
|
|
17301
|
+
}),
|
|
17302
|
+
async options(session, signal) {
|
|
17303
|
+
await waitForCliSessionSelection(session.sessionId);
|
|
17304
|
+
const catalog = await loadSkillCatalog(options.rpc, session.sessionId, signal, true);
|
|
17305
|
+
onCatalogReady(catalog);
|
|
17306
|
+
return catalog.filter((skill) => skill.enabled).map((skill) => ({
|
|
17307
|
+
id: skill.name,
|
|
17308
|
+
label: skill.displayName ?? skill.name,
|
|
17309
|
+
...skill.description.trim() === "" ? {} : { detail: skill.description }
|
|
17310
|
+
}));
|
|
17311
|
+
},
|
|
17312
|
+
onSelect(option, session) {
|
|
17313
|
+
const input = readSessionInput(ctx, session.sessionId);
|
|
17314
|
+
if (input === void 0 || input.setDraft === void 0 && input.insertReference === void 0) {
|
|
17315
|
+
input?.notify("error", t("skills.draftUnsupported"));
|
|
17316
|
+
return;
|
|
17317
|
+
}
|
|
17318
|
+
const snapshot = input.state.getSnapshot();
|
|
17319
|
+
const draft = snapshot.draft ?? "";
|
|
17320
|
+
const selection = input.caretSpan?.();
|
|
17321
|
+
const span = skillCommandSpan(draft, snapshot.draftRev ?? selection?.draftRev ?? 0, selection);
|
|
17322
|
+
const token = `/${option.id} `;
|
|
17323
|
+
const reference = {
|
|
17324
|
+
source: SKILL_INPUT_SOURCE,
|
|
17325
|
+
ref: option.id,
|
|
17326
|
+
label: option.label,
|
|
17327
|
+
appearance: "session",
|
|
17328
|
+
clipboardText: `/${option.id}`
|
|
17329
|
+
};
|
|
17330
|
+
if (span !== void 0 && input.insertReference?.(reference, span) === true) return;
|
|
17331
|
+
if (input.setDraft === void 0) {
|
|
17332
|
+
input.notify("error", t("skills.draftUnsupported"));
|
|
17333
|
+
return;
|
|
17334
|
+
}
|
|
17335
|
+
if (span !== void 0) {
|
|
17336
|
+
input.setDraft(`${draft.slice(0, span.start)}${token}${draft.slice(span.end)}`);
|
|
17337
|
+
return;
|
|
17338
|
+
}
|
|
17339
|
+
const caret = selection?.end ?? draft.length;
|
|
17340
|
+
input.setDraft(`${draft.slice(0, caret)}${token}${draft.slice(caret)}`);
|
|
17341
|
+
}
|
|
17342
|
+
}
|
|
17343
|
+
}), "codingns4dsh: skills command");
|
|
17344
|
+
} catch (error) {
|
|
17345
|
+
debugWarn("codingns4dsh: /skills 命令注册失败", { error: error instanceof Error ? error.message : String(error) });
|
|
17346
|
+
return;
|
|
17347
|
+
}
|
|
17348
|
+
}
|
|
17349
|
+
/** 注册直接的 `/` Skill 搜索源。 */
|
|
17350
|
+
function registerSkillInputTriggerSource(ctx, options, onCatalogReady) {
|
|
17351
|
+
const inputTriggers = readService(ctx, "inputTriggers");
|
|
17352
|
+
if (inputTriggers === void 0 || typeof inputTriggers.registerSource !== "function") {
|
|
17353
|
+
debugWarn("codingns4dsh: Skill `/` 搜索源未注册,当前 DSH 未提供 inputTriggers 服务");
|
|
17354
|
+
return false;
|
|
17355
|
+
}
|
|
17356
|
+
const source = {
|
|
17357
|
+
trigger: "/",
|
|
17358
|
+
name: SKILL_INPUT_SOURCE,
|
|
17359
|
+
order: 2,
|
|
17360
|
+
showGroupTitle: false,
|
|
17361
|
+
async candidates(session, request) {
|
|
17362
|
+
try {
|
|
17363
|
+
await waitForCliSessionSelection(session.sessionId);
|
|
17364
|
+
const query = request.query.trim().toLocaleLowerCase();
|
|
17365
|
+
const catalog = await loadSkillCatalog(options.rpc, session.sessionId, request.signal, query === "");
|
|
17366
|
+
onCatalogReady(catalog);
|
|
17367
|
+
return catalog.filter((skill) => skill.enabled).filter((skill) => query === "" || [
|
|
17368
|
+
skill.name,
|
|
17369
|
+
skill.displayName,
|
|
17370
|
+
skill.description
|
|
17371
|
+
].some((value) => value?.toLocaleLowerCase().includes(query) === true)).map((skill) => ({
|
|
17372
|
+
name: skill.name,
|
|
17373
|
+
label: skill.displayName ?? skill.name,
|
|
17374
|
+
value: skill.name,
|
|
17375
|
+
...skill.description.trim() === "" ? {} : { description: skill.description }
|
|
17376
|
+
}));
|
|
17377
|
+
} catch (error) {
|
|
17378
|
+
debugWarn("codingns4dsh: 读取 Skill `/` 搜索目录失败", { error: error instanceof Error ? error.message : String(error) });
|
|
17379
|
+
return [];
|
|
17380
|
+
}
|
|
17381
|
+
},
|
|
17382
|
+
warm(session) {
|
|
17383
|
+
const controller = new AbortController();
|
|
17384
|
+
waitForCliSessionSelection(session.sessionId).then(() => loadSkillCatalog(options.rpc, session.sessionId, controller.signal, true)).then((catalog) => {
|
|
17385
|
+
onCatalogReady(catalog);
|
|
17386
|
+
}).catch((error) => {
|
|
17387
|
+
debugWarn("codingns4dsh: 预热 Skill `/` 搜索目录失败", { error: error instanceof Error ? error.message : String(error) });
|
|
17388
|
+
});
|
|
17389
|
+
},
|
|
17390
|
+
lexicon(session) {
|
|
17391
|
+
return (skillCatalogCache.get(session.sessionId)?.catalog)?.filter((skill) => skill.enabled).map((skill) => skill.name);
|
|
17392
|
+
},
|
|
17393
|
+
onPick(pick) {
|
|
17394
|
+
const name = pick.candidate.value?.trim() || pick.candidate.name.trim();
|
|
17395
|
+
if (name === "") return { text: "" };
|
|
17396
|
+
return { insert: {
|
|
17397
|
+
source: SKILL_INPUT_SOURCE,
|
|
17398
|
+
ref: name,
|
|
17399
|
+
label: pick.candidate.label?.trim() || name,
|
|
17400
|
+
appearance: "session",
|
|
17401
|
+
clipboardText: `/${name}`
|
|
17402
|
+
} };
|
|
17403
|
+
},
|
|
17404
|
+
codec: {
|
|
17405
|
+
clipboardText(ref) {
|
|
17406
|
+
return `/${ref}`;
|
|
17407
|
+
},
|
|
17408
|
+
async serialize(ref) {
|
|
17409
|
+
return `/${ref}`;
|
|
17410
|
+
}
|
|
17411
|
+
}
|
|
17412
|
+
};
|
|
17413
|
+
try {
|
|
17414
|
+
ctx.effect(() => inputTriggers.registerSource(source), "codingns4dsh: skill slash source");
|
|
17415
|
+
debugInfo("codingns4dsh: Skill `/` 搜索源已注册");
|
|
17416
|
+
return true;
|
|
17417
|
+
} catch (error) {
|
|
17418
|
+
debugWarn("codingns4dsh: Skill `/` 搜索源注册失败", { error: error instanceof Error ? error.message : String(error) });
|
|
17419
|
+
return false;
|
|
17420
|
+
}
|
|
17421
|
+
}
|
|
17422
|
+
/** 按会话与 Agent 选择版本缓存目录,避免输入每个字符都请求 Codex。 */
|
|
17423
|
+
async function loadSkillCatalog(rpc, sessionId, signal, forceReload) {
|
|
17424
|
+
const selectionRevision = cliSessionSelectionRevision(sessionId);
|
|
17425
|
+
const cached = skillCatalogCache.get(sessionId);
|
|
17426
|
+
if (!forceReload && cached !== void 0 && cached.selectionRevision === selectionRevision && Date.now() - cached.loadedAt < SKILL_CATALOG_CACHE_TTL_MS) return cached.catalog;
|
|
17427
|
+
const catalog = await callCliRpc(rpc, "skills", {
|
|
17428
|
+
sessionId,
|
|
17429
|
+
forceReload: true
|
|
17430
|
+
}, signal);
|
|
17431
|
+
skillCatalogCache.set(sessionId, {
|
|
17432
|
+
selectionRevision,
|
|
17433
|
+
loadedAt: Date.now(),
|
|
17434
|
+
catalog
|
|
17435
|
+
});
|
|
17436
|
+
publishSkillCatalog(sessionId, catalog);
|
|
17437
|
+
return catalog;
|
|
17438
|
+
}
|
|
17439
|
+
function skillCommandSpan(draft, draftRev, selection) {
|
|
17440
|
+
const caret = selection?.end ?? draft.length;
|
|
17441
|
+
const token = /\/(?:skills|技能)(?=\s|$)/gu;
|
|
17442
|
+
let match;
|
|
17443
|
+
while ((match = token.exec(draft)) !== null) {
|
|
17444
|
+
const start = match.index;
|
|
17445
|
+
const end = start + match[0].length;
|
|
17446
|
+
const before = draft[start - 1];
|
|
17447
|
+
if (start > 0 && before !== void 0 && !/\s/u.test(before)) continue;
|
|
17448
|
+
if (caret >= start && caret <= end + 1) return {
|
|
17449
|
+
start,
|
|
17450
|
+
end,
|
|
17451
|
+
draftRev
|
|
17452
|
+
};
|
|
17453
|
+
if (caret > end && draft.slice(end, caret).trim() === "") return {
|
|
17454
|
+
start,
|
|
17455
|
+
end,
|
|
17456
|
+
draftRev
|
|
17457
|
+
};
|
|
17458
|
+
}
|
|
17459
|
+
}
|
|
17460
|
+
function readSessionInput(ctx, sessionId) {
|
|
17461
|
+
try {
|
|
17462
|
+
const actx = readSessions(ctx)?.scope(sessionId);
|
|
17463
|
+
if (actx === void 0) return void 0;
|
|
17464
|
+
return readConversation(ctx)?.input.for(actx);
|
|
17465
|
+
} catch {
|
|
17466
|
+
return;
|
|
17467
|
+
}
|
|
17468
|
+
}
|
|
17469
|
+
function readCommandUi(ctx) {
|
|
17470
|
+
const service = readService(ctx, "commandUi");
|
|
17471
|
+
return service !== void 0 && typeof service.register === "function" ? service : void 0;
|
|
17472
|
+
}
|
|
17473
|
+
function readSessions(ctx) {
|
|
17474
|
+
const service = readService(ctx, "sessions");
|
|
17475
|
+
return service !== void 0 && typeof service.scope === "function" ? service : void 0;
|
|
17476
|
+
}
|
|
17477
|
+
function readConversation(ctx) {
|
|
17478
|
+
const service = readService(ctx, "conversation");
|
|
17479
|
+
return service !== void 0 && typeof service.input?.for === "function" ? service : void 0;
|
|
17480
|
+
}
|
|
17481
|
+
function readService(ctx, name) {
|
|
17482
|
+
try {
|
|
17483
|
+
return ctx.get(name);
|
|
17484
|
+
} catch {
|
|
17485
|
+
return;
|
|
17486
|
+
}
|
|
17487
|
+
}
|
|
17488
|
+
//#endregion
|
|
16518
17489
|
//#region src/client/features/workspace-session-enhancement.ts
|
|
16519
17490
|
init_session_adapter_cache();
|
|
16520
17491
|
init_locale();
|
|
@@ -16548,6 +17519,8 @@ window.__ModuleLoader__.load({
|
|
|
16548
17519
|
let adapterRefreshTimer;
|
|
16549
17520
|
let disposeSubscription;
|
|
16550
17521
|
let disposeQuickPhrases;
|
|
17522
|
+
let disposeSkillCommand;
|
|
17523
|
+
let skillReferenceDom;
|
|
16551
17524
|
let lastSubscriptionUsageSignature = null;
|
|
16552
17525
|
const disableLogo = () => {
|
|
16553
17526
|
generation += 1;
|
|
@@ -16615,6 +17588,20 @@ window.__ModuleLoader__.load({
|
|
|
16615
17588
|
disposeQuickPhrases?.();
|
|
16616
17589
|
disposeQuickPhrases = void 0;
|
|
16617
17590
|
};
|
|
17591
|
+
const enableSkillQuickReference = () => {
|
|
17592
|
+
if (skillReferenceDom === void 0) skillReferenceDom = startSkillReferenceDom();
|
|
17593
|
+
if (disposeSkillCommand !== void 0 || context.services.uiContext === void 0) return;
|
|
17594
|
+
disposeSkillCommand = registerSkillCommand(context.services.uiContext, {
|
|
17595
|
+
rpc: context.services.rpc,
|
|
17596
|
+
locale: context.services.locale
|
|
17597
|
+
});
|
|
17598
|
+
};
|
|
17599
|
+
const disableSkillQuickReference = () => {
|
|
17600
|
+
disposeSkillCommand?.();
|
|
17601
|
+
disposeSkillCommand = void 0;
|
|
17602
|
+
skillReferenceDom?.dispose();
|
|
17603
|
+
skillReferenceDom = void 0;
|
|
17604
|
+
};
|
|
16618
17605
|
const disposeAll = () => {
|
|
16619
17606
|
disableLogo();
|
|
16620
17607
|
archiveDom?.dispose();
|
|
@@ -16623,6 +17610,7 @@ window.__ModuleLoader__.load({
|
|
|
16623
17610
|
disableRightbarMemory();
|
|
16624
17611
|
disableSubscription();
|
|
16625
17612
|
disableQuickPhrases();
|
|
17613
|
+
disableSkillQuickReference();
|
|
16626
17614
|
};
|
|
16627
17615
|
const enableLogo = () => {
|
|
16628
17616
|
if (logoDom !== void 0) return;
|
|
@@ -16657,6 +17645,8 @@ window.__ModuleLoader__.load({
|
|
|
16657
17645
|
else disableSubscription();
|
|
16658
17646
|
if (workspaceSettings?.showQuickPhrases ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showQuickPhrases) enableQuickPhrases();
|
|
16659
17647
|
else disableQuickPhrases();
|
|
17648
|
+
if (workspaceSettings?.showSkillQuickReference ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showSkillQuickReference) enableSkillQuickReference();
|
|
17649
|
+
else disableSkillQuickReference();
|
|
16660
17650
|
if (workspaceSettings?.rememberConversationRightbarRatio ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.rememberConversationRightbarRatio) enableRightbarMemory();
|
|
16661
17651
|
else disableRightbarMemory();
|
|
16662
17652
|
const subscriptionUsageSignature = JSON.stringify(context.services.settings.getSnapshot().value?.subscriptionUsage ?? null);
|
|
@@ -18509,9 +19499,9 @@ window.__ModuleLoader__.load({
|
|
|
18509
19499
|
}
|
|
18510
19500
|
function createProfileForm(value) {
|
|
18511
19501
|
const field = (label, key, type = "text", placeholder, layout) => (0, react.createElement)("label", { style: {
|
|
18512
|
-
...fieldStyle,
|
|
19502
|
+
...fieldStyle$1,
|
|
18513
19503
|
...layout
|
|
18514
|
-
} }, (0, react.createElement)("span", { style: fieldLabelStyle }, label), (0, react.createElement)("input", {
|
|
19504
|
+
} }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, label), (0, react.createElement)("input", {
|
|
18515
19505
|
type,
|
|
18516
19506
|
value: typeof value[key] === "boolean" ? void 0 : value[key],
|
|
18517
19507
|
placeholder,
|
|
@@ -18526,7 +19516,7 @@ window.__ModuleLoader__.load({
|
|
|
18526
19516
|
}),
|
|
18527
19517
|
style: inputStyle
|
|
18528
19518
|
}));
|
|
18529
|
-
const selectField = (label, key, options) => (0, react.createElement)("label", { style: selectFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle }, label), (0, react.createElement)("select", {
|
|
19519
|
+
const selectField = (label, key, options) => (0, react.createElement)("label", { style: selectFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, label), (0, react.createElement)("select", {
|
|
18530
19520
|
value: value[key],
|
|
18531
19521
|
onChange: (event) => setDraft({
|
|
18532
19522
|
...value,
|
|
@@ -18559,7 +19549,7 @@ window.__ModuleLoader__.load({
|
|
|
18559
19549
|
onClick: () => setDraft(null),
|
|
18560
19550
|
style: closeButtonStyle,
|
|
18561
19551
|
"aria-label": t("debug.closeFormAria")
|
|
18562
|
-
}, "×")), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionLaunch")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionLaunchHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldName"), "name", "text", t("debug.fieldNamePlaceholder")), field(t("debug.fieldCwd"), "cwdRelative", "text", t("debug.fieldCwdPlaceholder")), field(t("debug.fieldCommand"), "commandLine", "text", t("debug.fieldCommandPlaceholder"), { gridColumn: "1 / -1" }))), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionRuntime")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionRuntimeHint"))), (0, react.createElement)("div", { style: formGridStyle }, selectField(t("debug.fieldShell"), "shellProfileId", shellOptions(terminalStatus, t))), (0, react.createElement)("p", { style: runtimeHintStyle }, t("debug.runtimeHint", { runtime: terminalRuntimeLabel(terminalStatus, t) })), terminalStatus?.runtimeWarning === void 0 ? null : (0, react.createElement)("p", { style: warningHintStyle }, terminalStatus.runtimeWarning)), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionPort")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionPortHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldPort"), "port", "number", "1 - 65535"), (0, react.createElement)("label", { style: proxyFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle }, t("debug.fieldProxy")), (0, react.createElement)("span", { style: proxyToggleStyle }, (0, react.createElement)("span", void 0, t("debug.proxyToggleLabel")), (0, react.createElement)("input", {
|
|
19552
|
+
}, "×")), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionLaunch")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionLaunchHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldName"), "name", "text", t("debug.fieldNamePlaceholder")), field(t("debug.fieldCwd"), "cwdRelative", "text", t("debug.fieldCwdPlaceholder")), field(t("debug.fieldCommand"), "commandLine", "text", t("debug.fieldCommandPlaceholder"), { gridColumn: "1 / -1" }))), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionRuntime")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionRuntimeHint"))), (0, react.createElement)("div", { style: formGridStyle }, selectField(t("debug.fieldShell"), "shellProfileId", shellOptions(terminalStatus, t))), (0, react.createElement)("p", { style: runtimeHintStyle }, t("debug.runtimeHint", { runtime: terminalRuntimeLabel(terminalStatus, t) })), terminalStatus?.runtimeWarning === void 0 ? null : (0, react.createElement)("p", { style: warningHintStyle }, terminalStatus.runtimeWarning)), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionPort")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionPortHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldPort"), "port", "number", "1 - 65535"), (0, react.createElement)("label", { style: proxyFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, t("debug.fieldProxy")), (0, react.createElement)("span", { style: proxyToggleStyle }, (0, react.createElement)("span", void 0, t("debug.proxyToggleLabel")), (0, react.createElement)("input", {
|
|
18563
19553
|
type: "checkbox",
|
|
18564
19554
|
"aria-label": t("debug.proxyToggleAria"),
|
|
18565
19555
|
checked: value.proxyEnabled,
|
|
@@ -19141,14 +20131,14 @@ window.__ModuleLoader__.load({
|
|
|
19141
20131
|
fontSize: 20,
|
|
19142
20132
|
lineHeight: 1
|
|
19143
20133
|
};
|
|
19144
|
-
const fieldStyle = {
|
|
20134
|
+
const fieldStyle$1 = {
|
|
19145
20135
|
display: "flex",
|
|
19146
20136
|
flexDirection: "column",
|
|
19147
20137
|
gap: 5,
|
|
19148
20138
|
minWidth: 0
|
|
19149
20139
|
};
|
|
19150
|
-
const selectFieldStyle = { ...fieldStyle };
|
|
19151
|
-
const fieldLabelStyle = {
|
|
20140
|
+
const selectFieldStyle = { ...fieldStyle$1 };
|
|
20141
|
+
const fieldLabelStyle$1 = {
|
|
19152
20142
|
color: dshThemeColor.labelSecondary,
|
|
19153
20143
|
fontSize: 12,
|
|
19154
20144
|
fontWeight: 500
|
|
@@ -19181,7 +20171,7 @@ window.__ModuleLoader__.load({
|
|
|
19181
20171
|
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
|
19182
20172
|
gap: 14
|
|
19183
20173
|
};
|
|
19184
|
-
const proxyFieldStyle = { ...fieldStyle };
|
|
20174
|
+
const proxyFieldStyle = { ...fieldStyle$1 };
|
|
19185
20175
|
const proxyToggleStyle = {
|
|
19186
20176
|
display: "flex",
|
|
19187
20177
|
alignItems: "center",
|
|
@@ -56864,7 +57854,7 @@ window.__ModuleLoader__.load({
|
|
|
56864
57854
|
close();
|
|
56865
57855
|
return;
|
|
56866
57856
|
}
|
|
56867
|
-
setMessage(t("peerHost.handshakeFailed", { status: statusLabel(record.status, t) }));
|
|
57857
|
+
setMessage(t("peerHost.handshakeFailed", { status: statusLabel$1(record.status, t) }));
|
|
56868
57858
|
}).catch((error) => setMessage(error instanceof Error ? error.message : String(error))).finally(() => {
|
|
56869
57859
|
submit.disabled = false;
|
|
56870
57860
|
submit.textContent = t("peerHost.addAndLogin");
|
|
@@ -56973,7 +57963,7 @@ window.__ModuleLoader__.load({
|
|
|
56973
57963
|
lineHeight: "1.3"
|
|
56974
57964
|
});
|
|
56975
57965
|
const status = dom.createElement("span");
|
|
56976
|
-
status.textContent = statusLabel(record.status, t);
|
|
57966
|
+
status.textContent = statusLabel$1(record.status, t);
|
|
56977
57967
|
Object.assign(status.style, {
|
|
56978
57968
|
flex: "0 0 auto",
|
|
56979
57969
|
padding: "2px 6px",
|
|
@@ -57398,7 +58388,7 @@ window.__ModuleLoader__.load({
|
|
|
57398
58388
|
reset.style.borderColor = selected === null ? dshThemeColor.accent : dshThemeColor.border;
|
|
57399
58389
|
}
|
|
57400
58390
|
}
|
|
57401
|
-
function statusLabel(status, t) {
|
|
58391
|
+
function statusLabel$1(status, t) {
|
|
57402
58392
|
const key = {
|
|
57403
58393
|
configured: "peerHost.statusConfigured",
|
|
57404
58394
|
checking: "peerHost.statusChecking",
|
|
@@ -57679,7 +58669,7 @@ window.__ModuleLoader__.load({
|
|
|
57679
58669
|
const store = readNativeWorkspaceListStore(input.uiContext);
|
|
57680
58670
|
if (store === void 0) return () => void 0;
|
|
57681
58671
|
const service = readNativeService(input.uiContext, "workspaces");
|
|
57682
|
-
const serviceRecord = isRecord$
|
|
58672
|
+
const serviceRecord = isRecord$4(service) ? service : void 0;
|
|
57683
58673
|
const sessionStoreValue = readNativeService(input.uiContext, "sessions", "list");
|
|
57684
58674
|
const sessionStore = isSnapshotStore(sessionStoreValue) ? sessionStoreValue : void 0;
|
|
57685
58675
|
const ownsGetSnapshot = Object.hasOwn(store, "getSnapshot");
|
|
@@ -57738,7 +58728,7 @@ window.__ModuleLoader__.load({
|
|
|
57738
58728
|
*/
|
|
57739
58729
|
async function refreshPeerHostNativeSessions(uiContext) {
|
|
57740
58730
|
const sessions = readNativeService(uiContext, "sessions");
|
|
57741
|
-
const refresh = isRecord$
|
|
58731
|
+
const refresh = isRecord$4(sessions) ? sessions.refresh : void 0;
|
|
57742
58732
|
if (typeof refresh !== "function") return;
|
|
57743
58733
|
try {
|
|
57744
58734
|
await refresh.call(sessions);
|
|
@@ -57837,7 +58827,7 @@ window.__ModuleLoader__.load({
|
|
|
57837
58827
|
const byId = state?.byId;
|
|
57838
58828
|
const workspaceState = asRecord$1(workspaceSnapshot);
|
|
57839
58829
|
const items = Array.isArray(workspaceState?.items) ? workspaceState.items : [];
|
|
57840
|
-
if (state === null || !isRecord$
|
|
58830
|
+
if (state === null || !isRecord$4(byId) || items.length === 0) return snapshot;
|
|
57841
58831
|
let changed = false;
|
|
57842
58832
|
const nextById = { ...byId };
|
|
57843
58833
|
for (const [id, value] of Object.entries(byId)) {
|
|
@@ -57983,13 +58973,13 @@ window.__ModuleLoader__.load({
|
|
|
57983
58973
|
return merged.length === local.length ? void 0 : merged;
|
|
57984
58974
|
}
|
|
57985
58975
|
function asRecord$1(value) {
|
|
57986
|
-
return isRecord$
|
|
58976
|
+
return isRecord$4(value) ? value : null;
|
|
57987
58977
|
}
|
|
57988
|
-
function isRecord$
|
|
58978
|
+
function isRecord$4(value) {
|
|
57989
58979
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57990
58980
|
}
|
|
57991
58981
|
function isSnapshotStore(value) {
|
|
57992
|
-
return isRecord$
|
|
58982
|
+
return isRecord$4(value) && typeof value.getSnapshot === "function" && typeof value.subscribe === "function";
|
|
57993
58983
|
}
|
|
57994
58984
|
//#endregion
|
|
57995
58985
|
//#region src/client/peer-host-workspace-tag.ts
|
|
@@ -58099,7 +59089,7 @@ window.__ModuleLoader__.load({
|
|
|
58099
59089
|
const fiberKey = Object.getOwnPropertyNames(row).find((key) => key.startsWith("__reactFiber$"));
|
|
58100
59090
|
if (fiberKey === void 0) return void 0;
|
|
58101
59091
|
let current = row[fiberKey];
|
|
58102
|
-
for (let depth = 0; depth < 24 && isRecord$
|
|
59092
|
+
for (let depth = 0; depth < 24 && isRecord$3(current); depth += 1) {
|
|
58103
59093
|
for (const props of [current.memoizedProps, current.pendingProps]) {
|
|
58104
59094
|
const found = readWorkspaceIdFromProps(props);
|
|
58105
59095
|
if (found !== void 0) return found;
|
|
@@ -58108,7 +59098,7 @@ window.__ModuleLoader__.load({
|
|
|
58108
59098
|
}
|
|
58109
59099
|
}
|
|
58110
59100
|
function readWorkspaceIdFromProps(value) {
|
|
58111
|
-
if (!isRecord$
|
|
59101
|
+
if (!isRecord$3(value)) return void 0;
|
|
58112
59102
|
for (const key of ["workspaceId", "workspaceID"]) {
|
|
58113
59103
|
const candidate = value[key];
|
|
58114
59104
|
if (typeof candidate === "string" && candidate.trim() !== "") return candidate.trim();
|
|
@@ -58122,7 +59112,7 @@ window.__ModuleLoader__.load({
|
|
|
58122
59112
|
"data"
|
|
58123
59113
|
]) {
|
|
58124
59114
|
const nested = value[key];
|
|
58125
|
-
if (!isRecord$
|
|
59115
|
+
if (!isRecord$3(nested)) continue;
|
|
58126
59116
|
const candidate = nested.workspaceId ?? nested.id;
|
|
58127
59117
|
if (typeof candidate === "string" && candidate.trim() !== "") return candidate.trim();
|
|
58128
59118
|
}
|
|
@@ -58168,7 +59158,7 @@ window.__ModuleLoader__.load({
|
|
|
58168
59158
|
});
|
|
58169
59159
|
host.append(element);
|
|
58170
59160
|
}
|
|
58171
|
-
function isRecord$
|
|
59161
|
+
function isRecord$3(value) {
|
|
58172
59162
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58173
59163
|
}
|
|
58174
59164
|
//#endregion
|
|
@@ -59466,6 +60456,1598 @@ window.__ModuleLoader__.load({
|
|
|
59466
60456
|
}, message);
|
|
59467
60457
|
}
|
|
59468
60458
|
//#endregion
|
|
60459
|
+
//#region src/client/global-voice-runtime-registry.ts
|
|
60460
|
+
const adapters = /* @__PURE__ */ new WeakMap();
|
|
60461
|
+
function registerGlobalVoiceAdapter(services, adapter) {
|
|
60462
|
+
adapters.set(services, adapter);
|
|
60463
|
+
return () => {
|
|
60464
|
+
if (adapters.get(services) === adapter) adapters.delete(services);
|
|
60465
|
+
};
|
|
60466
|
+
}
|
|
60467
|
+
function getGlobalVoiceAdapter(services) {
|
|
60468
|
+
return adapters.get(services);
|
|
60469
|
+
}
|
|
60470
|
+
//#endregion
|
|
60471
|
+
//#region src/client/features/voice-initialization-dialog.ts
|
|
60472
|
+
init_transport();
|
|
60473
|
+
init_theme();
|
|
60474
|
+
init_locale();
|
|
60475
|
+
/** 全局语音助理初始化页;用户只选择模型,路径和下载由 Host 自动处理。 */
|
|
60476
|
+
function VoiceInitializationDialog({ services, value, onClose }) {
|
|
60477
|
+
const t = useCodingNsTranslator(services.locale);
|
|
60478
|
+
const defaultModelId = ASSISTANT_VOICE_MODEL_CATALOG[0]?.id ?? "";
|
|
60479
|
+
const configuredModelId = value.modelId?.trim() ?? "";
|
|
60480
|
+
const [modelId, setModelId] = (0, react.useState)(() => configuredModelId !== "" && findAssistantVoiceModel(configuredModelId) !== void 0 ? configuredModelId : defaultModelId);
|
|
60481
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
60482
|
+
const [error, setError] = (0, react.useState)();
|
|
60483
|
+
const settingsSnapshot = services.settings.getSnapshot();
|
|
60484
|
+
const disabled = saving || settingsSnapshot.status === "loading" || !settingsSnapshot.writable;
|
|
60485
|
+
const selectedModel = findAssistantVoiceModel(modelId);
|
|
60486
|
+
(0, react.useEffect)(() => {
|
|
60487
|
+
const closeOnEscape = (event) => {
|
|
60488
|
+
if (event.key === "Escape" && !saving) onClose();
|
|
60489
|
+
};
|
|
60490
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
60491
|
+
return () => document.removeEventListener("keydown", closeOnEscape);
|
|
60492
|
+
}, [onClose, saving]);
|
|
60493
|
+
const save = async () => {
|
|
60494
|
+
if (selectedModel === void 0) {
|
|
60495
|
+
setError(t("voice.setup.modelRequired"));
|
|
60496
|
+
return;
|
|
60497
|
+
}
|
|
60498
|
+
setSaving(true);
|
|
60499
|
+
setError(void 0);
|
|
60500
|
+
try {
|
|
60501
|
+
const result = await services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/setup", { modelId });
|
|
60502
|
+
if (!result.ok) {
|
|
60503
|
+
setError(result.error.message);
|
|
60504
|
+
return;
|
|
60505
|
+
}
|
|
60506
|
+
onClose();
|
|
60507
|
+
} catch (cause) {
|
|
60508
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
60509
|
+
} finally {
|
|
60510
|
+
setSaving(false);
|
|
60511
|
+
}
|
|
60512
|
+
};
|
|
60513
|
+
return (0, react.createElement)("div", {
|
|
60514
|
+
role: "presentation",
|
|
60515
|
+
onPointerDown: () => {
|
|
60516
|
+
if (!saving) onClose();
|
|
60517
|
+
},
|
|
60518
|
+
style: {
|
|
60519
|
+
position: "fixed",
|
|
60520
|
+
inset: 0,
|
|
60521
|
+
zIndex: 1e4,
|
|
60522
|
+
display: "flex",
|
|
60523
|
+
alignItems: "center",
|
|
60524
|
+
justifyContent: "center",
|
|
60525
|
+
padding: 16,
|
|
60526
|
+
background: dshThemeColor.overlay,
|
|
60527
|
+
boxSizing: "border-box"
|
|
60528
|
+
}
|
|
60529
|
+
}, (0, react.createElement)("div", {
|
|
60530
|
+
role: "dialog",
|
|
60531
|
+
"aria-modal": true,
|
|
60532
|
+
"aria-label": t("voice.setup.title"),
|
|
60533
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
60534
|
+
style: {
|
|
60535
|
+
display: "flex",
|
|
60536
|
+
flexDirection: "column",
|
|
60537
|
+
gap: 16,
|
|
60538
|
+
width: "min(560px, 100%)",
|
|
60539
|
+
maxHeight: "min(620px, 100%)",
|
|
60540
|
+
overflowY: "auto",
|
|
60541
|
+
padding: 24,
|
|
60542
|
+
color: dshThemeColor.labelPrimary,
|
|
60543
|
+
background: dshThemeColor.menuBackground,
|
|
60544
|
+
border: `1px solid ${dshThemeColor.border}`,
|
|
60545
|
+
borderRadius: 12,
|
|
60546
|
+
boxShadow: dshThemeColor.prominentShadow,
|
|
60547
|
+
boxSizing: "border-box"
|
|
60548
|
+
}
|
|
60549
|
+
}, (0, react.createElement)("div", { style: {
|
|
60550
|
+
display: "grid",
|
|
60551
|
+
gap: 7
|
|
60552
|
+
} }, (0, react.createElement)("strong", { style: {
|
|
60553
|
+
fontSize: 18,
|
|
60554
|
+
lineHeight: 1.4
|
|
60555
|
+
} }, t("voice.setup.title")), (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.setup.description"))), (0, react.createElement)("div", { style: {
|
|
60556
|
+
padding: "11px 13px",
|
|
60557
|
+
borderRadius: 8,
|
|
60558
|
+
background: dshThemeColor.inputBackground,
|
|
60559
|
+
color: dshThemeColor.labelSecondary,
|
|
60560
|
+
fontSize: 13,
|
|
60561
|
+
lineHeight: 1.55
|
|
60562
|
+
} }, t("voice.setup.onlyRealtime")), (0, react.createElement)("label", { style: fieldLabelStyle }, (0, react.createElement)("span", null, t("voice.setup.model")), (0, react.createElement)("select", {
|
|
60563
|
+
value: modelId,
|
|
60564
|
+
disabled,
|
|
60565
|
+
onChange: (event) => setModelId(event.currentTarget.value),
|
|
60566
|
+
style: fieldStyle
|
|
60567
|
+
}, ...ASSISTANT_VOICE_MODEL_CATALOG.map((model) => (0, react.createElement)("option", {
|
|
60568
|
+
key: model.id,
|
|
60569
|
+
value: model.id
|
|
60570
|
+
}, model.label)))), selectedModel === void 0 ? null : (0, react.createElement)("div", { style: {
|
|
60571
|
+
display: "grid",
|
|
60572
|
+
gap: 5,
|
|
60573
|
+
color: dshThemeColor.labelSecondary,
|
|
60574
|
+
fontSize: 13,
|
|
60575
|
+
lineHeight: 1.5
|
|
60576
|
+
} }, (0, react.createElement)("strong", { style: {
|
|
60577
|
+
color: dshThemeColor.labelPrimary,
|
|
60578
|
+
fontSize: 14
|
|
60579
|
+
} }, selectedModel.label), (0, react.createElement)("span", null, selectedModel.description), (0, react.createElement)("span", null, t("voice.setup.downloadHint"))), saving ? (0, react.createElement)("div", {
|
|
60580
|
+
role: "status",
|
|
60581
|
+
style: {
|
|
60582
|
+
color: dshThemeColor.labelSecondary,
|
|
60583
|
+
fontSize: 13
|
|
60584
|
+
}
|
|
60585
|
+
}, t("voice.setup.downloading")) : null, error === void 0 ? null : (0, react.createElement)("div", {
|
|
60586
|
+
role: "alert",
|
|
60587
|
+
style: {
|
|
60588
|
+
color: dshThemeColor.error,
|
|
60589
|
+
fontSize: 13,
|
|
60590
|
+
lineHeight: 1.5
|
|
60591
|
+
}
|
|
60592
|
+
}, error), (0, react.createElement)("div", { style: {
|
|
60593
|
+
display: "flex",
|
|
60594
|
+
justifyContent: "flex-end",
|
|
60595
|
+
gap: 8,
|
|
60596
|
+
paddingTop: 3
|
|
60597
|
+
} }, (0, react.createElement)("button", {
|
|
60598
|
+
type: "button",
|
|
60599
|
+
disabled: saving,
|
|
60600
|
+
onClick: onClose,
|
|
60601
|
+
style: dshSettingsButtonStyle
|
|
60602
|
+
}, t("voice.setup.cancel")), (0, react.createElement)("button", {
|
|
60603
|
+
type: "button",
|
|
60604
|
+
disabled,
|
|
60605
|
+
onClick: () => void save(),
|
|
60606
|
+
style: dshSettingsPrimaryButtonStyle
|
|
60607
|
+
}, t("voice.setup.save")))));
|
|
60608
|
+
}
|
|
60609
|
+
const fieldLabelStyle = {
|
|
60610
|
+
display: "grid",
|
|
60611
|
+
gap: 7,
|
|
60612
|
+
color: dshThemeColor.labelSecondary,
|
|
60613
|
+
fontSize: 12,
|
|
60614
|
+
lineHeight: 1.4,
|
|
60615
|
+
fontWeight: 600
|
|
60616
|
+
};
|
|
60617
|
+
const fieldStyle = {
|
|
60618
|
+
...dshFieldStyle,
|
|
60619
|
+
width: "100%",
|
|
60620
|
+
minHeight: 40,
|
|
60621
|
+
padding: "9px 11px",
|
|
60622
|
+
borderRadius: 7,
|
|
60623
|
+
boxSizing: "border-box",
|
|
60624
|
+
fontSize: 14
|
|
60625
|
+
};
|
|
60626
|
+
//#endregion
|
|
60627
|
+
//#region src/client/features/assistant-panel.ts
|
|
60628
|
+
init_locale();
|
|
60629
|
+
init_theme();
|
|
60630
|
+
/** 全局智能助理范围设置;默认空范围,工作区必须由用户逐个勾选。 */
|
|
60631
|
+
function AssistantPanel({ services, enabled, snapshot, notify }) {
|
|
60632
|
+
const t = useCodingNsTranslator(services.locale);
|
|
60633
|
+
const [workspaces, setWorkspaces] = (0, react.useState)([]);
|
|
60634
|
+
const [inputDevices, setInputDevices] = (0, react.useState)([]);
|
|
60635
|
+
const [inputDeviceId, setInputDeviceId] = (0, react.useState)("");
|
|
60636
|
+
const [outputDevices, setOutputDevices] = (0, react.useState)([]);
|
|
60637
|
+
const [outputDeviceId, setOutputDeviceId] = (0, react.useState)("");
|
|
60638
|
+
const [outputDeviceSupported, setOutputDeviceSupported] = (0, react.useState)(false);
|
|
60639
|
+
const [voiceSetupOpen, setVoiceSetupOpen] = (0, react.useState)(false);
|
|
60640
|
+
const value = snapshot.value?.assistant ?? DEFAULT_ASSISTANT_SETTINGS;
|
|
60641
|
+
const voice = value.voice ?? DEFAULT_ASSISTANT_VOICE_SETTINGS;
|
|
60642
|
+
const voiceModel = findAssistantVoiceModel(voice.modelId ?? "");
|
|
60643
|
+
const disabled = !enabled || snapshot.status === "loading" || !snapshot.writable;
|
|
60644
|
+
(0, react.useEffect)(() => {
|
|
60645
|
+
const store = readNativeWorkspaceListStore(services.uiContext);
|
|
60646
|
+
const refresh = () => setWorkspaces(readNativeWorkspaceSnapshot(services.uiContext)?.items ?? []);
|
|
60647
|
+
refresh();
|
|
60648
|
+
return store?.subscribe(refresh);
|
|
60649
|
+
}, [services.uiContext]);
|
|
60650
|
+
(0, react.useEffect)(() => {
|
|
60651
|
+
const adapter = getGlobalVoiceAdapter(services);
|
|
60652
|
+
if (adapter === void 0) return void 0;
|
|
60653
|
+
let disposed = false;
|
|
60654
|
+
const refresh = () => {
|
|
60655
|
+
adapter.enumerateInputDevices().then((devices) => {
|
|
60656
|
+
if (disposed) return;
|
|
60657
|
+
setInputDevices(devices.map(({ deviceId, label }) => ({
|
|
60658
|
+
deviceId,
|
|
60659
|
+
label
|
|
60660
|
+
})));
|
|
60661
|
+
setInputDeviceId(adapter.inputDeviceId ?? "");
|
|
60662
|
+
}).catch(() => {
|
|
60663
|
+
if (!disposed) setInputDevices([]);
|
|
60664
|
+
});
|
|
60665
|
+
setOutputDeviceSupported(adapter.outputDeviceSupported);
|
|
60666
|
+
adapter.enumerateOutputDevices().then((devices) => {
|
|
60667
|
+
if (disposed) return;
|
|
60668
|
+
setOutputDevices(devices.map(({ deviceId, label }) => ({
|
|
60669
|
+
deviceId,
|
|
60670
|
+
label
|
|
60671
|
+
})));
|
|
60672
|
+
setOutputDeviceId(adapter.outputDeviceId ?? "");
|
|
60673
|
+
}).catch(() => {
|
|
60674
|
+
if (!disposed) setOutputDevices([]);
|
|
60675
|
+
});
|
|
60676
|
+
};
|
|
60677
|
+
refresh();
|
|
60678
|
+
const mediaDevices = globalThis.navigator?.mediaDevices;
|
|
60679
|
+
const onChange = () => refresh();
|
|
60680
|
+
mediaDevices?.addEventListener?.("devicechange", onChange);
|
|
60681
|
+
return () => {
|
|
60682
|
+
disposed = true;
|
|
60683
|
+
mediaDevices?.removeEventListener?.("devicechange", onChange);
|
|
60684
|
+
};
|
|
60685
|
+
}, [services]);
|
|
60686
|
+
const chooseInputDevice = (deviceId) => {
|
|
60687
|
+
const adapter = getGlobalVoiceAdapter(services);
|
|
60688
|
+
if (adapter === void 0) return;
|
|
60689
|
+
adapter.selectInputDevice(deviceId).then(() => {
|
|
60690
|
+
setInputDeviceId(deviceId);
|
|
60691
|
+
notify({
|
|
60692
|
+
kind: "success",
|
|
60693
|
+
message: t("voice.microphoneUpdated")
|
|
60694
|
+
});
|
|
60695
|
+
}).catch((error) => notify({
|
|
60696
|
+
kind: "error",
|
|
60697
|
+
message: error instanceof Error ? error.message : String(error)
|
|
60698
|
+
}));
|
|
60699
|
+
};
|
|
60700
|
+
const chooseOutputDevice = (deviceId) => {
|
|
60701
|
+
const adapter = getGlobalVoiceAdapter(services);
|
|
60702
|
+
if (adapter === void 0) return;
|
|
60703
|
+
adapter.selectOutputDevice(deviceId).then(() => {
|
|
60704
|
+
setOutputDeviceId(deviceId);
|
|
60705
|
+
notify({
|
|
60706
|
+
kind: "success",
|
|
60707
|
+
message: t("voice.outputUpdated")
|
|
60708
|
+
});
|
|
60709
|
+
}).catch((error) => notify({
|
|
60710
|
+
kind: "error",
|
|
60711
|
+
message: error instanceof Error ? error.message : String(error)
|
|
60712
|
+
}));
|
|
60713
|
+
};
|
|
60714
|
+
const update = (workspaceId, checked) => {
|
|
60715
|
+
const current = new Set(value.managedWorkspaceIds);
|
|
60716
|
+
if (checked) current.add(workspaceId);
|
|
60717
|
+
else current.delete(workspaceId);
|
|
60718
|
+
services.settings.mutate([{
|
|
60719
|
+
op: "set",
|
|
60720
|
+
path: [CODINGNS_ASSISTANT_FIELD, "managedWorkspaceIds"],
|
|
60721
|
+
value: [...current]
|
|
60722
|
+
}]).then((accepted) => {
|
|
60723
|
+
if (!accepted) notify({
|
|
60724
|
+
kind: "error",
|
|
60725
|
+
message: t("settings.moduleWriteRejected")
|
|
60726
|
+
});
|
|
60727
|
+
else notify({
|
|
60728
|
+
kind: "success",
|
|
60729
|
+
message: t("assistant.saved")
|
|
60730
|
+
});
|
|
60731
|
+
}).catch((error) => notify({
|
|
60732
|
+
kind: "error",
|
|
60733
|
+
message: error instanceof Error ? error.message : String(error)
|
|
60734
|
+
}));
|
|
60735
|
+
};
|
|
60736
|
+
return (0, react.createElement)("div", {
|
|
60737
|
+
"aria-disabled": disabled,
|
|
60738
|
+
style: {
|
|
60739
|
+
...dshFormRootStyle,
|
|
60740
|
+
display: "flex",
|
|
60741
|
+
flexDirection: "column",
|
|
60742
|
+
gap: 12,
|
|
60743
|
+
opacity: disabled ? .5 : 1,
|
|
60744
|
+
pointerEvents: disabled ? "none" : "auto"
|
|
60745
|
+
}
|
|
60746
|
+
}, (0, react.createElement)("p", { style: dshSettingsHelpStyle }, t("assistant.scopeDescription")), (0, react.createElement)("div", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: {
|
|
60747
|
+
minWidth: 0,
|
|
60748
|
+
flex: "1 1 auto"
|
|
60749
|
+
} }, (0, react.createElement)("strong", { style: {
|
|
60750
|
+
display: "block",
|
|
60751
|
+
fontSize: 13,
|
|
60752
|
+
lineHeight: 1.4
|
|
60753
|
+
} }, t("voice.setup.title")), (0, react.createElement)("span", { style: {
|
|
60754
|
+
display: "block",
|
|
60755
|
+
marginTop: 3,
|
|
60756
|
+
...dshSettingsHelpStyle
|
|
60757
|
+
} }, voice.initialized && voice.provider === "sherpa-onnx" ? voiceModel?.label ?? t("voice.setup.status.sherpa") : t("voice.setup.status.uninitialized"))), (0, react.createElement)("button", {
|
|
60758
|
+
type: "button",
|
|
60759
|
+
disabled,
|
|
60760
|
+
onClick: () => setVoiceSetupOpen(true),
|
|
60761
|
+
style: {
|
|
60762
|
+
...dshSettingsButtonStyle,
|
|
60763
|
+
flex: "0 0 auto",
|
|
60764
|
+
whiteSpace: "nowrap"
|
|
60765
|
+
}
|
|
60766
|
+
}, t("voice.setup.reconfigure"))), inputDevices.length === 0 ? null : (0, react.createElement)("label", { style: {
|
|
60767
|
+
...dshSettingsListRowStyle,
|
|
60768
|
+
alignItems: "center"
|
|
60769
|
+
} }, (0, react.createElement)("span", { style: { minWidth: 0 } }, t("voice.inputDevice")), (0, react.createElement)("select", {
|
|
60770
|
+
value: inputDeviceId,
|
|
60771
|
+
disabled,
|
|
60772
|
+
onChange: (event) => chooseInputDevice(event.currentTarget.value),
|
|
60773
|
+
style: {
|
|
60774
|
+
maxWidth: 220,
|
|
60775
|
+
minWidth: 0,
|
|
60776
|
+
flex: "0 1 220px"
|
|
60777
|
+
}
|
|
60778
|
+
}, (0, react.createElement)("option", { value: "" }, t("voice.defaultMicrophone")), ...inputDevices.map((device) => (0, react.createElement)("option", {
|
|
60779
|
+
key: device.deviceId,
|
|
60780
|
+
value: device.deviceId
|
|
60781
|
+
}, device.label || t("voice.unnamedMicrophone"))))), !outputDeviceSupported ? null : (0, react.createElement)("label", { style: {
|
|
60782
|
+
...dshSettingsListRowStyle,
|
|
60783
|
+
alignItems: "center"
|
|
60784
|
+
} }, (0, react.createElement)("span", { style: { minWidth: 0 } }, t("voice.outputDevice")), outputDevices.length === 0 ? (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.defaultOutput")) : (0, react.createElement)("select", {
|
|
60785
|
+
value: outputDeviceId,
|
|
60786
|
+
disabled,
|
|
60787
|
+
onChange: (event) => chooseOutputDevice(event.currentTarget.value),
|
|
60788
|
+
style: {
|
|
60789
|
+
maxWidth: 220,
|
|
60790
|
+
minWidth: 0,
|
|
60791
|
+
flex: "0 1 220px"
|
|
60792
|
+
}
|
|
60793
|
+
}, (0, react.createElement)("option", { value: "" }, t("voice.defaultOutput")), ...outputDevices.map((device) => (0, react.createElement)("option", {
|
|
60794
|
+
key: device.deviceId,
|
|
60795
|
+
value: device.deviceId
|
|
60796
|
+
}, device.label || t("voice.unnamedOutput"))))), workspaces.length === 0 ? (0, react.createElement)("div", {
|
|
60797
|
+
role: "status",
|
|
60798
|
+
style: dshSettingsHelpStyle
|
|
60799
|
+
}, t("assistant.noWorkspaces")) : workspaces.map((workspace) => (0, react.createElement)("label", {
|
|
60800
|
+
key: workspace.workspaceId,
|
|
60801
|
+
style: dshSettingsListRowStyle
|
|
60802
|
+
}, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
|
|
60803
|
+
display: "block",
|
|
60804
|
+
fontSize: 13,
|
|
60805
|
+
lineHeight: 1.4
|
|
60806
|
+
} }, workspace.title), workspace.path === void 0 ? null : (0, react.createElement)("span", { style: {
|
|
60807
|
+
display: "block",
|
|
60808
|
+
marginTop: 3,
|
|
60809
|
+
...dshSettingsHelpStyle
|
|
60810
|
+
} }, workspace.path)), (0, react.createElement)("input", {
|
|
60811
|
+
type: "checkbox",
|
|
60812
|
+
role: "switch",
|
|
60813
|
+
"aria-label": workspace.title,
|
|
60814
|
+
checked: value.managedWorkspaceIds.includes(workspace.workspaceId),
|
|
60815
|
+
disabled,
|
|
60816
|
+
onChange: (event) => update(workspace.workspaceId, event.currentTarget.checked),
|
|
60817
|
+
style: {
|
|
60818
|
+
flex: "0 0 auto",
|
|
60819
|
+
accentColor: dshThemeColor.accent
|
|
60820
|
+
}
|
|
60821
|
+
}))), voiceSetupOpen ? (0, react.createElement)(VoiceInitializationDialog, {
|
|
60822
|
+
services,
|
|
60823
|
+
value: voice,
|
|
60824
|
+
onClose: () => setVoiceSetupOpen(false)
|
|
60825
|
+
}) : null);
|
|
60826
|
+
}
|
|
60827
|
+
//#endregion
|
|
60828
|
+
//#region src/client/voice-security.ts
|
|
60829
|
+
function inspectBrowserVoiceSecurity() {
|
|
60830
|
+
const environment = globalThis;
|
|
60831
|
+
const windowLike = environment.window;
|
|
60832
|
+
const location = windowLike?.location ?? environment.location;
|
|
60833
|
+
const protocol = typeof location?.protocol === "string" ? location.protocol.toLowerCase() : "";
|
|
60834
|
+
const hostname = typeof location?.hostname === "string" ? location.hostname.toLowerCase() : "";
|
|
60835
|
+
const secureValue = typeof windowLike?.isSecureContext === "boolean" ? windowLike.isSecureContext : typeof environment.isSecureContext === "boolean" ? environment.isSecureContext : void 0;
|
|
60836
|
+
if (!(windowLike !== void 0 || environment.document !== void 0 || secureValue !== void 0 || protocol !== "")) return {
|
|
60837
|
+
secure: true,
|
|
60838
|
+
protocol
|
|
60839
|
+
};
|
|
60840
|
+
if (secureValue === false) return {
|
|
60841
|
+
secure: false,
|
|
60842
|
+
protocol,
|
|
60843
|
+
reason: "isSecureContext"
|
|
60844
|
+
};
|
|
60845
|
+
if (protocol === "http:" && !isLoopbackHostname(hostname)) return {
|
|
60846
|
+
secure: false,
|
|
60847
|
+
protocol,
|
|
60848
|
+
reason: "insecureProtocol"
|
|
60849
|
+
};
|
|
60850
|
+
if (secureValue === true || protocol === "https:" || protocol === "wss:" || protocol === "file:" || protocol === "chrome-extension:") return {
|
|
60851
|
+
secure: true,
|
|
60852
|
+
protocol
|
|
60853
|
+
};
|
|
60854
|
+
if (protocol === "http:" && isLoopbackHostname(hostname)) return {
|
|
60855
|
+
secure: true,
|
|
60856
|
+
protocol
|
|
60857
|
+
};
|
|
60858
|
+
return {
|
|
60859
|
+
secure: false,
|
|
60860
|
+
protocol,
|
|
60861
|
+
reason: "unknown"
|
|
60862
|
+
};
|
|
60863
|
+
}
|
|
60864
|
+
function isLoopbackHostname(hostname) {
|
|
60865
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
60866
|
+
}
|
|
60867
|
+
//#endregion
|
|
60868
|
+
//#region src/client/voice-device-manager.ts
|
|
60869
|
+
const INPUT_KEY = "codingns4dsh.voice.input-device";
|
|
60870
|
+
const OUTPUT_KEY = "codingns4dsh.voice.output-device";
|
|
60871
|
+
/**
|
|
60872
|
+
* 浏览器 Client 的设备管理器。
|
|
60873
|
+
*
|
|
60874
|
+
* 设备权限、设备 ID 和设备变化都属于当前浏览器来源;Host 只会收到选择后的
|
|
60875
|
+
* PCM 数据,不会接触这里的真实硬件清单。
|
|
60876
|
+
*/
|
|
60877
|
+
var BrowserVoiceDeviceManager = class {
|
|
60878
|
+
listeners = /* @__PURE__ */ new Set();
|
|
60879
|
+
current;
|
|
60880
|
+
deviceChangeDispose;
|
|
60881
|
+
disposed = false;
|
|
60882
|
+
constructor() {
|
|
60883
|
+
const security = inspectBrowserVoiceSecurity();
|
|
60884
|
+
this.current = {
|
|
60885
|
+
secureContext: security.secure,
|
|
60886
|
+
permission: "unknown",
|
|
60887
|
+
inputs: [],
|
|
60888
|
+
outputs: [],
|
|
60889
|
+
selectedInputId: readStorage(INPUT_KEY),
|
|
60890
|
+
selectedOutputId: readStorage(OUTPUT_KEY)
|
|
60891
|
+
};
|
|
60892
|
+
const mediaDevices = globalThis.navigator?.mediaDevices;
|
|
60893
|
+
if (mediaDevices !== void 0 && typeof mediaDevices.addEventListener === "function") {
|
|
60894
|
+
const onDeviceChange = () => {
|
|
60895
|
+
this.refresh();
|
|
60896
|
+
};
|
|
60897
|
+
mediaDevices.addEventListener("devicechange", onDeviceChange);
|
|
60898
|
+
this.deviceChangeDispose = () => mediaDevices.removeEventListener("devicechange", onDeviceChange);
|
|
60899
|
+
} else this.deviceChangeDispose = void 0;
|
|
60900
|
+
}
|
|
60901
|
+
snapshot() {
|
|
60902
|
+
return cloneSnapshot(this.current);
|
|
60903
|
+
}
|
|
60904
|
+
subscribe(listener) {
|
|
60905
|
+
this.listeners.add(listener);
|
|
60906
|
+
listener(this.snapshot());
|
|
60907
|
+
return () => this.listeners.delete(listener);
|
|
60908
|
+
}
|
|
60909
|
+
async refresh() {
|
|
60910
|
+
if (this.disposed) return this.snapshot();
|
|
60911
|
+
const security = inspectBrowserVoiceSecurity();
|
|
60912
|
+
const mediaDevices = globalThis.navigator?.mediaDevices;
|
|
60913
|
+
if (!security.secure || mediaDevices === void 0 || typeof mediaDevices.enumerateDevices !== "function") {
|
|
60914
|
+
this.update({
|
|
60915
|
+
secureContext: security.secure,
|
|
60916
|
+
inputs: [],
|
|
60917
|
+
outputs: []
|
|
60918
|
+
});
|
|
60919
|
+
return this.snapshot();
|
|
60920
|
+
}
|
|
60921
|
+
const devices = await mediaDevices.enumerateDevices();
|
|
60922
|
+
const inputs = devices.filter((device) => device.kind === "audioinput").map(toDevice("audioinput"));
|
|
60923
|
+
const outputs = devices.filter((device) => device.kind === "audiooutput").map(toDevice("audiooutput"));
|
|
60924
|
+
const selectedInputId = this.current.selectedInputId !== null && inputs.some((device) => device.deviceId === this.current.selectedInputId) ? this.current.selectedInputId : null;
|
|
60925
|
+
const selectedOutputId = this.current.selectedOutputId !== null && outputs.some((device) => device.deviceId === this.current.selectedOutputId) ? this.current.selectedOutputId : null;
|
|
60926
|
+
if (selectedInputId === null) removeStorage(INPUT_KEY);
|
|
60927
|
+
if (selectedOutputId === null) removeStorage(OUTPUT_KEY);
|
|
60928
|
+
this.update({
|
|
60929
|
+
inputs,
|
|
60930
|
+
outputs,
|
|
60931
|
+
selectedInputId,
|
|
60932
|
+
selectedOutputId
|
|
60933
|
+
});
|
|
60934
|
+
await this.refreshPermission();
|
|
60935
|
+
return this.snapshot();
|
|
60936
|
+
}
|
|
60937
|
+
async requestInputPermission() {
|
|
60938
|
+
if (this.disposed) throw new Error("语音设备管理器已释放");
|
|
60939
|
+
if (!inspectBrowserVoiceSecurity().secure) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
|
|
60940
|
+
const mediaDevices = globalThis.navigator?.mediaDevices;
|
|
60941
|
+
if (mediaDevices === void 0 || typeof mediaDevices.getUserMedia !== "function") throw new Error("当前浏览器不支持 getUserMedia");
|
|
60942
|
+
const selected = this.current.selectedInputId;
|
|
60943
|
+
let stream;
|
|
60944
|
+
try {
|
|
60945
|
+
stream = await mediaDevices.getUserMedia({
|
|
60946
|
+
audio: buildInputConstraints(selected),
|
|
60947
|
+
video: false
|
|
60948
|
+
});
|
|
60949
|
+
} catch (error) {
|
|
60950
|
+
if (selected === null || !isMissingInputDeviceError(error)) throw error;
|
|
60951
|
+
removeStorage(INPUT_KEY);
|
|
60952
|
+
this.update({ selectedInputId: null });
|
|
60953
|
+
stream = await mediaDevices.getUserMedia({
|
|
60954
|
+
audio: buildInputConstraints(null),
|
|
60955
|
+
video: false
|
|
60956
|
+
});
|
|
60957
|
+
}
|
|
60958
|
+
for (const track of stream.getTracks()) track.stop();
|
|
60959
|
+
await this.refresh();
|
|
60960
|
+
this.update({ permission: "granted" });
|
|
60961
|
+
return this.snapshot();
|
|
60962
|
+
}
|
|
60963
|
+
async selectInput(deviceId) {
|
|
60964
|
+
const value = deviceId.trim();
|
|
60965
|
+
if (value !== "" && !this.current.inputs.some((device) => device.deviceId === value)) {
|
|
60966
|
+
await this.refresh();
|
|
60967
|
+
if (!this.current.inputs.some((device) => device.deviceId === value)) throw new Error("选择的麦克风设备不存在");
|
|
60968
|
+
}
|
|
60969
|
+
if (value === "") removeStorage(INPUT_KEY);
|
|
60970
|
+
else writeStorage(INPUT_KEY, value);
|
|
60971
|
+
this.update({ selectedInputId: value === "" ? null : value });
|
|
60972
|
+
return this.snapshot();
|
|
60973
|
+
}
|
|
60974
|
+
async selectOutput(deviceId) {
|
|
60975
|
+
const value = deviceId.trim();
|
|
60976
|
+
if (value !== "" && !this.current.outputs.some((device) => device.deviceId === value)) {
|
|
60977
|
+
await this.refresh();
|
|
60978
|
+
if (!this.current.outputs.some((device) => device.deviceId === value)) throw new Error("选择的播放设备不存在");
|
|
60979
|
+
}
|
|
60980
|
+
if (value === "") removeStorage(OUTPUT_KEY);
|
|
60981
|
+
else writeStorage(OUTPUT_KEY, value);
|
|
60982
|
+
this.update({ selectedOutputId: value === "" ? null : value });
|
|
60983
|
+
return this.snapshot();
|
|
60984
|
+
}
|
|
60985
|
+
dispose() {
|
|
60986
|
+
if (this.disposed) return;
|
|
60987
|
+
this.disposed = true;
|
|
60988
|
+
this.deviceChangeDispose?.();
|
|
60989
|
+
this.listeners.clear();
|
|
60990
|
+
}
|
|
60991
|
+
async refreshPermission() {
|
|
60992
|
+
const permissions = globalThis.navigator?.permissions;
|
|
60993
|
+
if (permissions === void 0 || typeof permissions.query !== "function") return;
|
|
60994
|
+
try {
|
|
60995
|
+
const status = await permissions.query({ name: "microphone" });
|
|
60996
|
+
this.update({ permission: status.state === "granted" || status.state === "denied" ? status.state : "prompt" });
|
|
60997
|
+
} catch {}
|
|
60998
|
+
}
|
|
60999
|
+
update(patch) {
|
|
61000
|
+
this.current = {
|
|
61001
|
+
...this.current,
|
|
61002
|
+
...patch
|
|
61003
|
+
};
|
|
61004
|
+
const snapshot = this.snapshot();
|
|
61005
|
+
for (const listener of [...this.listeners]) try {
|
|
61006
|
+
listener(snapshot);
|
|
61007
|
+
} catch {}
|
|
61008
|
+
}
|
|
61009
|
+
};
|
|
61010
|
+
function buildInputConstraints(selectedInputId) {
|
|
61011
|
+
return selectedInputId === null ? {
|
|
61012
|
+
echoCancellation: true,
|
|
61013
|
+
noiseSuppression: true,
|
|
61014
|
+
autoGainControl: true
|
|
61015
|
+
} : {
|
|
61016
|
+
deviceId: { exact: selectedInputId },
|
|
61017
|
+
echoCancellation: true,
|
|
61018
|
+
noiseSuppression: true,
|
|
61019
|
+
autoGainControl: true
|
|
61020
|
+
};
|
|
61021
|
+
}
|
|
61022
|
+
function isMissingInputDeviceError(error) {
|
|
61023
|
+
if (typeof error !== "object" || error === null) return false;
|
|
61024
|
+
const name = "name" in error && typeof error.name === "string" ? error.name : "";
|
|
61025
|
+
return name === "NotFoundError" || name === "OverconstrainedError" || name === "DevicesNotFoundError";
|
|
61026
|
+
}
|
|
61027
|
+
function toDevice(kind) {
|
|
61028
|
+
return (device) => ({
|
|
61029
|
+
kind,
|
|
61030
|
+
deviceId: device.deviceId,
|
|
61031
|
+
label: device.label,
|
|
61032
|
+
groupId: device.groupId
|
|
61033
|
+
});
|
|
61034
|
+
}
|
|
61035
|
+
function cloneSnapshot(value) {
|
|
61036
|
+
return {
|
|
61037
|
+
...value,
|
|
61038
|
+
inputs: [...value.inputs],
|
|
61039
|
+
outputs: [...value.outputs]
|
|
61040
|
+
};
|
|
61041
|
+
}
|
|
61042
|
+
function readStorage(key) {
|
|
61043
|
+
try {
|
|
61044
|
+
return (globalThis.localStorage?.getItem(key))?.trim() || null;
|
|
61045
|
+
} catch {
|
|
61046
|
+
return null;
|
|
61047
|
+
}
|
|
61048
|
+
}
|
|
61049
|
+
function writeStorage(key, value) {
|
|
61050
|
+
try {
|
|
61051
|
+
globalThis.localStorage?.setItem(key, value);
|
|
61052
|
+
} catch {}
|
|
61053
|
+
}
|
|
61054
|
+
function removeStorage(key) {
|
|
61055
|
+
try {
|
|
61056
|
+
globalThis.localStorage?.removeItem(key);
|
|
61057
|
+
} catch {}
|
|
61058
|
+
}
|
|
61059
|
+
//#endregion
|
|
61060
|
+
//#region src/client/voice-capture.ts
|
|
61061
|
+
/** 浏览器端实时 PCM 采集器;设备权限始终由 Client 自己持有。 */
|
|
61062
|
+
var ClientVoiceCapture = class {
|
|
61063
|
+
options;
|
|
61064
|
+
stream;
|
|
61065
|
+
context;
|
|
61066
|
+
source;
|
|
61067
|
+
processor;
|
|
61068
|
+
output;
|
|
61069
|
+
workletUrl;
|
|
61070
|
+
sequence = 0;
|
|
61071
|
+
started = false;
|
|
61072
|
+
inputRate = 16e3;
|
|
61073
|
+
constructor(options) {
|
|
61074
|
+
this.options = options;
|
|
61075
|
+
}
|
|
61076
|
+
get isStarted() {
|
|
61077
|
+
return this.started;
|
|
61078
|
+
}
|
|
61079
|
+
async start() {
|
|
61080
|
+
if (this.started) return;
|
|
61081
|
+
if (!inspectBrowserVoiceSecurity().secure) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
|
|
61082
|
+
const mediaDevices = globalThis.navigator?.mediaDevices;
|
|
61083
|
+
if (mediaDevices === void 0 || typeof mediaDevices.getUserMedia !== "function") throw new Error("当前浏览器不支持 getUserMedia");
|
|
61084
|
+
const selected = (await this.options.devices.requestInputPermission()).selectedInputId;
|
|
61085
|
+
const constraints = {
|
|
61086
|
+
audio: selected === null ? {
|
|
61087
|
+
channelCount: 1,
|
|
61088
|
+
echoCancellation: true,
|
|
61089
|
+
noiseSuppression: true,
|
|
61090
|
+
autoGainControl: true
|
|
61091
|
+
} : {
|
|
61092
|
+
deviceId: { exact: selected },
|
|
61093
|
+
channelCount: 1,
|
|
61094
|
+
echoCancellation: true,
|
|
61095
|
+
noiseSuppression: true,
|
|
61096
|
+
autoGainControl: true
|
|
61097
|
+
},
|
|
61098
|
+
video: false
|
|
61099
|
+
};
|
|
61100
|
+
const stream = await mediaDevices.getUserMedia(constraints);
|
|
61101
|
+
this.stream = stream;
|
|
61102
|
+
try {
|
|
61103
|
+
const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
|
|
61104
|
+
if (Context === void 0) throw new Error("当前浏览器不支持 AudioContext");
|
|
61105
|
+
const context = new Context({ sampleRate: this.options.targetSampleRate ?? 16e3 });
|
|
61106
|
+
this.context = context;
|
|
61107
|
+
this.inputRate = context.sampleRate;
|
|
61108
|
+
await context.resume();
|
|
61109
|
+
this.source = context.createMediaStreamSource(stream);
|
|
61110
|
+
this.output = context.createGain();
|
|
61111
|
+
this.output.gain.value = 0;
|
|
61112
|
+
this.output.connect(context.destination);
|
|
61113
|
+
this.sequence = 0;
|
|
61114
|
+
if (context.audioWorklet !== void 0 && typeof AudioWorkletNode === "function") await this.startWorklet(context);
|
|
61115
|
+
else this.startScriptProcessor(context);
|
|
61116
|
+
this.started = true;
|
|
61117
|
+
for (const track of stream.getAudioTracks()) track.addEventListener("ended", this.handleEnded, { once: true });
|
|
61118
|
+
} catch (error) {
|
|
61119
|
+
await this.stop();
|
|
61120
|
+
throw error;
|
|
61121
|
+
}
|
|
61122
|
+
}
|
|
61123
|
+
async stop() {
|
|
61124
|
+
const stream = this.stream;
|
|
61125
|
+
const processor = this.processor;
|
|
61126
|
+
this.started = false;
|
|
61127
|
+
this.stream = void 0;
|
|
61128
|
+
this.processor = void 0;
|
|
61129
|
+
this.source?.disconnect();
|
|
61130
|
+
this.source = void 0;
|
|
61131
|
+
processor?.disconnect();
|
|
61132
|
+
this.output?.disconnect();
|
|
61133
|
+
this.output = void 0;
|
|
61134
|
+
if (this.context !== void 0 && this.context.state !== "closed") await this.context.close();
|
|
61135
|
+
this.context = void 0;
|
|
61136
|
+
if (this.workletUrl !== void 0) URL.revokeObjectURL(this.workletUrl);
|
|
61137
|
+
this.workletUrl = void 0;
|
|
61138
|
+
for (const track of stream?.getTracks() ?? []) {
|
|
61139
|
+
track.removeEventListener("ended", this.handleEnded);
|
|
61140
|
+
try {
|
|
61141
|
+
track.stop();
|
|
61142
|
+
} catch {}
|
|
61143
|
+
}
|
|
61144
|
+
}
|
|
61145
|
+
async startWorklet(context) {
|
|
61146
|
+
const source = this.source;
|
|
61147
|
+
const output = this.output;
|
|
61148
|
+
if (source === void 0 || output === void 0 || context.audioWorklet === void 0) throw new Error("音频采集图未初始化");
|
|
61149
|
+
const blob = new Blob([`class CodingNsVoiceProcessor extends AudioWorkletProcessor { process(inputs) { const input = inputs[0] && inputs[0][0]; if (input) this.port.postMessage(new Float32Array(input)); return true; } } registerProcessor('codingns-voice-capture', CodingNsVoiceProcessor);`], { type: "application/javascript" });
|
|
61150
|
+
this.workletUrl = URL.createObjectURL(blob);
|
|
61151
|
+
await context.audioWorklet.addModule(this.workletUrl);
|
|
61152
|
+
const node = new AudioWorkletNode(context, "codingns-voice-capture", {
|
|
61153
|
+
numberOfInputs: 1,
|
|
61154
|
+
numberOfOutputs: 1,
|
|
61155
|
+
outputChannelCount: [1]
|
|
61156
|
+
});
|
|
61157
|
+
node.port.onmessage = (event) => {
|
|
61158
|
+
if (!this.started && this.context !== context) return;
|
|
61159
|
+
const samples = event.data instanceof Float32Array ? event.data : new Float32Array(event.data);
|
|
61160
|
+
this.emitSamples(samples);
|
|
61161
|
+
};
|
|
61162
|
+
source.connect(node);
|
|
61163
|
+
node.connect(output);
|
|
61164
|
+
this.processor = node;
|
|
61165
|
+
}
|
|
61166
|
+
startScriptProcessor(context) {
|
|
61167
|
+
const source = this.source;
|
|
61168
|
+
const output = this.output;
|
|
61169
|
+
if (source === void 0 || output === void 0) throw new Error("音频采集图未初始化");
|
|
61170
|
+
const node = context.createScriptProcessor(1024, 1, 1);
|
|
61171
|
+
node.onaudioprocess = (event) => this.emitSamples(event.inputBuffer.getChannelData(0));
|
|
61172
|
+
source.connect(node);
|
|
61173
|
+
node.connect(output);
|
|
61174
|
+
this.processor = node;
|
|
61175
|
+
}
|
|
61176
|
+
handleEnded = () => {
|
|
61177
|
+
if (!this.started) return;
|
|
61178
|
+
this.options.onEnded?.(/* @__PURE__ */ new Error("麦克风轨道已结束"));
|
|
61179
|
+
this.stop();
|
|
61180
|
+
};
|
|
61181
|
+
emitSamples(samples) {
|
|
61182
|
+
if (!this.started) return;
|
|
61183
|
+
const bytes = resampleFloat32ToPcm16(samples, this.inputRate, this.options.targetSampleRate ?? 16e3);
|
|
61184
|
+
if (bytes.byteLength === 0) return;
|
|
61185
|
+
const frame = {
|
|
61186
|
+
sequence: ++this.sequence,
|
|
61187
|
+
bytes,
|
|
61188
|
+
sampleRate: this.options.targetSampleRate ?? 16e3,
|
|
61189
|
+
channels: 1
|
|
61190
|
+
};
|
|
61191
|
+
Promise.resolve(this.options.onFrame(frame)).catch((error) => this.options.onEnded?.(error instanceof Error ? error : new Error(String(error))));
|
|
61192
|
+
}
|
|
61193
|
+
};
|
|
61194
|
+
/** 无依赖的线性重采样,适合语音 16 kHz 采集;整段音频不会进入内存。 */
|
|
61195
|
+
function resampleFloat32ToPcm16(samples, inputRate, outputRate) {
|
|
61196
|
+
if (samples.length === 0) return /* @__PURE__ */ new Uint8Array();
|
|
61197
|
+
if (!Number.isFinite(inputRate) || inputRate <= 0 || !Number.isFinite(outputRate) || outputRate <= 0) throw new RangeError("采样率必须为正数");
|
|
61198
|
+
const outputLength = Math.max(1, Math.round(samples.length * outputRate / inputRate));
|
|
61199
|
+
const bytes = new Uint8Array(outputLength * 2);
|
|
61200
|
+
const view = new DataView(bytes.buffer);
|
|
61201
|
+
for (let index = 0; index < outputLength; index += 1) {
|
|
61202
|
+
const sourcePosition = index * inputRate / outputRate;
|
|
61203
|
+
const left = Math.min(samples.length - 1, Math.floor(sourcePosition));
|
|
61204
|
+
const right = Math.min(samples.length - 1, left + 1);
|
|
61205
|
+
const fraction = sourcePosition - left;
|
|
61206
|
+
const sample = Math.max(-1, Math.min(1, (samples[left] ?? 0) * (1 - fraction) + (samples[right] ?? 0) * fraction));
|
|
61207
|
+
view.setInt16(index * 2, Math.round(sample * (sample < 0 ? 32768 : 32767)), true);
|
|
61208
|
+
}
|
|
61209
|
+
return bytes;
|
|
61210
|
+
}
|
|
61211
|
+
//#endregion
|
|
61212
|
+
//#region src/client/sherpa-voice-adapter.ts
|
|
61213
|
+
init_transport();
|
|
61214
|
+
/** 浏览器侧 Sherpa 数据面:设备和权限在 Client,识别在 Host。 */
|
|
61215
|
+
var ClientSherpaVoiceAdapter = class {
|
|
61216
|
+
listeners = /* @__PURE__ */ new Set();
|
|
61217
|
+
services;
|
|
61218
|
+
devices;
|
|
61219
|
+
options;
|
|
61220
|
+
capture;
|
|
61221
|
+
uploader;
|
|
61222
|
+
responseAbort;
|
|
61223
|
+
active = false;
|
|
61224
|
+
hostLeaseActive = false;
|
|
61225
|
+
epoch = 0;
|
|
61226
|
+
configuredOwner;
|
|
61227
|
+
selectedInputDeviceId;
|
|
61228
|
+
selectedOutputDeviceId;
|
|
61229
|
+
constructor(options) {
|
|
61230
|
+
this.options = options;
|
|
61231
|
+
this.services = options.services;
|
|
61232
|
+
this.configuredOwner = options.ownerId;
|
|
61233
|
+
this.devices = new BrowserVoiceDeviceManager();
|
|
61234
|
+
const snapshot = this.devices.snapshot();
|
|
61235
|
+
this.selectedInputDeviceId = snapshot.selectedInputId ?? void 0;
|
|
61236
|
+
this.selectedOutputDeviceId = snapshot.selectedOutputId ?? void 0;
|
|
61237
|
+
this.devices.subscribe((next) => {
|
|
61238
|
+
this.selectedInputDeviceId = next.selectedInputId ?? void 0;
|
|
61239
|
+
this.selectedOutputDeviceId = next.selectedOutputId ?? void 0;
|
|
61240
|
+
});
|
|
61241
|
+
}
|
|
61242
|
+
get capabilities() {
|
|
61243
|
+
const supported = this.devices.snapshot().secureContext && typeof globalThis.fetch === "function" && typeof globalThis.ReadableStream === "function";
|
|
61244
|
+
return {
|
|
61245
|
+
realtime: supported,
|
|
61246
|
+
wakeWord: false,
|
|
61247
|
+
streamingInput: supported,
|
|
61248
|
+
streamingOutput: false,
|
|
61249
|
+
bargeIn: supported,
|
|
61250
|
+
speechToText: supported,
|
|
61251
|
+
textToSpeech: typeof globalThis.speechSynthesis !== "undefined"
|
|
61252
|
+
};
|
|
61253
|
+
}
|
|
61254
|
+
get ownerId() {
|
|
61255
|
+
return this.active ? this.configuredOwner : void 0;
|
|
61256
|
+
}
|
|
61257
|
+
get configuredOwnerId() {
|
|
61258
|
+
return this.configuredOwner;
|
|
61259
|
+
}
|
|
61260
|
+
get inputDeviceId() {
|
|
61261
|
+
return this.selectedInputDeviceId;
|
|
61262
|
+
}
|
|
61263
|
+
get outputDeviceId() {
|
|
61264
|
+
return this.selectedOutputDeviceId;
|
|
61265
|
+
}
|
|
61266
|
+
get outputDeviceSupported() {
|
|
61267
|
+
return typeof (globalThis.HTMLMediaElement?.prototype)?.setSinkId === "function";
|
|
61268
|
+
}
|
|
61269
|
+
subscribe(listener) {
|
|
61270
|
+
this.listeners.add(listener);
|
|
61271
|
+
return () => this.listeners.delete(listener);
|
|
61272
|
+
}
|
|
61273
|
+
async enumerateInputDevices() {
|
|
61274
|
+
return (await this.devices.refresh()).inputs;
|
|
61275
|
+
}
|
|
61276
|
+
async enumerateOutputDevices() {
|
|
61277
|
+
return (await this.devices.refresh()).outputs;
|
|
61278
|
+
}
|
|
61279
|
+
async selectInputDevice(deviceId) {
|
|
61280
|
+
await this.devices.selectInput(deviceId);
|
|
61281
|
+
this.selectedInputDeviceId = this.devices.snapshot().selectedInputId ?? void 0;
|
|
61282
|
+
if (this.active) {
|
|
61283
|
+
await this.stop();
|
|
61284
|
+
await this.start(this.configuredOwner);
|
|
61285
|
+
}
|
|
61286
|
+
}
|
|
61287
|
+
async selectOutputDevice(deviceId) {
|
|
61288
|
+
if (!this.outputDeviceSupported) throw new Error("当前浏览器不支持输出设备选择");
|
|
61289
|
+
await this.devices.selectOutput(deviceId);
|
|
61290
|
+
this.selectedOutputDeviceId = this.devices.snapshot().selectedOutputId ?? void 0;
|
|
61291
|
+
await this.applyOutputDevice();
|
|
61292
|
+
}
|
|
61293
|
+
async start(ownerId) {
|
|
61294
|
+
if (this.active) return;
|
|
61295
|
+
this.configuredOwner = ownerId?.trim() || this.configuredOwner;
|
|
61296
|
+
if (this.configuredOwner === "") throw new Error("全局语音租约缺少 ownerId");
|
|
61297
|
+
const lease = await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/start", { ownerId: this.configuredOwner });
|
|
61298
|
+
if (!lease.ok) throw new Error(lease.error.message);
|
|
61299
|
+
if (isRecord$2(lease.value) && lease.value.unavailable === true) throw new Error(typeof lease.value.message === "string" ? lease.value.message : "本地 Sherpa 语音运行时不可用");
|
|
61300
|
+
this.hostLeaseActive = true;
|
|
61301
|
+
if (!(await this.devices.refresh()).secureContext) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
|
|
61302
|
+
this.epoch += 1;
|
|
61303
|
+
const epoch = this.epoch;
|
|
61304
|
+
const uploader = new VoiceStreamUploader();
|
|
61305
|
+
const abort = new AbortController();
|
|
61306
|
+
this.uploader = uploader;
|
|
61307
|
+
this.responseAbort = abort;
|
|
61308
|
+
this.active = true;
|
|
61309
|
+
this.emit({
|
|
61310
|
+
type: "state",
|
|
61311
|
+
state: "loading",
|
|
61312
|
+
epoch
|
|
61313
|
+
});
|
|
61314
|
+
try {
|
|
61315
|
+
uploader.enqueue(encodeVoiceStreamOpen({
|
|
61316
|
+
ownerId: this.configuredOwner,
|
|
61317
|
+
clientId: createClientId(),
|
|
61318
|
+
sampleRate: 16e3,
|
|
61319
|
+
channels: 1,
|
|
61320
|
+
encoding: "pcm16le"
|
|
61321
|
+
}));
|
|
61322
|
+
const response = await globalThis.fetch(resolveVoiceStreamUrl(), {
|
|
61323
|
+
method: "POST",
|
|
61324
|
+
body: uploader.stream,
|
|
61325
|
+
signal: abort.signal,
|
|
61326
|
+
credentials: "same-origin",
|
|
61327
|
+
headers: {
|
|
61328
|
+
"content-type": "application/octet-stream",
|
|
61329
|
+
accept: "application/x-ndjson"
|
|
61330
|
+
},
|
|
61331
|
+
duplex: "half"
|
|
61332
|
+
});
|
|
61333
|
+
if (!response.ok || response.body === null) throw new Error(`语音流连接失败(HTTP ${response.status})`);
|
|
61334
|
+
this.readEvents(response.body, epoch);
|
|
61335
|
+
const capture = new ClientVoiceCapture({
|
|
61336
|
+
devices: this.devices,
|
|
61337
|
+
targetSampleRate: 16e3,
|
|
61338
|
+
onFrame: (frame) => this.sendPcmFrame(frame, epoch),
|
|
61339
|
+
onEnded: (error) => this.emit({
|
|
61340
|
+
type: "error",
|
|
61341
|
+
code: "voice_capture_ended",
|
|
61342
|
+
message: error.message,
|
|
61343
|
+
recoverable: true,
|
|
61344
|
+
epoch
|
|
61345
|
+
})
|
|
61346
|
+
});
|
|
61347
|
+
this.capture = capture;
|
|
61348
|
+
await capture.start();
|
|
61349
|
+
this.emit({
|
|
61350
|
+
type: "state",
|
|
61351
|
+
state: "listening",
|
|
61352
|
+
epoch
|
|
61353
|
+
});
|
|
61354
|
+
} catch (error) {
|
|
61355
|
+
await this.stop().catch(() => void 0);
|
|
61356
|
+
throw error;
|
|
61357
|
+
}
|
|
61358
|
+
}
|
|
61359
|
+
async stop() {
|
|
61360
|
+
if (!this.active && this.capture === void 0 && !this.hostLeaseActive) return;
|
|
61361
|
+
this.active = false;
|
|
61362
|
+
let captureError;
|
|
61363
|
+
try {
|
|
61364
|
+
await this.capture?.stop();
|
|
61365
|
+
} catch (error) {
|
|
61366
|
+
captureError = error;
|
|
61367
|
+
}
|
|
61368
|
+
this.capture = void 0;
|
|
61369
|
+
this.uploader?.enqueue(encodeVoiceStreamMessage({
|
|
61370
|
+
type: "close",
|
|
61371
|
+
epoch: this.epoch
|
|
61372
|
+
}));
|
|
61373
|
+
this.uploader?.close();
|
|
61374
|
+
this.uploader = void 0;
|
|
61375
|
+
this.responseAbort?.abort();
|
|
61376
|
+
this.responseAbort = void 0;
|
|
61377
|
+
if (this.hostLeaseActive) {
|
|
61378
|
+
this.hostLeaseActive = false;
|
|
61379
|
+
await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/stop", { ownerId: this.configuredOwner }).catch(() => void 0);
|
|
61380
|
+
}
|
|
61381
|
+
this.epoch += 1;
|
|
61382
|
+
this.emit({
|
|
61383
|
+
type: "state",
|
|
61384
|
+
state: "disabled",
|
|
61385
|
+
epoch: this.epoch
|
|
61386
|
+
});
|
|
61387
|
+
if (captureError !== void 0) throw captureError;
|
|
61388
|
+
}
|
|
61389
|
+
async interrupt() {
|
|
61390
|
+
this.epoch += 1;
|
|
61391
|
+
globalThis.speechSynthesis?.cancel();
|
|
61392
|
+
this.uploader?.enqueue(encodeVoiceStreamMessage({
|
|
61393
|
+
type: "interrupt",
|
|
61394
|
+
epoch: this.epoch
|
|
61395
|
+
}));
|
|
61396
|
+
await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/interrupt", { ownerId: this.configuredOwner }).catch(() => void 0);
|
|
61397
|
+
this.emit({
|
|
61398
|
+
type: "barge-in",
|
|
61399
|
+
epoch: this.epoch
|
|
61400
|
+
});
|
|
61401
|
+
}
|
|
61402
|
+
sendPcm(frame, epoch) {
|
|
61403
|
+
this.sendPcmFrame(frame, epoch);
|
|
61404
|
+
}
|
|
61405
|
+
async speak(text, epoch) {
|
|
61406
|
+
if (epoch !== this.epoch || text.trim() === "") return;
|
|
61407
|
+
const synthesis = globalThis.speechSynthesis;
|
|
61408
|
+
if (synthesis === void 0 || typeof globalThis.SpeechSynthesisUtterance !== "function") throw new Error("当前浏览器不支持语音播报");
|
|
61409
|
+
synthesis.cancel();
|
|
61410
|
+
await new Promise((resolve) => {
|
|
61411
|
+
const utterance = new SpeechSynthesisUtterance(text.trim());
|
|
61412
|
+
utterance.lang = "zh-CN";
|
|
61413
|
+
utterance.onend = () => resolve();
|
|
61414
|
+
utterance.onerror = () => resolve();
|
|
61415
|
+
synthesis.speak(utterance);
|
|
61416
|
+
});
|
|
61417
|
+
}
|
|
61418
|
+
dispose() {
|
|
61419
|
+
this.stop();
|
|
61420
|
+
this.devices.dispose();
|
|
61421
|
+
this.listeners.clear();
|
|
61422
|
+
}
|
|
61423
|
+
sendPcmFrame(frame, epoch) {
|
|
61424
|
+
if (!this.active || this.uploader === void 0 || epoch !== this.epoch) return;
|
|
61425
|
+
this.uploader.enqueue(encodeVoiceStreamMessage({
|
|
61426
|
+
type: "pcm",
|
|
61427
|
+
sequence: frame.sequence,
|
|
61428
|
+
sampleRate: frame.sampleRate,
|
|
61429
|
+
channels: 1,
|
|
61430
|
+
epoch,
|
|
61431
|
+
byteLength: frame.bytes.byteLength
|
|
61432
|
+
}));
|
|
61433
|
+
this.uploader.enqueue(frame.bytes);
|
|
61434
|
+
}
|
|
61435
|
+
async readEvents(body, streamEpoch) {
|
|
61436
|
+
const reader = body.getReader();
|
|
61437
|
+
const decoder = new TextDecoder();
|
|
61438
|
+
let text = "";
|
|
61439
|
+
try {
|
|
61440
|
+
while (true) {
|
|
61441
|
+
const next = await reader.read();
|
|
61442
|
+
if (next.done) break;
|
|
61443
|
+
text += decoder.decode(next.value, { stream: true });
|
|
61444
|
+
let newline = text.indexOf("\n");
|
|
61445
|
+
while (newline >= 0) {
|
|
61446
|
+
const line = text.slice(0, newline).trim();
|
|
61447
|
+
text = text.slice(newline + 1);
|
|
61448
|
+
if (line !== "") await this.handleEvent(JSON.parse(line), streamEpoch);
|
|
61449
|
+
newline = text.indexOf("\n");
|
|
61450
|
+
}
|
|
61451
|
+
}
|
|
61452
|
+
} catch (error) {
|
|
61453
|
+
if (this.active) this.emit({
|
|
61454
|
+
type: "error",
|
|
61455
|
+
code: "voice_stream_response_failed",
|
|
61456
|
+
message: error instanceof Error ? error.message : String(error),
|
|
61457
|
+
recoverable: true,
|
|
61458
|
+
epoch: streamEpoch
|
|
61459
|
+
});
|
|
61460
|
+
}
|
|
61461
|
+
}
|
|
61462
|
+
async handleEvent(value, streamEpoch) {
|
|
61463
|
+
if (streamEpoch !== this.epoch || typeof value.type !== "string") return;
|
|
61464
|
+
if (value.type === "state" && typeof value.state === "string") this.emit({
|
|
61465
|
+
type: "state",
|
|
61466
|
+
state: value.state,
|
|
61467
|
+
epoch: readEpoch(value, streamEpoch)
|
|
61468
|
+
});
|
|
61469
|
+
else if (value.type === "partial" && typeof value.text === "string") this.emit({
|
|
61470
|
+
type: "partial",
|
|
61471
|
+
text: value.text,
|
|
61472
|
+
epoch: readEpoch(value, streamEpoch)
|
|
61473
|
+
});
|
|
61474
|
+
else if (value.type === "final" && typeof value.text === "string") {
|
|
61475
|
+
const eventEpoch = readEpoch(value, streamEpoch);
|
|
61476
|
+
this.emit({
|
|
61477
|
+
type: "final",
|
|
61478
|
+
text: value.text,
|
|
61479
|
+
epoch: eventEpoch
|
|
61480
|
+
});
|
|
61481
|
+
const result = await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/text", {
|
|
61482
|
+
ownerId: this.configuredOwner,
|
|
61483
|
+
text: value.text,
|
|
61484
|
+
requestId: createClientId()
|
|
61485
|
+
});
|
|
61486
|
+
if (result.ok && isRecord$2(result.value) && typeof result.value.speechText === "string") await this.speak(result.value.speechText, eventEpoch);
|
|
61487
|
+
} else if (value.type === "barge-in") this.emit({
|
|
61488
|
+
type: "barge-in",
|
|
61489
|
+
epoch: readEpoch(value, streamEpoch)
|
|
61490
|
+
});
|
|
61491
|
+
else if (value.type === "error") this.emit({
|
|
61492
|
+
type: "error",
|
|
61493
|
+
code: String(value.code ?? "voice_stream_error"),
|
|
61494
|
+
message: String(value.message ?? value.code ?? ""),
|
|
61495
|
+
recoverable: value.recoverable !== false,
|
|
61496
|
+
epoch: readEpoch(value, streamEpoch)
|
|
61497
|
+
});
|
|
61498
|
+
}
|
|
61499
|
+
async applyOutputDevice() {
|
|
61500
|
+
if (!this.outputDeviceSupported) return;
|
|
61501
|
+
for (const media of [...document.querySelectorAll("audio,video")]) {
|
|
61502
|
+
const sink = media.setSinkId;
|
|
61503
|
+
if (typeof sink === "function") await sink.call(media, this.selectedOutputDeviceId ?? "");
|
|
61504
|
+
}
|
|
61505
|
+
}
|
|
61506
|
+
emit(event) {
|
|
61507
|
+
for (const listener of [...this.listeners]) try {
|
|
61508
|
+
listener(event);
|
|
61509
|
+
} catch {}
|
|
61510
|
+
}
|
|
61511
|
+
};
|
|
61512
|
+
var VoiceStreamUploader = class {
|
|
61513
|
+
stream;
|
|
61514
|
+
controller;
|
|
61515
|
+
closed = false;
|
|
61516
|
+
constructor() {
|
|
61517
|
+
this.stream = new ReadableStream({ start: (controller) => {
|
|
61518
|
+
this.controller = controller;
|
|
61519
|
+
} });
|
|
61520
|
+
}
|
|
61521
|
+
enqueue(bytes) {
|
|
61522
|
+
if (!this.closed) try {
|
|
61523
|
+
this.controller?.enqueue(bytes.slice());
|
|
61524
|
+
} catch {
|
|
61525
|
+
this.closed = true;
|
|
61526
|
+
}
|
|
61527
|
+
}
|
|
61528
|
+
close() {
|
|
61529
|
+
if (this.closed) return;
|
|
61530
|
+
this.closed = true;
|
|
61531
|
+
try {
|
|
61532
|
+
this.controller?.close();
|
|
61533
|
+
} catch {}
|
|
61534
|
+
}
|
|
61535
|
+
};
|
|
61536
|
+
function resolveVoiceStreamUrl() {
|
|
61537
|
+
return new URL("/api/codingns/assistant/voice/stream", globalThis.location?.origin ?? "http://localhost").toString();
|
|
61538
|
+
}
|
|
61539
|
+
function createClientId() {
|
|
61540
|
+
try {
|
|
61541
|
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
61542
|
+
} catch {
|
|
61543
|
+
return `${Date.now()}-${Math.random()}`;
|
|
61544
|
+
}
|
|
61545
|
+
}
|
|
61546
|
+
function readEpoch(value, fallback) {
|
|
61547
|
+
return typeof value.epoch === "number" && Number.isInteger(value.epoch) ? value.epoch : fallback;
|
|
61548
|
+
}
|
|
61549
|
+
function isRecord$2(value) {
|
|
61550
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61551
|
+
}
|
|
61552
|
+
//#endregion
|
|
61553
|
+
//#region src/client/features/voice-conversation-dialog.ts
|
|
61554
|
+
init_theme();
|
|
61555
|
+
/** 全局语音助理独立对话窗口;打开窗口不会自动申请麦克风。 */
|
|
61556
|
+
function VoiceConversationDialog({ t, active, pending, state, message, partialText, transcript, realtimeAvailable, unavailableMessage, onStart, onStop, onClose, onClear }) {
|
|
61557
|
+
const status = statusLabel(t, state, active);
|
|
61558
|
+
const canStart = !active && !pending;
|
|
61559
|
+
const transcriptRef = (0, react.useRef)(null);
|
|
61560
|
+
(0, react.useEffect)(() => {
|
|
61561
|
+
const element = transcriptRef.current;
|
|
61562
|
+
if (element !== null) element.scrollTop = element.scrollHeight;
|
|
61563
|
+
}, [partialText, transcript.length]);
|
|
61564
|
+
return (0, react.createElement)("div", {
|
|
61565
|
+
role: "presentation",
|
|
61566
|
+
onPointerDown: () => {
|
|
61567
|
+
if (!pending) onClose();
|
|
61568
|
+
},
|
|
61569
|
+
style: {
|
|
61570
|
+
position: "fixed",
|
|
61571
|
+
inset: 0,
|
|
61572
|
+
zIndex: 1e4,
|
|
61573
|
+
display: "flex",
|
|
61574
|
+
alignItems: "center",
|
|
61575
|
+
justifyContent: "center",
|
|
61576
|
+
padding: 16,
|
|
61577
|
+
background: dshThemeColor.overlay,
|
|
61578
|
+
boxSizing: "border-box"
|
|
61579
|
+
}
|
|
61580
|
+
}, (0, react.createElement)("div", {
|
|
61581
|
+
role: "dialog",
|
|
61582
|
+
"aria-modal": true,
|
|
61583
|
+
"aria-label": t("voice.dialog.title"),
|
|
61584
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
61585
|
+
style: {
|
|
61586
|
+
display: "flex",
|
|
61587
|
+
flexDirection: "column",
|
|
61588
|
+
gap: 14,
|
|
61589
|
+
width: "min(620px, 100%)",
|
|
61590
|
+
maxHeight: "min(700px, 100%)",
|
|
61591
|
+
overflowY: "auto",
|
|
61592
|
+
padding: 24,
|
|
61593
|
+
color: dshThemeColor.labelPrimary,
|
|
61594
|
+
background: dshThemeColor.menuBackground,
|
|
61595
|
+
border: `1px solid ${dshThemeColor.border}`,
|
|
61596
|
+
borderRadius: 12,
|
|
61597
|
+
boxShadow: dshThemeColor.prominentShadow,
|
|
61598
|
+
boxSizing: "border-box"
|
|
61599
|
+
}
|
|
61600
|
+
}, (0, react.createElement)("div", { style: {
|
|
61601
|
+
display: "flex",
|
|
61602
|
+
alignItems: "flex-start",
|
|
61603
|
+
justifyContent: "space-between",
|
|
61604
|
+
gap: 16
|
|
61605
|
+
} }, (0, react.createElement)("div", { style: {
|
|
61606
|
+
display: "grid",
|
|
61607
|
+
gap: 6,
|
|
61608
|
+
minWidth: 0
|
|
61609
|
+
} }, (0, react.createElement)("strong", { style: {
|
|
61610
|
+
fontSize: 18,
|
|
61611
|
+
lineHeight: 1.4
|
|
61612
|
+
} }, t("voice.dialog.title")), (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.dialog.description"))), (0, react.createElement)("span", {
|
|
61613
|
+
role: "status",
|
|
61614
|
+
style: {
|
|
61615
|
+
flex: "0 0 auto",
|
|
61616
|
+
color: active ? dshThemeColor.success : dshThemeColor.labelSecondary,
|
|
61617
|
+
fontSize: 12
|
|
61618
|
+
}
|
|
61619
|
+
}, status)), (0, react.createElement)("div", { style: {
|
|
61620
|
+
padding: "10px 12px",
|
|
61621
|
+
borderRadius: 8,
|
|
61622
|
+
background: dshThemeColor.surfaceSubtle,
|
|
61623
|
+
color: dshThemeColor.labelSecondary,
|
|
61624
|
+
fontSize: 12,
|
|
61625
|
+
lineHeight: 1.5
|
|
61626
|
+
} }, realtimeAvailable ? t("voice.dialog.realtimeHint") : unavailableMessage ?? t("voice.dialog.unavailable")), (0, react.createElement)("div", {
|
|
61627
|
+
"aria-live": "polite",
|
|
61628
|
+
ref: transcriptRef,
|
|
61629
|
+
style: {
|
|
61630
|
+
display: "flex",
|
|
61631
|
+
flexDirection: "column",
|
|
61632
|
+
gap: 8,
|
|
61633
|
+
minHeight: 180,
|
|
61634
|
+
maxHeight: 340,
|
|
61635
|
+
overflowY: "auto",
|
|
61636
|
+
padding: 14,
|
|
61637
|
+
border: `1px solid ${dshThemeColor.border}`,
|
|
61638
|
+
borderRadius: 8,
|
|
61639
|
+
background: dshThemeColor.inputBackground,
|
|
61640
|
+
boxSizing: "border-box"
|
|
61641
|
+
}
|
|
61642
|
+
}, (0, react.createElement)("div", { style: {
|
|
61643
|
+
color: dshThemeColor.labelTertiary,
|
|
61644
|
+
fontSize: 12,
|
|
61645
|
+
fontWeight: 600
|
|
61646
|
+
} }, t("voice.dialog.liveText")), transcript.length === 0 && partialText === "" ? (0, react.createElement)("div", { style: {
|
|
61647
|
+
color: dshThemeColor.labelTertiary,
|
|
61648
|
+
fontSize: 14,
|
|
61649
|
+
lineHeight: 1.6
|
|
61650
|
+
} }, t("voice.dialog.empty")) : null, ...transcript.map((text, index) => (0, react.createElement)("div", {
|
|
61651
|
+
key: `${index}-${text}`,
|
|
61652
|
+
style: {
|
|
61653
|
+
color: dshThemeColor.labelPrimary,
|
|
61654
|
+
fontSize: 15,
|
|
61655
|
+
lineHeight: 1.65
|
|
61656
|
+
}
|
|
61657
|
+
}, text)), partialText === "" ? null : (0, react.createElement)("div", { style: {
|
|
61658
|
+
color: dshThemeColor.accent,
|
|
61659
|
+
fontSize: 15,
|
|
61660
|
+
lineHeight: 1.65
|
|
61661
|
+
} }, partialText)), message === void 0 || message === "" || state !== "error" ? null : (0, react.createElement)("div", {
|
|
61662
|
+
role: "alert",
|
|
61663
|
+
style: {
|
|
61664
|
+
color: dshThemeColor.error,
|
|
61665
|
+
fontSize: 13,
|
|
61666
|
+
lineHeight: 1.5
|
|
61667
|
+
}
|
|
61668
|
+
}, message), (0, react.createElement)("div", { style: {
|
|
61669
|
+
display: "flex",
|
|
61670
|
+
justifyContent: "space-between",
|
|
61671
|
+
gap: 8,
|
|
61672
|
+
paddingTop: 3
|
|
61673
|
+
} }, (0, react.createElement)("button", {
|
|
61674
|
+
type: "button",
|
|
61675
|
+
disabled: transcript.length === 0 && partialText === "",
|
|
61676
|
+
onClick: onClear,
|
|
61677
|
+
style: dshSettingsButtonStyle
|
|
61678
|
+
}, t("voice.dialog.clear")), (0, react.createElement)("div", { style: {
|
|
61679
|
+
display: "flex",
|
|
61680
|
+
justifyContent: "flex-end",
|
|
61681
|
+
gap: 8
|
|
61682
|
+
} }, (0, react.createElement)("button", {
|
|
61683
|
+
type: "button",
|
|
61684
|
+
disabled: pending,
|
|
61685
|
+
onClick: onClose,
|
|
61686
|
+
style: dshSettingsButtonStyle
|
|
61687
|
+
}, t("voice.dialog.close")), active ? (0, react.createElement)("button", {
|
|
61688
|
+
type: "button",
|
|
61689
|
+
onClick: onStop,
|
|
61690
|
+
style: dshSettingsButtonStyle
|
|
61691
|
+
}, t("voice.dialog.stop")) : (0, react.createElement)("button", {
|
|
61692
|
+
type: "button",
|
|
61693
|
+
disabled: !canStart || !realtimeAvailable,
|
|
61694
|
+
onClick: onStart,
|
|
61695
|
+
style: dshSettingsPrimaryButtonStyle
|
|
61696
|
+
}, t("voice.dialog.start"))))));
|
|
61697
|
+
}
|
|
61698
|
+
function statusLabel(t, state, active) {
|
|
61699
|
+
if (state === "loading") return t("voice.dialog.status.loading");
|
|
61700
|
+
if (state === "listening") return t("voice.dialog.status.listening");
|
|
61701
|
+
if (state === "recording") return t("voice.dialog.status.recording");
|
|
61702
|
+
if (state === "error") return t("voice.dialog.status.error");
|
|
61703
|
+
return active ? t("voice.dialog.status.active") : t("voice.dialog.status.idle");
|
|
61704
|
+
}
|
|
61705
|
+
//#endregion
|
|
61706
|
+
//#region src/client/features/global-voice-assistant.ts
|
|
61707
|
+
init_transport();
|
|
61708
|
+
init_locale();
|
|
61709
|
+
const GLOBAL_ASSISTANT_BUTTON_SIZE = 38;
|
|
61710
|
+
/** Client 根级语音入口;使用 shell.overlay,绝不绑定当前 session。 */
|
|
61711
|
+
const globalVoiceAssistantFeature = {
|
|
61712
|
+
descriptor: {
|
|
61713
|
+
name: "globalVoiceAssistant",
|
|
61714
|
+
version: "0.1.0",
|
|
61715
|
+
enabledByDefault: false,
|
|
61716
|
+
dependencies: [],
|
|
61717
|
+
runtime: "client",
|
|
61718
|
+
ui: {
|
|
61719
|
+
label: "Global smart assistant (Testing)",
|
|
61720
|
+
description: "Choose the workspaces that the global assistant may read and control.",
|
|
61721
|
+
labelKey: "feature.assistant.label",
|
|
61722
|
+
descriptionKey: "feature.assistant.description",
|
|
61723
|
+
order: 30,
|
|
61724
|
+
defaultOpen: false
|
|
61725
|
+
}
|
|
61726
|
+
},
|
|
61727
|
+
start(context) {
|
|
61728
|
+
const ui = context.services.uiContext;
|
|
61729
|
+
if (ui === void 0) return;
|
|
61730
|
+
const actionOwnerId = createVoiceLeaseOwnerId();
|
|
61731
|
+
const adapter = new ClientSherpaVoiceAdapter({
|
|
61732
|
+
ownerId: actionOwnerId,
|
|
61733
|
+
services: context.services
|
|
61734
|
+
});
|
|
61735
|
+
context.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/register-client", {
|
|
61736
|
+
ownerId: actionOwnerId,
|
|
61737
|
+
capabilities: adapter.capabilities,
|
|
61738
|
+
secureContext: inspectBrowserVoiceSecurity().secure
|
|
61739
|
+
}).catch(() => void 0);
|
|
61740
|
+
const disposeAdapter = registerGlobalVoiceAdapter(context.services, adapter);
|
|
61741
|
+
context.resources.add(() => {
|
|
61742
|
+
disposeAdapter();
|
|
61743
|
+
adapter.dispose();
|
|
61744
|
+
context.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/unregister-client", { ownerId: actionOwnerId }).catch(() => void 0);
|
|
61745
|
+
});
|
|
61746
|
+
const disposer = ui.slots.inject("shell.overlay", () => ui.slots.register({
|
|
61747
|
+
name: "shell.overlay",
|
|
61748
|
+
id: "codingns4dsh-global-voice-assistant",
|
|
61749
|
+
order: 40,
|
|
61750
|
+
inject: () => ({ services: context.services })
|
|
61751
|
+
}, GlobalVoiceOverlay));
|
|
61752
|
+
context.resources.add(disposer);
|
|
61753
|
+
},
|
|
61754
|
+
settingsPanel: AssistantPanel
|
|
61755
|
+
};
|
|
61756
|
+
function GlobalVoiceOverlay({ services }) {
|
|
61757
|
+
const [snapshot, setSnapshot] = (0, react.useState)({ state: "disabled" });
|
|
61758
|
+
const [setupOpen, setSetupOpen] = (0, react.useState)(false);
|
|
61759
|
+
const [conversationOpen, setConversationOpen] = (0, react.useState)(false);
|
|
61760
|
+
const [conversationPending, setConversationPending] = (0, react.useState)(false);
|
|
61761
|
+
const [partialText, setPartialText] = (0, react.useState)("");
|
|
61762
|
+
const [transcript, setTranscript] = (0, react.useState)([]);
|
|
61763
|
+
const [settingsValue, setSettingsValue] = (0, react.useState)(() => services.settings.getSnapshot().value);
|
|
61764
|
+
const ownerIdRef = (0, react.useRef)(void 0);
|
|
61765
|
+
const eventSequenceRef = (0, react.useRef)(0);
|
|
61766
|
+
if (ownerIdRef.current === void 0) ownerIdRef.current = createVoiceLeaseOwnerId();
|
|
61767
|
+
const t = useCodingNsTranslator(services.locale);
|
|
61768
|
+
const nativeAdapter = getGlobalVoiceAdapter(services);
|
|
61769
|
+
const ownerId = nativeAdapter?.configuredOwnerId ?? ownerIdRef.current;
|
|
61770
|
+
const browserVoiceSecurity = inspectBrowserVoiceSecurity();
|
|
61771
|
+
const voiceSettings = settingsValue?.assistant?.voice ?? DEFAULT_ASSISTANT_VOICE_SETTINGS;
|
|
61772
|
+
const needsSetup = !isVoiceSettingsReady(voiceSettings);
|
|
61773
|
+
const adapter = nativeAdapter?.capabilities.realtime === true && browserVoiceSecurity.secure && !needsSetup && voiceSettings.provider === "sherpa-onnx" ? nativeAdapter : void 0;
|
|
61774
|
+
const realtimeUnavailableMessage = !browserVoiceSecurity.secure ? t("voice.secureContextRequired") : nativeAdapter?.capabilities.realtime !== true ? t("voice.dialog.unavailable") : voiceSettings.provider !== "sherpa-onnx" ? t("voice.dialog.unavailable") : void 0;
|
|
61775
|
+
(0, react.useEffect)(() => {
|
|
61776
|
+
const refresh = () => setSettingsValue(services.settings.getSnapshot().value);
|
|
61777
|
+
refresh();
|
|
61778
|
+
return services.settings.subscribe(refresh);
|
|
61779
|
+
}, [services.settings]);
|
|
61780
|
+
(0, react.useEffect)(() => {
|
|
61781
|
+
if (adapter !== void 0) {
|
|
61782
|
+
setSnapshot({
|
|
61783
|
+
state: "disabled",
|
|
61784
|
+
active: false
|
|
61785
|
+
});
|
|
61786
|
+
const unsubscribe = adapter.subscribe((event) => {
|
|
61787
|
+
if (event.type === "state" || event.type === "wake" || event.type === "barge-in" || event.type === "error") services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/event", {
|
|
61788
|
+
ownerId,
|
|
61789
|
+
event,
|
|
61790
|
+
sequence: ++eventSequenceRef.current
|
|
61791
|
+
}).catch(() => void 0);
|
|
61792
|
+
if (event.type === "partial") setPartialText(event.text);
|
|
61793
|
+
else if (event.type === "final" && event.text.trim() !== "") {
|
|
61794
|
+
setTranscript((previous) => [...previous, event.text.trim()]);
|
|
61795
|
+
setPartialText("");
|
|
61796
|
+
}
|
|
61797
|
+
if (event.type === "state") setSnapshot({
|
|
61798
|
+
state: event.state,
|
|
61799
|
+
active: event.state !== "disabled",
|
|
61800
|
+
ownerId
|
|
61801
|
+
});
|
|
61802
|
+
else if (event.type === "barge-in") setSnapshot({
|
|
61803
|
+
state: "interrupted",
|
|
61804
|
+
active: true,
|
|
61805
|
+
ownerId
|
|
61806
|
+
});
|
|
61807
|
+
else if (event.type === "error") {
|
|
61808
|
+
const message = event.code === "voice_empty_transcript" ? t("voice.emptyTranscript") : event.code === "voice_invalid_transcript" ? t("voice.invalidTranscript") : event.message;
|
|
61809
|
+
setSnapshot({
|
|
61810
|
+
state: "error",
|
|
61811
|
+
active: true,
|
|
61812
|
+
ownerId,
|
|
61813
|
+
message
|
|
61814
|
+
});
|
|
61815
|
+
if (!event.recoverable) adapter.stop().catch(() => void 0);
|
|
61816
|
+
}
|
|
61817
|
+
});
|
|
61818
|
+
const onPageHide = () => {
|
|
61819
|
+
adapter.stop().catch(() => void 0);
|
|
61820
|
+
};
|
|
61821
|
+
if (typeof window !== "undefined") window.addEventListener("pagehide", onPageHide);
|
|
61822
|
+
return () => {
|
|
61823
|
+
unsubscribe();
|
|
61824
|
+
if (typeof window !== "undefined") window.removeEventListener("pagehide", onPageHide);
|
|
61825
|
+
adapter.stop().catch(() => void 0);
|
|
61826
|
+
};
|
|
61827
|
+
}
|
|
61828
|
+
setSnapshot({
|
|
61829
|
+
state: "error",
|
|
61830
|
+
active: false,
|
|
61831
|
+
ownerId,
|
|
61832
|
+
...realtimeUnavailableMessage === void 0 ? {} : { message: realtimeUnavailableMessage }
|
|
61833
|
+
});
|
|
61834
|
+
}, [
|
|
61835
|
+
adapter,
|
|
61836
|
+
ownerId,
|
|
61837
|
+
realtimeUnavailableMessage,
|
|
61838
|
+
services,
|
|
61839
|
+
t
|
|
61840
|
+
]);
|
|
61841
|
+
(0, react.useEffect)(() => {
|
|
61842
|
+
if (adapter === void 0) return void 0;
|
|
61843
|
+
const timer = setInterval(() => {
|
|
61844
|
+
if (adapter.ownerId === void 0) return;
|
|
61845
|
+
services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/heartbeat", { ownerId }).catch(() => void 0);
|
|
61846
|
+
}, 1e4);
|
|
61847
|
+
return () => clearInterval(timer);
|
|
61848
|
+
}, [
|
|
61849
|
+
adapter,
|
|
61850
|
+
ownerId,
|
|
61851
|
+
services
|
|
61852
|
+
]);
|
|
61853
|
+
const startConversation = async () => {
|
|
61854
|
+
if (conversationPending || snapshot.active === true) return;
|
|
61855
|
+
setConversationPending(true);
|
|
61856
|
+
setPartialText("");
|
|
61857
|
+
setSnapshot((previous) => ({
|
|
61858
|
+
...previous,
|
|
61859
|
+
state: "loading",
|
|
61860
|
+
active: false,
|
|
61861
|
+
ownerId
|
|
61862
|
+
}));
|
|
61863
|
+
try {
|
|
61864
|
+
if (adapter === void 0) throw new Error(realtimeUnavailableMessage ?? t("voice.dialog.unavailable"));
|
|
61865
|
+
await adapter.start(ownerId);
|
|
61866
|
+
} catch (error) {
|
|
61867
|
+
setSnapshot({
|
|
61868
|
+
state: "error",
|
|
61869
|
+
active: false,
|
|
61870
|
+
ownerId,
|
|
61871
|
+
message: error instanceof Error ? error.message : String(error)
|
|
61872
|
+
});
|
|
61873
|
+
} finally {
|
|
61874
|
+
setConversationPending(false);
|
|
61875
|
+
}
|
|
61876
|
+
};
|
|
61877
|
+
const stopConversation = async () => {
|
|
61878
|
+
if (!conversationPending && snapshot.active !== true) return;
|
|
61879
|
+
setConversationPending(true);
|
|
61880
|
+
try {
|
|
61881
|
+
if (adapter !== void 0) await adapter.stop();
|
|
61882
|
+
setPartialText("");
|
|
61883
|
+
} catch (error) {
|
|
61884
|
+
setSnapshot({
|
|
61885
|
+
state: "error",
|
|
61886
|
+
active: false,
|
|
61887
|
+
ownerId,
|
|
61888
|
+
message: error instanceof Error ? error.message : String(error)
|
|
61889
|
+
});
|
|
61890
|
+
} finally {
|
|
61891
|
+
setConversationPending(false);
|
|
61892
|
+
}
|
|
61893
|
+
};
|
|
61894
|
+
const closeConversation = () => {
|
|
61895
|
+
if (snapshot.active === true) {
|
|
61896
|
+
stopConversation().finally(() => setConversationOpen(false));
|
|
61897
|
+
return;
|
|
61898
|
+
}
|
|
61899
|
+
setConversationOpen(false);
|
|
61900
|
+
};
|
|
61901
|
+
const openConversation = () => {
|
|
61902
|
+
if (needsSetup) {
|
|
61903
|
+
setSetupOpen(true);
|
|
61904
|
+
return;
|
|
61905
|
+
}
|
|
61906
|
+
setConversationOpen(true);
|
|
61907
|
+
};
|
|
61908
|
+
(0, react.useEffect)(() => {
|
|
61909
|
+
const doc = typeof document === "undefined" ? void 0 : document;
|
|
61910
|
+
if (doc === void 0) return void 0;
|
|
61911
|
+
const assistantButtonSize = `${GLOBAL_ASSISTANT_BUTTON_SIZE}px`;
|
|
61912
|
+
let mountedNative;
|
|
61913
|
+
let mountedRow;
|
|
61914
|
+
let mountedParent;
|
|
61915
|
+
let mountedNextSibling;
|
|
61916
|
+
let mountedNativeInlineStyle;
|
|
61917
|
+
const mount = () => {
|
|
61918
|
+
const buttons = [...doc.querySelectorAll("button")];
|
|
61919
|
+
const native = buttons.find((candidate) => {
|
|
61920
|
+
return (typeof candidate.className === "string" ? candidate.className.split(/\s+/u) : []).some((name) => name.endsWith("_newSession"));
|
|
61921
|
+
}) ?? buttons.find((candidate) => {
|
|
61922
|
+
const aria = candidate.getAttribute("aria-label") ?? "";
|
|
61923
|
+
return /新建会话|新建对话|new session|new conversation/iu.test(aria);
|
|
61924
|
+
});
|
|
61925
|
+
if (native === void 0 || native.parentElement === null || native.parentElement.querySelector("[data-codingns-global-voice-button]") !== null) return;
|
|
61926
|
+
const button = doc.createElement("button");
|
|
61927
|
+
button.type = "button";
|
|
61928
|
+
button.setAttribute("data-codingns-global-voice-button", "true");
|
|
61929
|
+
const label = needsSetup ? t("voice.setup.open") : t("voice.globalAssistant");
|
|
61930
|
+
button.setAttribute("aria-label", label);
|
|
61931
|
+
button.setAttribute("aria-description", [
|
|
61932
|
+
t("voice.microphoneNotice"),
|
|
61933
|
+
t("voice.audioNotice"),
|
|
61934
|
+
snapshot.message
|
|
61935
|
+
].filter(Boolean).join(" "));
|
|
61936
|
+
button.setAttribute("aria-haspopup", "dialog");
|
|
61937
|
+
button.setAttribute("aria-expanded", String(conversationOpen || setupOpen));
|
|
61938
|
+
button.setAttribute("data-codingns-voice-state", snapshot.state ?? "disabled");
|
|
61939
|
+
button.title = label;
|
|
61940
|
+
button.innerHTML = "<svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 2 1.45 5.55L19 9l-5.55 1.45L12 16l-1.45-5.55L5 9l5.55-1.45L12 2Z\"/><path d=\"m5 15 .7 2.3L8 18l-2.3.7L5 21l-.7-2.3L2 18l2.3-.7L5 15Z\"/><path d=\"m19 14 .45 1.55L21 16l-1.55.45L19 18l-.45-1.55L17 16l1.55-.45L19 14Z\"/></svg>";
|
|
61941
|
+
button.style.cssText = `display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;cursor:pointer;background:var(--ds-color-fill-tertiary,rgba(127,127,127,.12));flex:0 1 ${assistantButtonSize};min-width:${assistantButtonSize};width:${assistantButtonSize};max-width:${assistantButtonSize};height:${assistantButtonSize};aspect-ratio:1 / 1;box-sizing:border-box;white-space:nowrap;margin:0`;
|
|
61942
|
+
button.addEventListener("click", openConversation);
|
|
61943
|
+
const parent = native.parentElement;
|
|
61944
|
+
const row = doc.createElement("div");
|
|
61945
|
+
row.setAttribute("data-codingns-global-voice-row", "true");
|
|
61946
|
+
row.style.cssText = "display:flex;flex:0 1 auto;flex-flow:row nowrap;align-items:center;gap:4px;min-width:0;width:calc(100% - 4px);max-width:100%;box-sizing:border-box;overflow:hidden;margin:0 2px 12px";
|
|
61947
|
+
mountedNative = native;
|
|
61948
|
+
mountedRow = row;
|
|
61949
|
+
mountedParent = parent;
|
|
61950
|
+
mountedNextSibling = native.nextSibling;
|
|
61951
|
+
mountedNativeInlineStyle = {
|
|
61952
|
+
flex: native.style.flex,
|
|
61953
|
+
minWidth: native.style.minWidth,
|
|
61954
|
+
width: native.style.width,
|
|
61955
|
+
maxWidth: native.style.maxWidth,
|
|
61956
|
+
boxSizing: native.style.boxSizing,
|
|
61957
|
+
whiteSpace: native.style.whiteSpace,
|
|
61958
|
+
margin: native.style.margin
|
|
61959
|
+
};
|
|
61960
|
+
parent.insertBefore(row, native);
|
|
61961
|
+
row.append(native, button);
|
|
61962
|
+
native.style.flex = "1 1 auto";
|
|
61963
|
+
native.style.minWidth = "0";
|
|
61964
|
+
native.style.width = "auto";
|
|
61965
|
+
native.style.maxWidth = "100%";
|
|
61966
|
+
native.style.boxSizing = "border-box";
|
|
61967
|
+
native.style.whiteSpace = "nowrap";
|
|
61968
|
+
native.style.margin = "0";
|
|
61969
|
+
button.style.flex = `0 1 ${assistantButtonSize}`;
|
|
61970
|
+
button.style.width = assistantButtonSize;
|
|
61971
|
+
button.style.minWidth = assistantButtonSize;
|
|
61972
|
+
button.style.maxWidth = assistantButtonSize;
|
|
61973
|
+
button.style.height = assistantButtonSize;
|
|
61974
|
+
};
|
|
61975
|
+
mount();
|
|
61976
|
+
const observer = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(mount);
|
|
61977
|
+
observer?.observe(doc.body ?? doc.documentElement, {
|
|
61978
|
+
childList: true,
|
|
61979
|
+
subtree: true
|
|
61980
|
+
});
|
|
61981
|
+
return () => {
|
|
61982
|
+
observer?.disconnect();
|
|
61983
|
+
if (mountedNative !== void 0 && mountedNativeInlineStyle !== void 0) {
|
|
61984
|
+
mountedNative.style.flex = mountedNativeInlineStyle.flex;
|
|
61985
|
+
mountedNative.style.minWidth = mountedNativeInlineStyle.minWidth;
|
|
61986
|
+
mountedNative.style.width = mountedNativeInlineStyle.width;
|
|
61987
|
+
mountedNative.style.maxWidth = mountedNativeInlineStyle.maxWidth;
|
|
61988
|
+
mountedNative.style.boxSizing = mountedNativeInlineStyle.boxSizing;
|
|
61989
|
+
mountedNative.style.whiteSpace = mountedNativeInlineStyle.whiteSpace;
|
|
61990
|
+
mountedNative.style.margin = mountedNativeInlineStyle.margin;
|
|
61991
|
+
}
|
|
61992
|
+
if (mountedNative !== void 0 && mountedParent !== void 0 && mountedParent.isConnected) {
|
|
61993
|
+
if (mountedNextSibling !== null && mountedNextSibling !== void 0 && mountedNextSibling.parentNode === mountedParent) mountedParent.insertBefore(mountedNative, mountedNextSibling);
|
|
61994
|
+
else mountedParent.appendChild(mountedNative);
|
|
61995
|
+
}
|
|
61996
|
+
mountedRow?.remove();
|
|
61997
|
+
doc.querySelectorAll("[data-codingns-global-voice-button]").forEach((node) => node.remove());
|
|
61998
|
+
};
|
|
61999
|
+
}, [
|
|
62000
|
+
adapter,
|
|
62001
|
+
conversationOpen,
|
|
62002
|
+
needsSetup,
|
|
62003
|
+
openConversation,
|
|
62004
|
+
setSetupOpen,
|
|
62005
|
+
setupOpen,
|
|
62006
|
+
snapshot.state,
|
|
62007
|
+
t
|
|
62008
|
+
]);
|
|
62009
|
+
return (0, react.createElement)("span", {
|
|
62010
|
+
"data-codingns-global-voice": "true",
|
|
62011
|
+
...setupOpen || conversationOpen ? {} : { "aria-hidden": "true" }
|
|
62012
|
+
}, setupOpen ? (0, react.createElement)(VoiceInitializationDialog, {
|
|
62013
|
+
services,
|
|
62014
|
+
value: voiceSettings,
|
|
62015
|
+
onClose: () => setSetupOpen(false)
|
|
62016
|
+
}) : conversationOpen ? (0, react.createElement)(VoiceConversationDialog, {
|
|
62017
|
+
t,
|
|
62018
|
+
active: snapshot.active === true,
|
|
62019
|
+
pending: conversationPending,
|
|
62020
|
+
state: snapshot.state,
|
|
62021
|
+
message: snapshot.message,
|
|
62022
|
+
partialText,
|
|
62023
|
+
transcript,
|
|
62024
|
+
realtimeAvailable: adapter !== void 0,
|
|
62025
|
+
unavailableMessage: realtimeUnavailableMessage,
|
|
62026
|
+
onStart: () => {
|
|
62027
|
+
startConversation();
|
|
62028
|
+
},
|
|
62029
|
+
onStop: () => {
|
|
62030
|
+
stopConversation();
|
|
62031
|
+
},
|
|
62032
|
+
onClose: closeConversation,
|
|
62033
|
+
onClear: () => {
|
|
62034
|
+
setTranscript([]);
|
|
62035
|
+
setPartialText("");
|
|
62036
|
+
}
|
|
62037
|
+
}) : null);
|
|
62038
|
+
}
|
|
62039
|
+
function createVoiceLeaseOwnerId() {
|
|
62040
|
+
try {
|
|
62041
|
+
const randomUUID = globalThis.crypto?.randomUUID;
|
|
62042
|
+
if (typeof randomUUID === "function") return `codingns4dsh:global-voice:${randomUUID.call(globalThis.crypto)}`;
|
|
62043
|
+
} catch {}
|
|
62044
|
+
return `codingns4dsh:global-voice:${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
62045
|
+
}
|
|
62046
|
+
function isVoiceSettingsReady(settings) {
|
|
62047
|
+
if (!settings.initialized || settings.provider !== "sherpa-onnx") return false;
|
|
62048
|
+
return settings.asrEncoder.trim() !== "" && settings.asrDecoder.trim() !== "" && settings.asrJoiner.trim() !== "" && settings.asrTokens.trim() !== "";
|
|
62049
|
+
}
|
|
62050
|
+
//#endregion
|
|
59469
62051
|
//#region src/client/peer-host-scoped-client.ts
|
|
59470
62052
|
init_transport();
|
|
59471
62053
|
/** 根据当前 Host 自有端点生成作用域 WebSocket 工厂;不接受目标 Host URL。 */
|
|
@@ -60060,7 +62642,8 @@ window.__ModuleLoader__.load({
|
|
|
60060
62642
|
debugFeature,
|
|
60061
62643
|
gitManagementFeature,
|
|
60062
62644
|
fileManagementFeature,
|
|
60063
|
-
peerHostFeature
|
|
62645
|
+
peerHostFeature,
|
|
62646
|
+
globalVoiceAssistantFeature
|
|
60064
62647
|
];
|
|
60065
62648
|
/**
|
|
60066
62649
|
* 汇总需要在设置页显示的模块,按 ui.order 升序。
|
|
@@ -60754,7 +63337,7 @@ window.__ModuleLoader__.load({
|
|
|
60754
63337
|
Time.template = template;
|
|
60755
63338
|
})(Time || (Time = {}));
|
|
60756
63339
|
//#endregion
|
|
60757
|
-
//#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.4_@deepseek-ai+cordis-plugin-include@1.0.9_@deepseek-ai+cordis-plugin-loader@1.0.
|
|
63340
|
+
//#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.4_@deepseek-ai+cordis-plugin-include@1.0.9_@deepseek-ai+cordis-plugin-loader@1.0.6-alpha.1/node_modules/@deepseek-ai/cordis/lib/index.js
|
|
60758
63341
|
/** Ordered collection of disposable values with O(1) deletion by value. */
|
|
60759
63342
|
var DisposableList = class {
|
|
60760
63343
|
sn = 0;
|
|
@@ -62659,7 +65242,7 @@ window.__ModuleLoader__.load({
|
|
|
62659
65242
|
this.initialStateConsumed = true;
|
|
62660
65243
|
if (initial.info.state === "running") this.connect();
|
|
62661
65244
|
else this.refresh();
|
|
62662
|
-
} else this.refresh();
|
|
65245
|
+
} else if (!this.resumeFollow()) this.refresh();
|
|
62663
65246
|
}
|
|
62664
65247
|
let active = true;
|
|
62665
65248
|
return () => {
|
|
@@ -62669,9 +65252,20 @@ window.__ModuleLoader__.load({
|
|
|
62669
65252
|
if (this.mounted === 0 && !this.keepAlive) this.detach();
|
|
62670
65253
|
};
|
|
62671
65254
|
}
|
|
62672
|
-
|
|
62673
|
-
|
|
65255
|
+
/**
|
|
65256
|
+
* 重新加载终端环境与库存。
|
|
65257
|
+
*
|
|
65258
|
+
* 默认复用仍然活跃的 follow 连接:只有连接确实不存在时才向 Host 重新发起
|
|
65259
|
+
* environment/list。`force` 供“重新连接/刷新”这类显式动作使用,会先释放
|
|
65260
|
+
* 现有 follow 再重建,避免保活连接变成无法恢复的僵尸订阅。
|
|
65261
|
+
*/
|
|
65262
|
+
async refresh(options) {
|
|
62674
65263
|
if (this.lifetime.signal.aborted) return;
|
|
65264
|
+
if (this.loading !== void 0) return this.loading;
|
|
65265
|
+
if (options?.force === true) {
|
|
65266
|
+
this.clearReconnect();
|
|
65267
|
+
this.releaseFollow();
|
|
65268
|
+
} else if (this.resumeFollow()) return;
|
|
62675
65269
|
this.loading = this.load().finally(() => {
|
|
62676
65270
|
this.loading = void 0;
|
|
62677
65271
|
});
|
|
@@ -62687,6 +65281,7 @@ window.__ModuleLoader__.load({
|
|
|
62687
65281
|
if (this.loading !== void 0) return this.loading;
|
|
62688
65282
|
this.clearReconnect();
|
|
62689
65283
|
this.reconnectAttempts = 0;
|
|
65284
|
+
this.releaseFollow();
|
|
62690
65285
|
this.patch({
|
|
62691
65286
|
phase: "loading",
|
|
62692
65287
|
writable: false
|
|
@@ -62700,17 +65295,17 @@ window.__ModuleLoader__.load({
|
|
|
62700
65295
|
async loadWithRebuild() {
|
|
62701
65296
|
try {
|
|
62702
65297
|
const remote = resolveRemote(this.remote);
|
|
62703
|
-
const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
|
|
65298
|
+
const environment = unwrap(await this.request(remote.environment(this.sessionId, this.lifetime.signal)));
|
|
62704
65299
|
this.patch({
|
|
62705
65300
|
phase: "creating",
|
|
62706
65301
|
environment
|
|
62707
65302
|
});
|
|
62708
|
-
const info = unwrap(await remote.create(this.sessionId, {
|
|
65303
|
+
const info = unwrap(await this.request(remote.create(this.sessionId, {
|
|
62709
65304
|
id: this.id,
|
|
62710
65305
|
...this.shellPath === void 0 ? {} : { shellPath: this.shellPath },
|
|
62711
65306
|
cols: Math.min(80, environment.maxCols),
|
|
62712
65307
|
rows: Math.min(24, environment.maxRows)
|
|
62713
|
-
}, this.lifetime.signal));
|
|
65308
|
+
}, this.lifetime.signal)));
|
|
62714
65309
|
debugInfo("codingns4dsh: client terminal rebuilt", {
|
|
62715
65310
|
sessionId: this.sessionId,
|
|
62716
65311
|
terminalId: info.id
|
|
@@ -62722,7 +65317,7 @@ window.__ModuleLoader__.load({
|
|
|
62722
65317
|
info,
|
|
62723
65318
|
title: info.title
|
|
62724
65319
|
});
|
|
62725
|
-
|
|
65320
|
+
this.finishLoad();
|
|
62726
65321
|
} catch (error) {
|
|
62727
65322
|
if (!this.lifetime.signal.aborted) this.fail(error);
|
|
62728
65323
|
}
|
|
@@ -62747,7 +65342,7 @@ window.__ModuleLoader__.load({
|
|
|
62747
65342
|
unwrap(await resolveRemote(this.remote).write(this.sessionId, this.id, attachmentId, data));
|
|
62748
65343
|
}).catch((error) => this.fail(error));
|
|
62749
65344
|
}
|
|
62750
|
-
resize(cols, rows) {
|
|
65345
|
+
resize(cols, rows, options) {
|
|
62751
65346
|
const state = this.store.getSnapshot();
|
|
62752
65347
|
const attachmentId = this.attachmentId;
|
|
62753
65348
|
if (!state.writable || attachmentId === void 0) return;
|
|
@@ -62758,7 +65353,7 @@ window.__ModuleLoader__.load({
|
|
|
62758
65353
|
cols: nextCols,
|
|
62759
65354
|
rows: nextRows
|
|
62760
65355
|
};
|
|
62761
|
-
if (sameResize(this.lastResize, request)) return;
|
|
65356
|
+
if (options?.force !== true && sameResize(this.lastResize, request)) return;
|
|
62762
65357
|
this.lastResize = request;
|
|
62763
65358
|
this.writes = this.writes.then(async () => {
|
|
62764
65359
|
unwrap(await resolveRemote(this.remote).resize(this.sessionId, this.id, attachmentId, nextCols, nextRows));
|
|
@@ -62817,7 +65412,7 @@ window.__ModuleLoader__.load({
|
|
|
62817
65412
|
});
|
|
62818
65413
|
try {
|
|
62819
65414
|
const remote = resolveRemote(this.remote);
|
|
62820
|
-
const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
|
|
65415
|
+
const environment = unwrap(await this.request(remote.environment(this.sessionId, this.lifetime.signal)));
|
|
62821
65416
|
debugInfo("codingns4dsh: client terminal environment", {
|
|
62822
65417
|
sessionId: this.sessionId,
|
|
62823
65418
|
workspaceId: environment.workspaceId,
|
|
@@ -62837,7 +65432,7 @@ window.__ModuleLoader__.load({
|
|
|
62837
65432
|
createWhenMissing = createWhenMissing && !binding.existing;
|
|
62838
65433
|
}
|
|
62839
65434
|
}
|
|
62840
|
-
const listed = unwrap(await remote.list(this.sessionId));
|
|
65435
|
+
const listed = unwrap(await this.request(remote.list(this.sessionId)));
|
|
62841
65436
|
debugInfo("codingns4dsh: client terminal list", {
|
|
62842
65437
|
sessionId: this.sessionId,
|
|
62843
65438
|
workspaceId: environment.workspaceId,
|
|
@@ -62851,12 +65446,12 @@ window.__ModuleLoader__.load({
|
|
|
62851
65446
|
phase: "creating",
|
|
62852
65447
|
environment
|
|
62853
65448
|
});
|
|
62854
|
-
info = unwrap(await remote.create(this.sessionId, {
|
|
65449
|
+
info = unwrap(await this.request(remote.create(this.sessionId, {
|
|
62855
65450
|
id: this.id,
|
|
62856
65451
|
...this.shellPath === void 0 ? {} : { shellPath: this.shellPath },
|
|
62857
65452
|
cols: Math.min(80, environment.maxCols),
|
|
62858
65453
|
rows: Math.min(24, environment.maxRows)
|
|
62859
|
-
}, this.lifetime.signal));
|
|
65454
|
+
}, this.lifetime.signal)));
|
|
62860
65455
|
debugInfo("codingns4dsh: client terminal created", {
|
|
62861
65456
|
sessionId: this.sessionId,
|
|
62862
65457
|
workspaceId: environment.workspaceId,
|
|
@@ -62869,13 +65464,42 @@ window.__ModuleLoader__.load({
|
|
|
62869
65464
|
info,
|
|
62870
65465
|
title: info.title
|
|
62871
65466
|
});
|
|
62872
|
-
|
|
65467
|
+
this.finishLoad();
|
|
62873
65468
|
} catch (error) {
|
|
62874
65469
|
if (!this.lifetime.signal.aborted) this.fail(error);
|
|
62875
65470
|
}
|
|
62876
65471
|
}
|
|
65472
|
+
/**
|
|
65473
|
+
* 加载阶段的 Host 请求等待上限。
|
|
65474
|
+
*
|
|
65475
|
+
* 页面被浏览器冻结(切到后台)期间发出的请求可能永远不会返回;没有上限时
|
|
65476
|
+
* 视图会永久停在“正在读取终端环境”,用户点重试也只会复用同一个挂起 Promise。
|
|
65477
|
+
*/
|
|
65478
|
+
request(task) {
|
|
65479
|
+
return withLoadTimeout(task, TERMINAL_LOAD_TIMEOUT_MS, this.t("terminalView.loadTimeout"));
|
|
65480
|
+
}
|
|
65481
|
+
/**
|
|
65482
|
+
* 加载完成后的状态收敛。
|
|
65483
|
+
*
|
|
65484
|
+
* 可见时建立或复用 follow;不可见且没有保活连接时不能停在 loading,否则
|
|
65485
|
+
* 切回会话的瞬间会先渲染成“正在读取终端环境”。
|
|
65486
|
+
*/
|
|
65487
|
+
finishLoad() {
|
|
65488
|
+
if (this.mounted > 0) {
|
|
65489
|
+
this.connect();
|
|
65490
|
+
return;
|
|
65491
|
+
}
|
|
65492
|
+
if (this.followController === void 0 && this.store.getSnapshot().phase === "loading") this.patch({
|
|
65493
|
+
phase: "disconnected",
|
|
65494
|
+
writable: false
|
|
65495
|
+
});
|
|
65496
|
+
}
|
|
62877
65497
|
connect() {
|
|
62878
|
-
if (this.followController !== void 0
|
|
65498
|
+
if (this.followController !== void 0) {
|
|
65499
|
+
this.resumeFollow();
|
|
65500
|
+
return;
|
|
65501
|
+
}
|
|
65502
|
+
if (this.lifetime.signal.aborted || this.mounted === 0) return;
|
|
62879
65503
|
this.clearReconnect();
|
|
62880
65504
|
const controller = new AbortController();
|
|
62881
65505
|
this.followController = controller;
|
|
@@ -62887,6 +65511,41 @@ window.__ModuleLoader__.load({
|
|
|
62887
65511
|
});
|
|
62888
65512
|
this.consume(controller.signal);
|
|
62889
65513
|
}
|
|
65514
|
+
/**
|
|
65515
|
+
* 复用仍然活跃的 follow 连接,并恢复可见状态。
|
|
65516
|
+
*
|
|
65517
|
+
* 聚合视图在后台保活期间连接不会释放,重新可见只是显示层切换,不需要重新
|
|
65518
|
+
* 读取环境或库存;把它降级成 loading 会让终端变成只读,且只能等下一次
|
|
65519
|
+
* 输出帧才能恢复。
|
|
65520
|
+
*
|
|
65521
|
+
* @returns 是否复用了已有连接;false 表示必须重新加载。
|
|
65522
|
+
*/
|
|
65523
|
+
resumeFollow() {
|
|
65524
|
+
const controller = this.followController;
|
|
65525
|
+
if (controller === void 0 || controller.signal.aborted) return false;
|
|
65526
|
+
const state = this.store.getSnapshot();
|
|
65527
|
+
if (state.phase === "closed" || state.phase === "failed") return false;
|
|
65528
|
+
this.clearReconnect();
|
|
65529
|
+
if (state.render !== void 0) {
|
|
65530
|
+
if (state.phase !== "connected" || !state.writable) this.patch({
|
|
65531
|
+
phase: "connected",
|
|
65532
|
+
writable: true
|
|
65533
|
+
});
|
|
65534
|
+
} else if (state.phase !== "connecting") this.patch({
|
|
65535
|
+
phase: "connecting",
|
|
65536
|
+
writable: false
|
|
65537
|
+
});
|
|
65538
|
+
return true;
|
|
65539
|
+
}
|
|
65540
|
+
/** 只释放 follow 连接与附件,不改变对外状态;detach、重建和强制刷新共用。 */
|
|
65541
|
+
releaseFollow() {
|
|
65542
|
+
this.pendingRender?.resolve();
|
|
65543
|
+
this.pendingRender = void 0;
|
|
65544
|
+
this.followController?.abort(/* @__PURE__ */ new Error("终端视图重新连接"));
|
|
65545
|
+
this.followController = void 0;
|
|
65546
|
+
this.attachmentId = void 0;
|
|
65547
|
+
this.lastResize = void 0;
|
|
65548
|
+
}
|
|
62890
65549
|
async consume(signal) {
|
|
62891
65550
|
try {
|
|
62892
65551
|
const attachmentId = this.attachmentId;
|
|
@@ -62934,6 +65593,7 @@ window.__ModuleLoader__.load({
|
|
|
62934
65593
|
revision: this.revision,
|
|
62935
65594
|
resolve: resolveWaiting
|
|
62936
65595
|
};
|
|
65596
|
+
const recoveredControl = this.store.getSnapshot().error?.includes("当前 attach 没有终端输入控制权") === true;
|
|
62937
65597
|
this.patch({
|
|
62938
65598
|
phase: "connected",
|
|
62939
65599
|
writable: true,
|
|
@@ -62942,16 +65602,12 @@ window.__ModuleLoader__.load({
|
|
|
62942
65602
|
frame
|
|
62943
65603
|
}
|
|
62944
65604
|
});
|
|
65605
|
+
if (recoveredControl) this.clearError();
|
|
62945
65606
|
await waiting;
|
|
62946
65607
|
}
|
|
62947
65608
|
detach() {
|
|
62948
65609
|
this.clearReconnect();
|
|
62949
|
-
this.
|
|
62950
|
-
this.pendingRender = void 0;
|
|
62951
|
-
this.followController?.abort(/* @__PURE__ */ new Error("终端视图已 detach"));
|
|
62952
|
-
this.followController = void 0;
|
|
62953
|
-
this.attachmentId = void 0;
|
|
62954
|
-
this.lastResize = void 0;
|
|
65610
|
+
this.releaseFollow();
|
|
62955
65611
|
if (!this.lifetime.signal.aborted && this.store.getSnapshot().phase !== "closed") {
|
|
62956
65612
|
const { render: _render, ...state } = this.store.getSnapshot();
|
|
62957
65613
|
this.store.set({
|
|
@@ -63017,6 +65673,10 @@ window.__ModuleLoader__.load({
|
|
|
63017
65673
|
...patch
|
|
63018
65674
|
});
|
|
63019
65675
|
}
|
|
65676
|
+
clearError() {
|
|
65677
|
+
const { error: _error, ...state } = this.store.getSnapshot();
|
|
65678
|
+
this.store.set(state);
|
|
65679
|
+
}
|
|
63020
65680
|
fail(error) {
|
|
63021
65681
|
this.patch({
|
|
63022
65682
|
phase: "failed",
|
|
@@ -63360,6 +66020,37 @@ window.__ModuleLoader__.load({
|
|
|
63360
66020
|
}
|
|
63361
66021
|
}
|
|
63362
66022
|
};
|
|
66023
|
+
/** 加载阶段单次 Host 请求的等待上限;超过后按失败处理,用户可以显式重试。 */
|
|
66024
|
+
const TERMINAL_LOAD_TIMEOUT_MS = 2e4;
|
|
66025
|
+
/**
|
|
66026
|
+
* 给加载阶段的请求加等待上限。
|
|
66027
|
+
*
|
|
66028
|
+
* 页面被浏览器冻结(切到后台)期间发出的请求可能永远不会返回;没有上限时
|
|
66029
|
+
* 视图会永久停在“正在读取终端环境”,重试也只会复用同一个挂起的 Promise。
|
|
66030
|
+
* 底层请求不保证可取消,因此这里只结束等待,并先给原请求挂上拒绝处理器,
|
|
66031
|
+
* 避免它稍后失败时产生 unhandled rejection。
|
|
66032
|
+
*/
|
|
66033
|
+
async function withLoadTimeout(task, timeoutMs, message) {
|
|
66034
|
+
const guarded = task.then((value) => ({
|
|
66035
|
+
state: "value",
|
|
66036
|
+
value
|
|
66037
|
+
}), (error) => ({
|
|
66038
|
+
state: "error",
|
|
66039
|
+
error
|
|
66040
|
+
}));
|
|
66041
|
+
let timer;
|
|
66042
|
+
const expired = new Promise((resolve) => {
|
|
66043
|
+
timer = setTimeout(() => resolve({ state: "timeout" }), timeoutMs);
|
|
66044
|
+
});
|
|
66045
|
+
try {
|
|
66046
|
+
const outcome = await Promise.race([guarded, expired]);
|
|
66047
|
+
if (outcome.state === "value") return outcome.value;
|
|
66048
|
+
if (outcome.state === "error") throw outcome.error;
|
|
66049
|
+
throw new Error(message);
|
|
66050
|
+
} finally {
|
|
66051
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
66052
|
+
}
|
|
66053
|
+
}
|
|
63363
66054
|
function unwrap(result) {
|
|
63364
66055
|
if (typeof result !== "object" || result === null || !("ok" in result)) throw new Error("终端服务返回了无效响应");
|
|
63365
66056
|
if (result.ok) return result.value;
|
|
@@ -63670,8 +66361,16 @@ window.__ModuleLoader__.load({
|
|
|
63670
66361
|
titleInput: "codingns4dsh-terminal-title-input",
|
|
63671
66362
|
root: "codingns4dsh-terminal-root",
|
|
63672
66363
|
aggregateRoot: "codingns4dsh-terminal-aggregate-root",
|
|
66364
|
+
listDock: "codingns4dsh-terminal-list-dock",
|
|
63673
66365
|
list: "codingns4dsh-terminal-list",
|
|
66366
|
+
listTabs: "codingns4dsh-terminal-list-tabs",
|
|
66367
|
+
listActions: "codingns4dsh-terminal-list-actions",
|
|
63674
66368
|
newButton: "codingns4dsh-terminal-new-button",
|
|
66369
|
+
refreshButton: "codingns4dsh-terminal-refresh-button",
|
|
66370
|
+
toolsButton: "codingns4dsh-terminal-tools-button",
|
|
66371
|
+
toolsPanel: "codingns4dsh-terminal-tools-panel",
|
|
66372
|
+
toolAction: "codingns4dsh-terminal-tool-action",
|
|
66373
|
+
toolModifierActive: "codingns4dsh-terminal-tool-modifier-active",
|
|
63675
66374
|
listRow: "codingns4dsh-terminal-list-row",
|
|
63676
66375
|
listRowSelected: "codingns4dsh-terminal-list-row-selected",
|
|
63677
66376
|
listSelect: "codingns4dsh-terminal-list-select",
|
|
@@ -63709,8 +66408,20 @@ window.__ModuleLoader__.load({
|
|
|
63709
66408
|
.${terminalClass.titleInput}{width:120px;min-width:48px;max-width:100%;color:inherit;font:inherit;background:var(--dsw-alias-bg-l1);border:.5px solid var(--dsw-alias-border-l3);border-radius:4px;outline:none;padding:0 4px}
|
|
63710
66409
|
.${terminalClass.root}{box-sizing:border-box;width:0;height:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;flex:1 1 auto;padding-top:8px;font-size:13px;display:flex;overflow:hidden}
|
|
63711
66410
|
.${terminalClass.aggregateRoot}{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;display:flex;flex-direction:column;flex:1 1 auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);overflow:hidden}
|
|
63712
|
-
.${terminalClass.
|
|
66411
|
+
.${terminalClass.listDock}{box-sizing:border-box;width:100%;min-width:0;flex:none;background:var(--dsw-alias-bg-layer-1);border-bottom:.5px solid var(--dsw-alias-border-l3)}
|
|
66412
|
+
.${terminalClass.list}{box-sizing:border-box;width:100%;height:40px;min-width:0;min-height:40px;display:flex;align-items:center;gap:2px;padding:3px 8px;background:inherit;overflow-x:auto;overflow-y:hidden;flex:none}
|
|
66413
|
+
.${terminalClass.listTabs}{box-sizing:border-box;min-width:0;height:34px;display:flex;align-items:center;gap:2px;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}
|
|
66414
|
+
.${terminalClass.listTabs}::-webkit-scrollbar{display:none}
|
|
66415
|
+
.${terminalClass.listActions}{box-sizing:border-box;display:flex;align-items:center;gap:2px;flex:none;margin-left:4px;padding-left:4px;border-left:.5px solid var(--dsw-alias-border-l3);background:inherit}
|
|
63713
66416
|
.${terminalClass.newButton}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;align-self:center;width:30px;min-width:30px;height:30px;min-height:30px;max-height:30px;margin:0 0 0 4px;padding:0;white-space:nowrap}
|
|
66417
|
+
.${terminalClass.refreshButton},.${terminalClass.toolsButton}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;width:30px;min-width:30px;height:30px;min-height:30px;max-height:30px;margin:0;padding:0;color:var(--dsw-alias-label-secondary)}
|
|
66418
|
+
.${terminalClass.refreshButton}:hover, .${terminalClass.toolsButton}:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
66419
|
+
.${terminalClass.toolsButton}[aria-expanded="true"]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
66420
|
+
.${terminalClass.toolsPanel}{box-sizing:border-box;width:100%;min-width:0;display:flex;align-items:center;gap:4px;padding:4px 8px 6px;border-top:.5px solid var(--dsw-alias-border-l3);overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;overscroll-behavior:contain}
|
|
66421
|
+
.${terminalClass.toolsPanel}::-webkit-scrollbar{height:5px}
|
|
66422
|
+
.${terminalClass.toolsPanel}::-webkit-scrollbar-thumb{background:var(--dsw-alias-label-tertiary,#777);border-radius:3px}
|
|
66423
|
+
.${terminalClass.toolAction}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;width:38px;min-width:38px;height:28px;min-height:28px;padding:0!important;border-radius:var(--dsw-radius-sm,6px)}
|
|
66424
|
+
.${terminalClass.toolAction}[aria-pressed="true"],.${terminalClass.toolModifierActive}{background:var(--dsw-alias-button-ghost-active-fill,var(--dsw-alias-interactive-bg-active));box-shadow:inset 0 0 0 .5px var(--dsw-alias-button-ghost-active-border,var(--dsw-alias-border-l3))}
|
|
63714
66425
|
.${terminalClass.listRow}{box-sizing:border-box;display:flex;align-items:center;height:28px;min-width:110px;max-width:220px;border-radius:var(--dsw-radius-sm,6px);background:transparent;flex:none}
|
|
63715
66426
|
.${terminalClass.listRowSelected}{background:var(--dsw-alias-interactive-bg-hover);box-shadow:var(--dsw-elevation-soft,0 1px 2px rgba(0,0,0,.08))}
|
|
63716
66427
|
.${terminalClass.listSelect}{box-sizing:border-box;min-width:0;height:28px;min-height:28px;flex:1;text-align:left;color:inherit;border:0;background:transparent;cursor:pointer;padding:0 8px;display:flex;align-items:center;gap:5px;font:inherit;line-height:20px}
|
|
@@ -63721,12 +66432,13 @@ window.__ModuleLoader__.load({
|
|
|
63721
66432
|
.${terminalClass.listClose}:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
63722
66433
|
.${terminalClass.content}{box-sizing:border-box;width:100%;min-width:0;min-height:0;display:flex;flex:1 1 auto;overflow:hidden}
|
|
63723
66434
|
.${terminalClass.empty}{display:flex;flex:1;min-height:0;align-items:center;justify-content:center;flex-direction:column;gap:10px;color:var(--dsw-alias-label-secondary);text-align:center;padding:24px}
|
|
63724
|
-
.${terminalClass.screen}{box-sizing:border-box;width:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);flex:1 1 auto;padding:
|
|
66435
|
+
.${terminalClass.screen}{box-sizing:border-box;width:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);flex:1 1 auto;padding:0;overflow:hidden}
|
|
63725
66436
|
.${terminalClass.status}{background:var(--dsw-alias-bg-l2);flex-wrap:wrap;align-items:center;gap:8px;padding:5px 12px;display:flex}
|
|
63726
66437
|
.${terminalClass.error}{overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-l2);margin:0;padding:10px 12px}
|
|
63727
66438
|
.${terminalClass.cleanupStack}{pointer-events:auto;gap:8px;max-width:min(440px,calc(100vw - 40px));display:grid;position:fixed;bottom:20px;right:20px}
|
|
63728
66439
|
.${terminalClass.cleanupNotice}{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-l1);overflow-wrap:anywhere;border-radius:8px;align-items:center;gap:12px;padding:12px 16px;font-size:13px;display:flex;box-shadow:0 4px 20px #0002}
|
|
63729
66440
|
.${terminalClass.cleanupNotice} button{border:.5px solid var(--dsw-alias-border-l3);color:inherit;cursor:pointer;background:transparent;border-radius:4px;flex-shrink:0;padding:4px 8px}
|
|
66441
|
+
@media (max-width:640px){.${terminalClass.list}{padding-left:6px;padding-right:6px}.${terminalClass.listActions}{margin-left:2px;padding-left:2px}.${terminalClass.toolsPanel}{padding-left:6px;padding-right:6px}}
|
|
63730
66442
|
`;
|
|
63731
66443
|
//#endregion
|
|
63732
66444
|
//#region node_modules/.pnpm/@xterm+addon-fit@0.11.0/node_modules/@xterm/addon-fit/lib/addon-fit.js
|
|
@@ -76779,6 +79491,12 @@ window.__ModuleLoader__.load({
|
|
|
76779
79491
|
const TERMINAL_TOUCH_MOMENTUM_MAX_DURATION_MS = 3600;
|
|
76780
79492
|
const TERMINAL_TOUCH_MOMENTUM_MAX_IDLE_FRAMES = 3;
|
|
76781
79493
|
const TERMINAL_TOUCH_MOMENTUM_RELEASE_IDLE_MS = 100;
|
|
79494
|
+
/** 视图夺回终端尺寸的最小间隔,避免可见性抖动时反复重发。 */
|
|
79495
|
+
const TERMINAL_SIZE_CLAIM_INTERVAL_MS = 1e3;
|
|
79496
|
+
/** 终端文本与可视容器边缘之间至少保留 5px 的安全距离。 */
|
|
79497
|
+
const TERMINAL_CONTENT_EDGE_GAP = 5;
|
|
79498
|
+
/** 终端滚动条停止滚动后的自动隐藏延迟。 */
|
|
79499
|
+
const TERMINAL_SCROLLBAR_HIDE_DELAY_MS = 3e3;
|
|
76782
79500
|
/** 使用与 DSH 内置终端相同的布局、状态条和 xterm 默认参数。 */
|
|
76783
79501
|
function CodingNsXtermView({ view, settings, themeRevision, onNewTerminal, active = true, visible = true, t: injectedTranslator }) {
|
|
76784
79502
|
const t = injectedTranslator ?? resolveCodingNsTranslator();
|
|
@@ -76787,6 +79505,8 @@ window.__ModuleLoader__.load({
|
|
|
76787
79505
|
const fitRef = (0, react.useRef)(null);
|
|
76788
79506
|
const scheduleReflowRef = (0, react.useRef)(null);
|
|
76789
79507
|
const lastRevision = (0, react.useRef)(0);
|
|
79508
|
+
const sizeClaimAtRef = (0, react.useRef)(0);
|
|
79509
|
+
const previousActiveRef = (0, react.useRef)(active);
|
|
76790
79510
|
const state = (0, react.useSyncExternalStore)(view.state.subscribe.bind(view.state), view.state.getSnapshot.bind(view.state));
|
|
76791
79511
|
const appearance = (0, react.useSyncExternalStore)(settings.subscribe.bind(settings), settings.getSnapshot.bind(settings)).value?.terminalEnhancement.appearance ?? DEFAULT_TERMINAL_ENHANCEMENT_SETTINGS.appearance;
|
|
76792
79512
|
const hasTerminal = state.info !== void 0;
|
|
@@ -76944,11 +79664,34 @@ window.__ModuleLoader__.load({
|
|
|
76944
79664
|
};
|
|
76945
79665
|
const xtermRoot = terminal.element;
|
|
76946
79666
|
if (xtermRoot === void 0) return;
|
|
76947
|
-
const scrollTarget = xtermRoot?.querySelector(".xterm-
|
|
79667
|
+
const scrollTarget = xtermRoot?.querySelector(".xterm-scrollable-element") ?? xtermRoot ?? container;
|
|
76948
79668
|
const interactionTarget = xtermRoot ?? container;
|
|
76949
|
-
|
|
79669
|
+
let scrollbarHideTimer;
|
|
79670
|
+
let lastViewportY = terminal.buffer.active.viewportY;
|
|
79671
|
+
const hideScrollbar = () => {
|
|
79672
|
+
if (scrollbarHideTimer !== void 0) {
|
|
79673
|
+
window.clearTimeout(scrollbarHideTimer);
|
|
79674
|
+
scrollbarHideTimer = void 0;
|
|
79675
|
+
}
|
|
79676
|
+
xtermRoot.dataset.codingnsScrollbar = "hidden";
|
|
79677
|
+
};
|
|
79678
|
+
const revealScrollbar = () => {
|
|
79679
|
+
if (terminal.buffer.active.baseY <= 0) {
|
|
79680
|
+
hideScrollbar();
|
|
79681
|
+
return;
|
|
79682
|
+
}
|
|
79683
|
+
xtermRoot.dataset.codingnsScrollbar = "visible";
|
|
79684
|
+
if (scrollbarHideTimer !== void 0) window.clearTimeout(scrollbarHideTimer);
|
|
79685
|
+
scrollbarHideTimer = window.setTimeout(() => {
|
|
79686
|
+
scrollbarHideTimer = void 0;
|
|
79687
|
+
xtermRoot.dataset.codingnsScrollbar = "hidden";
|
|
79688
|
+
}, TERMINAL_SCROLLBAR_HIDE_DELAY_MS);
|
|
79689
|
+
};
|
|
79690
|
+
const syncScrollbarState = () => {
|
|
76950
79691
|
const hasScrollback = terminal.buffer.active.baseY > 0;
|
|
76951
79692
|
xtermRoot.dataset.codingnsScrollback = hasScrollback ? "true" : "false";
|
|
79693
|
+
if (!hasScrollback) hideScrollbar();
|
|
79694
|
+
else if (xtermRoot.dataset.codingnsScrollbar === void 0) xtermRoot.dataset.codingnsScrollbar = "hidden";
|
|
76952
79695
|
debugInfo("codingns4dsh: client terminal scrollbar state", {
|
|
76953
79696
|
terminalId: view.id,
|
|
76954
79697
|
hasScrollback,
|
|
@@ -76956,7 +79699,7 @@ window.__ModuleLoader__.load({
|
|
|
76956
79699
|
viewportY: terminal.buffer.active.viewportY
|
|
76957
79700
|
});
|
|
76958
79701
|
};
|
|
76959
|
-
|
|
79702
|
+
syncScrollbarState();
|
|
76960
79703
|
scrollTarget.style.touchAction = "pan-y";
|
|
76961
79704
|
scrollTarget.style.overscrollBehavior = "contain";
|
|
76962
79705
|
if ("webkitOverflowScrolling" in scrollTarget.style) scrollTarget.style.webkitOverflowScrolling = "touch";
|
|
@@ -76965,7 +79708,10 @@ window.__ModuleLoader__.load({
|
|
|
76965
79708
|
interactionTarget.addEventListener("touchend", touchEnd, { passive: true });
|
|
76966
79709
|
interactionTarget.addEventListener("touchcancel", touchCancel, { passive: true });
|
|
76967
79710
|
const scroll = terminal.onScroll((viewportY) => {
|
|
76968
|
-
|
|
79711
|
+
const didScroll = viewportY !== lastViewportY;
|
|
79712
|
+
lastViewportY = viewportY;
|
|
79713
|
+
syncScrollbarState();
|
|
79714
|
+
if (didScroll) revealScrollbar();
|
|
76969
79715
|
debugInfo("codingns4dsh: client terminal viewport scroll", {
|
|
76970
79716
|
terminalId: view.id,
|
|
76971
79717
|
viewportY,
|
|
@@ -76977,9 +79723,10 @@ window.__ModuleLoader__.load({
|
|
|
76977
79723
|
if (!preserveHostTitle) view.rename(value);
|
|
76978
79724
|
});
|
|
76979
79725
|
const measure = () => {
|
|
79726
|
+
syncTerminalViewport(host);
|
|
76980
79727
|
if (host.clientWidth === 0 || host.clientHeight === 0) return;
|
|
76981
79728
|
fitTerminal(terminal, fit, view);
|
|
76982
|
-
|
|
79729
|
+
syncScrollbarState();
|
|
76983
79730
|
};
|
|
76984
79731
|
let measureFrame;
|
|
76985
79732
|
const scheduleMeasure = () => {
|
|
@@ -77009,17 +79756,20 @@ window.__ModuleLoader__.load({
|
|
|
77009
79756
|
resize.observe(host);
|
|
77010
79757
|
resize.observe(container);
|
|
77011
79758
|
const visualViewport = window.visualViewport;
|
|
77012
|
-
const
|
|
77013
|
-
visualViewport?.addEventListener("resize",
|
|
77014
|
-
|
|
79759
|
+
const handleViewportChange = () => schedulePostAttachReflow();
|
|
79760
|
+
visualViewport?.addEventListener("resize", handleViewportChange);
|
|
79761
|
+
visualViewport?.addEventListener("scroll", handleViewportChange);
|
|
79762
|
+
window.addEventListener("resize", handleViewportChange);
|
|
77015
79763
|
schedulePostAttachReflow();
|
|
77016
79764
|
return () => {
|
|
77017
79765
|
resize.disconnect();
|
|
77018
|
-
visualViewport?.removeEventListener("resize",
|
|
77019
|
-
|
|
79766
|
+
visualViewport?.removeEventListener("resize", handleViewportChange);
|
|
79767
|
+
visualViewport?.removeEventListener("scroll", handleViewportChange);
|
|
79768
|
+
window.removeEventListener("resize", handleViewportChange);
|
|
77020
79769
|
if (measureFrame !== void 0) window.cancelAnimationFrame(measureFrame);
|
|
77021
79770
|
if (reflowFrame !== void 0) window.cancelAnimationFrame(reflowFrame);
|
|
77022
79771
|
if (reflowTimer !== void 0) window.clearTimeout(reflowTimer);
|
|
79772
|
+
if (scrollbarHideTimer !== void 0) window.clearTimeout(scrollbarHideTimer);
|
|
77023
79773
|
scheduleReflowRef.current = null;
|
|
77024
79774
|
input.dispose();
|
|
77025
79775
|
scroll.dispose();
|
|
@@ -77032,17 +79782,24 @@ window.__ModuleLoader__.load({
|
|
|
77032
79782
|
terminal.dispose();
|
|
77033
79783
|
terminalRef.current = null;
|
|
77034
79784
|
fitRef.current = null;
|
|
79785
|
+
host.style.height = "100%";
|
|
77035
79786
|
root.replaceChildren();
|
|
77036
79787
|
};
|
|
77037
79788
|
}, [hasTerminal, view]);
|
|
77038
79789
|
(0, react.useEffect)(() => {
|
|
79790
|
+
const becameActive = active && !previousActiveRef.current;
|
|
79791
|
+
previousActiveRef.current = active;
|
|
77039
79792
|
const terminal = terminalRef.current;
|
|
77040
79793
|
const host = hostRef.current;
|
|
77041
79794
|
if (terminal === null || host === null) return;
|
|
77042
79795
|
applyAppearance(terminal, appearance, host, state.environment?.scrollback ?? 1e3);
|
|
77043
79796
|
terminal.options.disableStdin = !state.writable;
|
|
79797
|
+
if (active) syncTerminalViewport(host);
|
|
77044
79798
|
if (state.writable && host.clientWidth > 0 && host.clientHeight > 0) {
|
|
77045
|
-
|
|
79799
|
+
const now = Date.now();
|
|
79800
|
+
const claim = becameActive && now - sizeClaimAtRef.current >= TERMINAL_SIZE_CLAIM_INTERVAL_MS;
|
|
79801
|
+
if (claim) sizeClaimAtRef.current = now;
|
|
79802
|
+
fitTerminal(terminal, fitRef.current, view, claim ? { force: true } : void 0);
|
|
77046
79803
|
terminal.focus();
|
|
77047
79804
|
} else if (state.info !== void 0) terminal.resize(state.info.cols, state.info.rows);
|
|
77048
79805
|
}, [
|
|
@@ -77055,6 +79812,26 @@ window.__ModuleLoader__.load({
|
|
|
77055
79812
|
themeRevision,
|
|
77056
79813
|
view
|
|
77057
79814
|
]);
|
|
79815
|
+
(0, react.useEffect)(() => {
|
|
79816
|
+
if (!active || !hasTerminal) return;
|
|
79817
|
+
const onVisibilityChange = () => {
|
|
79818
|
+
if (document.visibilityState !== "visible") return;
|
|
79819
|
+
const terminal = terminalRef.current;
|
|
79820
|
+
const host = hostRef.current;
|
|
79821
|
+
if (terminal === null || host === null) return;
|
|
79822
|
+
if (host.clientWidth === 0 || host.clientHeight === 0) return;
|
|
79823
|
+
const now = Date.now();
|
|
79824
|
+
if (now - sizeClaimAtRef.current < TERMINAL_SIZE_CLAIM_INTERVAL_MS) return;
|
|
79825
|
+
sizeClaimAtRef.current = now;
|
|
79826
|
+
fitTerminal(terminal, fitRef.current, view, { force: true });
|
|
79827
|
+
};
|
|
79828
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
79829
|
+
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
79830
|
+
}, [
|
|
79831
|
+
active,
|
|
79832
|
+
hasTerminal,
|
|
79833
|
+
view
|
|
79834
|
+
]);
|
|
77058
79835
|
(0, react.useEffect)(() => {
|
|
77059
79836
|
const terminal = terminalRef.current;
|
|
77060
79837
|
const host = hostRef.current;
|
|
@@ -77072,8 +79849,12 @@ window.__ModuleLoader__.load({
|
|
|
77072
79849
|
fitTerminal(terminal, fitRef.current, view);
|
|
77073
79850
|
scheduleReflowRef.current?.();
|
|
77074
79851
|
}
|
|
77075
|
-
if (terminal.element !== void 0)
|
|
77076
|
-
|
|
79852
|
+
if (terminal.element !== void 0) {
|
|
79853
|
+
const hasScrollback = terminal.buffer.active.baseY > 0;
|
|
79854
|
+
terminal.element.dataset.codingnsScrollback = hasScrollback ? "true" : "false";
|
|
79855
|
+
if (!hasScrollback) terminal.element.dataset.codingnsScrollbar = "hidden";
|
|
79856
|
+
}
|
|
79857
|
+
const viewport = terminal.element?.querySelector(".xterm-scrollable-element");
|
|
77077
79858
|
debugInfo("codingns4dsh: client terminal frame rendered", {
|
|
77078
79859
|
terminalId: view.id,
|
|
77079
79860
|
frameType: render.frame.type,
|
|
@@ -77121,7 +79902,7 @@ window.__ModuleLoader__.load({
|
|
|
77121
79902
|
variant: "outline",
|
|
77122
79903
|
size: "sm",
|
|
77123
79904
|
onClick: () => {
|
|
77124
|
-
view.refresh();
|
|
79905
|
+
view.refresh({ force: true });
|
|
77125
79906
|
}
|
|
77126
79907
|
}, state.phase === "disconnected" ? t("terminalView.reconnect") : t("terminal.retry")) : null, lost ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
77127
79908
|
variant: "outline",
|
|
@@ -77189,31 +79970,80 @@ window.__ModuleLoader__.load({
|
|
|
77189
79970
|
selectionInactiveBackground: foreground
|
|
77190
79971
|
};
|
|
77191
79972
|
}
|
|
77192
|
-
function fitTerminal(terminal, fit, view) {
|
|
79973
|
+
function fitTerminal(terminal, fit, view, options) {
|
|
77193
79974
|
if (fit === null) return;
|
|
77194
79975
|
const dimensions = resolveTerminalDimensions(terminal, fit);
|
|
77195
79976
|
if (dimensions === void 0) return;
|
|
77196
79977
|
if (terminal.cols !== dimensions.cols || terminal.rows !== dimensions.rows) terminal.resize(dimensions.cols, dimensions.rows);
|
|
77197
|
-
view.resize(terminal.cols, terminal.rows);
|
|
79978
|
+
view.resize(terminal.cols, terminal.rows, options);
|
|
79979
|
+
}
|
|
79980
|
+
/**
|
|
79981
|
+
* 让终端宿主节点的底边停在当前可视视口之内。
|
|
79982
|
+
*
|
|
79983
|
+
* 移动端软键盘出现时,布局视口通常仍保持原高度,只有 `visualViewport.height`
|
|
79984
|
+
* 变小。只调用 FitAddon 不够,因为它读取到的父节点高度没有变化;这里按屏幕
|
|
79985
|
+
* 实际可见底边收缩宿主,键盘收起后再恢复 `100%`。
|
|
79986
|
+
*/
|
|
79987
|
+
function syncTerminalViewport(host) {
|
|
79988
|
+
const screen = host.parentElement;
|
|
79989
|
+
if (screen === null) return;
|
|
79990
|
+
const hostRect = host.getBoundingClientRect();
|
|
79991
|
+
const screenRect = screen.getBoundingClientRect();
|
|
79992
|
+
if (screenRect.width <= 0 || screenRect.height <= 0 || hostRect.width <= 0) return;
|
|
79993
|
+
const screenStyle = getComputedStyle(screen);
|
|
79994
|
+
const contentBottom = screenRect.bottom - parseCssPixels(screenStyle.paddingBottom) - parseCssPixels(screenStyle.borderBottomWidth);
|
|
79995
|
+
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
|
79996
|
+
if (!Number.isFinite(contentBottom) || !Number.isFinite(viewportHeight) || viewportHeight <= 0) return;
|
|
79997
|
+
const visibleBottom = Math.min(contentBottom, viewportHeight);
|
|
79998
|
+
const normalHeight = Math.max(1, Math.floor(contentBottom - hostRect.top));
|
|
79999
|
+
if (visibleBottom >= contentBottom - 1) {
|
|
80000
|
+
if (host.style.height !== "100%") host.style.height = "100%";
|
|
80001
|
+
return;
|
|
80002
|
+
}
|
|
80003
|
+
const visibleHeight = Math.max(1, Math.floor(visibleBottom - hostRect.top - 1));
|
|
80004
|
+
const nextHeight = `${Math.min(normalHeight, visibleHeight)}px`;
|
|
80005
|
+
if (host.style.height !== nextHeight) host.style.height = nextHeight;
|
|
77198
80006
|
}
|
|
77199
80007
|
function resolveTerminalDimensions(terminal, fit) {
|
|
77200
80008
|
const fallback = fit.proposeDimensions();
|
|
77201
80009
|
const root = terminal.element;
|
|
77202
80010
|
if (root === void 0 || root.clientWidth <= 0 || root.clientHeight <= 0) return fallback;
|
|
77203
|
-
const
|
|
77204
|
-
|
|
77205
|
-
|
|
77206
|
-
|
|
77207
|
-
|
|
80011
|
+
const cell = resolveRendererCell(terminal);
|
|
80012
|
+
if (cell === void 0) return fallback === void 0 ? void 0 : {
|
|
80013
|
+
...fallback,
|
|
80014
|
+
cols: Math.max(2, fallback.cols - 1)
|
|
80015
|
+
};
|
|
80016
|
+
const HOST_PADDING_LEFT = 8;
|
|
80017
|
+
const HOST_PADDING_RIGHT = 8;
|
|
77208
80018
|
const computed = getComputedStyle(root);
|
|
77209
|
-
const paddingX = parseCssPixels(computed.paddingLeft) + parseCssPixels(computed.paddingRight);
|
|
77210
80019
|
const paddingY = parseCssPixels(computed.paddingTop) + parseCssPixels(computed.paddingBottom);
|
|
77211
|
-
const
|
|
80020
|
+
const rootRect = root.getBoundingClientRect();
|
|
80021
|
+
const scrollable = root.querySelector(".xterm-scrollable-element");
|
|
80022
|
+
const scrollbarRect = (terminal.options.scrollback === 0 ? void 0 : scrollable?.querySelector(".scrollbar.vertical"))?.getBoundingClientRect();
|
|
80023
|
+
scrollable?.getBoundingClientRect();
|
|
80024
|
+
const scrollbarInset = scrollbarRect !== void 0 && scrollbarRect.width > 0 ? Math.max(0, rootRect.right - scrollbarRect.left) : 0;
|
|
80025
|
+
const safeEdgeGap = Math.max(0, TERMINAL_CONTENT_EDGE_GAP - scrollbarInset);
|
|
80026
|
+
const width = rootRect.width - HOST_PADDING_LEFT - HOST_PADDING_RIGHT - scrollbarInset - safeEdgeGap;
|
|
77212
80027
|
const height = root.clientHeight - paddingY;
|
|
77213
|
-
if (
|
|
80028
|
+
if (width <= 0 || height <= 0) return fallback;
|
|
80029
|
+
return {
|
|
80030
|
+
cols: Math.max(2, Math.floor(width / cell.width)),
|
|
80031
|
+
rows: Math.max(1, Math.floor(height / cell.height))
|
|
80032
|
+
};
|
|
80033
|
+
}
|
|
80034
|
+
/**
|
|
80035
|
+
* 读取 xterm 渲染服务的单元格尺寸。`.xterm-char-measure-element` 会被 DOM renderer
|
|
80036
|
+
* 的宽度缓存复用,其文本内容会随最近一次测量的字符变化(例如中文全角字符)。
|
|
80037
|
+
* 用它除以 32 推算列宽会把列数按该字符宽度计算,宽字符时列数近乎减半,表现为
|
|
80038
|
+
* 文本提前换行、右侧留出大片空白。这里改用官方 FitAddon 依赖的 renderService
|
|
80039
|
+
* cell 尺寸(CharSizeService 固定按 32 个 'W' 测量),列宽不再受终端内容影响。
|
|
80040
|
+
*/
|
|
80041
|
+
function resolveRendererCell(terminal) {
|
|
80042
|
+
const cell = (terminal._core?._renderService?.dimensions)?.css?.cell;
|
|
80043
|
+
if (cell === void 0 || !(cell.width > 0) || !(cell.height > 0)) return void 0;
|
|
77214
80044
|
return {
|
|
77215
|
-
|
|
77216
|
-
|
|
80045
|
+
width: cell.width,
|
|
80046
|
+
height: cell.height
|
|
77217
80047
|
};
|
|
77218
80048
|
}
|
|
77219
80049
|
function parseCssPixels(value) {
|
|
@@ -77239,28 +80069,37 @@ window.__ModuleLoader__.load({
|
|
|
77239
80069
|
return normalized === "" || normalized === "rgba(0, 0, 0, 0)" ? fallback : normalized;
|
|
77240
80070
|
}
|
|
77241
80071
|
const terminalHostStyle = {
|
|
80072
|
+
boxSizing: "border-box",
|
|
77242
80073
|
width: "100%",
|
|
77243
80074
|
height: "100%",
|
|
77244
80075
|
minWidth: 0,
|
|
77245
80076
|
minHeight: 0,
|
|
80077
|
+
paddingLeft: 8,
|
|
80078
|
+
paddingRight: 8,
|
|
77246
80079
|
color: "var(--dsw-alias-label-primary)",
|
|
77247
80080
|
background: "var(--dsw-alias-bg-base)"
|
|
77248
80081
|
};
|
|
77249
80082
|
const shadowCss = `${_codingns4dsh_xterm_css_default}
|
|
77250
80083
|
:host{display:block;width:100%;height:100%;min-width:0;min-height:0;color:inherit;background:inherit}
|
|
77251
|
-
.codingns-xterm{width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
|
|
77252
|
-
.xterm{width:100%;height:100%;min-width:0;min-height:0}
|
|
77253
|
-
.xterm-scrollable-element{min-width:0;max-width:100%}
|
|
77254
|
-
|
|
77255
|
-
.xterm-viewport
|
|
77256
|
-
.xterm-viewport::-webkit-scrollbar
|
|
77257
|
-
.xterm-viewport::-webkit-scrollbar-
|
|
77258
|
-
.xterm-viewport::-webkit-scrollbar-thumb
|
|
77259
|
-
.xterm
|
|
77260
|
-
.xterm
|
|
77261
|
-
.xterm[data-codingns-
|
|
77262
|
-
.xterm
|
|
80084
|
+
.codingns-xterm{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
|
|
80085
|
+
.xterm{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0}
|
|
80086
|
+
.xterm-scrollable-element{box-sizing:border-box;width:100%;min-width:0;max-width:100%}
|
|
80087
|
+
/* xterm 6 已由 xterm-scrollable-element 接管滚动;隐藏兼容 viewport,避免产生第二个滚动条槽位。 */
|
|
80088
|
+
.xterm-viewport{background:var(--dsw-alias-bg-base);touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;overflow:hidden;scrollbar-gutter:stable;scrollbar-width:none}
|
|
80089
|
+
.xterm-viewport::-webkit-scrollbar{display:none}
|
|
80090
|
+
.xterm-viewport::-webkit-scrollbar-track{display:none}
|
|
80091
|
+
.xterm-viewport::-webkit-scrollbar-thumb{display:none}
|
|
80092
|
+
.xterm-viewport::-webkit-scrollbar-thumb:hover{display:none}
|
|
80093
|
+
.xterm .xterm-scrollable-element>.scrollbar.vertical{width:6px!important;right:2px!important;pointer-events:none!important;opacity:0!important;background:transparent!important;transition:opacity 180ms ease!important}
|
|
80094
|
+
.xterm[data-codingns-scrollbar="visible"] .xterm-scrollable-element>.scrollbar.vertical{opacity:.7!important;pointer-events:auto!important}
|
|
80095
|
+
.xterm .xterm-scrollable-element>.scrollbar.vertical>.slider{width:6px!important;left:0!important;min-height:24px;border-radius:999px;background:rgba(170,178,190,.72)!important;box-shadow:0 1px 4px rgba(0,0,0,.28);transition:background-color 180ms ease,box-shadow 180ms ease!important}
|
|
80096
|
+
.xterm[data-codingns-scrollbar="visible"] .xterm-scrollable-element>.scrollbar.vertical>.slider:hover{background:rgba(224,229,237,.9)!important;box-shadow:0 1px 6px rgba(0,0,0,.4)}
|
|
77263
80097
|
.xterm .xterm-scrollable-element>.scrollbar.horizontal{display:none!important}
|
|
80098
|
+
/* xterm 的字宽缓存会把单个字符重复 32 次测量宽度,Chrome 对连续的全角标点默认做
|
|
80099
|
+
标点压缩(「。」从 13px 压到 6.72px),导致 letter-spacing 在每处标点被多补约
|
|
80100
|
+
6.3px,行内标点密集时渲染内容向右累积溢出网格宽度,行尾一两个字被裁剪。
|
|
80101
|
+
space-all 关闭标点压缩,让字宽测量与单字实际渲染一致,全角标点按两格渲染。 */
|
|
80102
|
+
.xterm-width-cache-measure-container,.xterm-rows{text-spacing-trim:space-all}
|
|
77264
80103
|
`;
|
|
77265
80104
|
//#endregion
|
|
77266
80105
|
//#region src/client/terminal/ui.ts
|
|
@@ -77354,8 +80193,12 @@ window.__ModuleLoader__.load({
|
|
|
77354
80193
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
77355
80194
|
const [creating, setCreating] = (0, react.useState)(false);
|
|
77356
80195
|
const [createError, setCreateError] = (0, react.useState)();
|
|
80196
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
80197
|
+
const [toolsOpen, setToolsOpen] = (0, react.useState)(false);
|
|
80198
|
+
const [heldModifiers, setHeldModifiers] = (0, react.useState)(EMPTY_HELD_MODIFIERS);
|
|
77357
80199
|
/** React 状态更新前可能收到连续点击;用同步锁保证只发出一个 create 请求。 */
|
|
77358
80200
|
const creatingRef = (0, react.useRef)(false);
|
|
80201
|
+
const refreshingRef = (0, react.useRef)(false);
|
|
77359
80202
|
const autoCreatedFor = (0, react.useRef)(void 0);
|
|
77360
80203
|
const reloadSequence = (0, react.useRef)(0);
|
|
77361
80204
|
const reload = (0, react.useCallback)(async () => {
|
|
@@ -77427,10 +80270,32 @@ window.__ModuleLoader__.load({
|
|
|
77427
80270
|
terminals.length
|
|
77428
80271
|
]);
|
|
77429
80272
|
const activeId = terminals.find((item) => item.id === selectedId)?.id ?? terminals[0]?.id;
|
|
77430
|
-
const
|
|
80273
|
+
const activeView = activeId === void 0 ? void 0 : webTerminals.viewForTerminal(String(sessionId), activeId, terminals.find((item) => item.id === activeId)?.shell.path);
|
|
80274
|
+
const activePlatform = (0, react.useSyncExternalStore)(activeView?.state.subscribe.bind(activeView.state) ?? noSubscribe, () => activeView?.state.getSnapshot().environment?.platform, () => activeView?.state.getSnapshot().environment?.platform);
|
|
80275
|
+
const refreshTerminal = (0, react.useCallback)(async () => {
|
|
80276
|
+
if (refreshingRef.current) return;
|
|
80277
|
+
refreshingRef.current = true;
|
|
80278
|
+
setRefreshing(true);
|
|
80279
|
+
try {
|
|
80280
|
+
await Promise.all([reload(), activeView?.refresh({ force: true })]);
|
|
80281
|
+
} catch (cause) {
|
|
80282
|
+
debugWarn("codingns4dsh: client terminal toolbar refresh failed", {
|
|
80283
|
+
sessionId: String(sessionId),
|
|
80284
|
+
error: messageOf(cause)
|
|
80285
|
+
});
|
|
80286
|
+
} finally {
|
|
80287
|
+
refreshingRef.current = false;
|
|
80288
|
+
setRefreshing(false);
|
|
80289
|
+
}
|
|
80290
|
+
}, [
|
|
80291
|
+
activeView,
|
|
80292
|
+
reload,
|
|
80293
|
+
sessionId
|
|
80294
|
+
]);
|
|
80295
|
+
const navigation = (0, react.createElement)("div", { className: terminalClass.listDock }, (0, react.createElement)("nav", {
|
|
77431
80296
|
className: terminalClass.list,
|
|
77432
80297
|
"aria-label": t("terminal.title")
|
|
77433
|
-
},
|
|
80298
|
+
}, (0, react.createElement)("div", { className: terminalClass.listTabs }, ...terminals.map((item) => (0, react.createElement)(TerminalListRow, {
|
|
77434
80299
|
key: item.id,
|
|
77435
80300
|
item,
|
|
77436
80301
|
selected: item.id === activeId,
|
|
@@ -77454,7 +80319,37 @@ window.__ModuleLoader__.load({
|
|
|
77454
80319
|
onClick: () => {
|
|
77455
80320
|
createNewTerminal();
|
|
77456
80321
|
}
|
|
77457
|
-
})
|
|
80322
|
+
})), (0, react.createElement)("div", { className: terminalClass.listActions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
80323
|
+
variant: "ghost",
|
|
80324
|
+
size: "sm",
|
|
80325
|
+
className: terminalClass.refreshButton,
|
|
80326
|
+
icon: (0, react.createElement)(resolveRefreshIcon(), { size: 16 }),
|
|
80327
|
+
"aria-label": t("terminal.refresh"),
|
|
80328
|
+
title: t("terminal.refresh"),
|
|
80329
|
+
disabled: refreshing,
|
|
80330
|
+
onClick: () => {
|
|
80331
|
+
refreshTerminal();
|
|
80332
|
+
}
|
|
80333
|
+
}), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
80334
|
+
variant: "ghost",
|
|
80335
|
+
size: "sm",
|
|
80336
|
+
className: terminalClass.toolsButton,
|
|
80337
|
+
icon: (0, react.createElement)(resolveToolIcon(), { size: 16 }),
|
|
80338
|
+
"aria-label": t("terminal.tools"),
|
|
80339
|
+
title: t("terminal.tools"),
|
|
80340
|
+
"aria-haspopup": "true",
|
|
80341
|
+
"aria-expanded": toolsOpen,
|
|
80342
|
+
onClick: () => setToolsOpen((value) => !value)
|
|
80343
|
+
}))), toolsOpen ? (0, react.createElement)(TerminalToolsPanel, {
|
|
80344
|
+
view: activeView,
|
|
80345
|
+
platform: activePlatform,
|
|
80346
|
+
heldModifiers,
|
|
80347
|
+
onToggleModifier: (modifier) => setHeldModifiers((current) => ({
|
|
80348
|
+
...current,
|
|
80349
|
+
[modifier]: !current[modifier]
|
|
80350
|
+
})),
|
|
80351
|
+
t
|
|
80352
|
+
}) : null);
|
|
77458
80353
|
const content = (0, react.createElement)("div", { className: terminalClass.content }, terminals.length === 0 ? (0, react.createElement)("div", {
|
|
77459
80354
|
role: "status",
|
|
77460
80355
|
className: terminalClass.empty
|
|
@@ -77478,6 +80373,88 @@ window.__ModuleLoader__.load({
|
|
|
77478
80373
|
})));
|
|
77479
80374
|
return (0, react.createElement)("div", { className: `${terminalClass.aggregateRoot} ${terminalClass.content}` }, navigation, content);
|
|
77480
80375
|
}
|
|
80376
|
+
const EMPTY_HELD_MODIFIERS = {
|
|
80377
|
+
ctrl: false,
|
|
80378
|
+
alt: false,
|
|
80379
|
+
win: false
|
|
80380
|
+
};
|
|
80381
|
+
function TerminalToolsPanel({ view, platform, heldModifiers, onToggleModifier, t }) {
|
|
80382
|
+
const modifier = (kind, label) => (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
80383
|
+
key: kind,
|
|
80384
|
+
variant: "toolbar",
|
|
80385
|
+
size: "sm",
|
|
80386
|
+
className: `${terminalClass.toolAction} ${heldModifiers[kind] ? terminalClass.toolModifierActive : ""}`,
|
|
80387
|
+
"aria-label": label,
|
|
80388
|
+
title: label,
|
|
80389
|
+
"aria-pressed": heldModifiers[kind],
|
|
80390
|
+
disabled: view === void 0,
|
|
80391
|
+
onClick: () => onToggleModifier(kind)
|
|
80392
|
+
}, (0, react.createElement)(resolveShortcutKeys(), { keys: [terminalModifierKey(kind, platform)] }));
|
|
80393
|
+
const command = (kind, label) => (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
80394
|
+
key: kind,
|
|
80395
|
+
variant: "toolbar",
|
|
80396
|
+
size: "sm",
|
|
80397
|
+
className: terminalClass.toolAction,
|
|
80398
|
+
"aria-label": label,
|
|
80399
|
+
title: label,
|
|
80400
|
+
disabled: view === void 0,
|
|
80401
|
+
onClick: () => {
|
|
80402
|
+
if (view !== void 0) view.write(terminalShortcutData(kind, heldModifiers));
|
|
80403
|
+
}
|
|
80404
|
+
}, terminalToolControl(kind));
|
|
80405
|
+
return (0, react.createElement)("div", {
|
|
80406
|
+
className: terminalClass.toolsPanel,
|
|
80407
|
+
role: "toolbar",
|
|
80408
|
+
"aria-label": t("terminal.tools")
|
|
80409
|
+
}, modifier("ctrl", terminalModifierLabel("ctrl", platform, t)), modifier("alt", terminalModifierLabel("alt", platform, t)), modifier("win", terminalModifierLabel("win", platform, t)), command("tab", t("terminal.shortcutTab")), command("escape", t("terminal.shortcutEscape")), command("enter", t("terminal.shortcutEnter")), command("backspace", t("terminal.shortcutBackspace")), command("up", t("terminal.shortcutUp")), command("down", t("terminal.shortcutDown")), command("left", t("terminal.shortcutLeft")), command("right", t("terminal.shortcutRight")), command("c", t("terminal.shortcutC")), command("d", t("terminal.shortcutD")), command("l", t("terminal.shortcutL")), command("z", t("terminal.shortcutZ")));
|
|
80410
|
+
}
|
|
80411
|
+
function terminalModifierKey(modifier, platform) {
|
|
80412
|
+
if (modifier === "alt" && platform === "darwin") return "Option";
|
|
80413
|
+
if (modifier !== "win") return modifier === "ctrl" ? "Ctrl" : "Alt";
|
|
80414
|
+
if (platform === "darwin") return "Cmd";
|
|
80415
|
+
if (platform === "linux") return "Super";
|
|
80416
|
+
return "Win";
|
|
80417
|
+
}
|
|
80418
|
+
function terminalModifierLabel(modifier, platform, t) {
|
|
80419
|
+
if (modifier === "ctrl") return t("terminal.holdCtrl");
|
|
80420
|
+
if (modifier === "alt" && platform === "darwin") return t("terminal.holdOption");
|
|
80421
|
+
if (modifier === "win" && platform === "darwin") return t("terminal.holdCommand");
|
|
80422
|
+
if (modifier === "win" && platform === "linux") return t("terminal.holdSuper");
|
|
80423
|
+
if (modifier === "alt") return t("terminal.holdAlt");
|
|
80424
|
+
return t("terminal.holdWin");
|
|
80425
|
+
}
|
|
80426
|
+
function terminalToolControl(command) {
|
|
80427
|
+
if (command === "up" || command === "down" || command === "left" || command === "right") return (0, react.createElement)(resolveTerminalArrowIcon(command), { size: 16 });
|
|
80428
|
+
return (0, react.createElement)(resolveShortcutKeys(), { keys: [{
|
|
80429
|
+
tab: "Tab",
|
|
80430
|
+
escape: "Esc",
|
|
80431
|
+
enter: "Enter",
|
|
80432
|
+
backspace: "⌫",
|
|
80433
|
+
c: "C",
|
|
80434
|
+
d: "D",
|
|
80435
|
+
l: "L",
|
|
80436
|
+
z: "Z"
|
|
80437
|
+
}[command]] });
|
|
80438
|
+
}
|
|
80439
|
+
function terminalShortcutData(command, modifiers) {
|
|
80440
|
+
let value = {
|
|
80441
|
+
tab: " ",
|
|
80442
|
+
escape: "\x1B",
|
|
80443
|
+
enter: "\r",
|
|
80444
|
+
backspace: "",
|
|
80445
|
+
up: "\x1B[A",
|
|
80446
|
+
down: "\x1B[B",
|
|
80447
|
+
left: "\x1B[D",
|
|
80448
|
+
right: "\x1B[C",
|
|
80449
|
+
c: "c",
|
|
80450
|
+
d: "d",
|
|
80451
|
+
l: "l",
|
|
80452
|
+
z: "z"
|
|
80453
|
+
}[command];
|
|
80454
|
+
if (modifiers.ctrl && /^[a-z]$/u.test(command)) value = String.fromCharCode(command.charCodeAt(0) - 96);
|
|
80455
|
+
if (modifiers.alt || modifiers.win) value = `\x1b${value}`;
|
|
80456
|
+
return value;
|
|
80457
|
+
}
|
|
77481
80458
|
function TerminalTitle({ useTabInfo, locale }) {
|
|
77482
80459
|
const t = useCodingNsTranslator(locale);
|
|
77483
80460
|
useTabInfo();
|
|
@@ -83840,6 +86817,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
83840
86817
|
const environmentSchema = object({
|
|
83841
86818
|
cwd: string().readonly(),
|
|
83842
86819
|
workspaceId: string().readonly().optional(),
|
|
86820
|
+
platform: union([
|
|
86821
|
+
literal("darwin"),
|
|
86822
|
+
literal("linux"),
|
|
86823
|
+
literal("win32"),
|
|
86824
|
+
literal("unsupported")
|
|
86825
|
+
]).readonly().optional(),
|
|
83843
86826
|
maxInputBytes: number().readonly(),
|
|
83844
86827
|
maxCols: number().readonly(),
|
|
83845
86828
|
maxRows: number().readonly(),
|