@chatcode/cco-better-sidebar 0.18.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +554 -0
- package/README_EN.md +563 -0
- package/assets/logo.svg +1 -0
- package/cordis.patch.yml +79 -0
- package/lib/client-editor.js +55744 -0
- package/lib/client-mermaid.js +208256 -0
- package/lib/client-registry.js +30378 -0
- package/lib/client-terminal.js +16894 -0
- package/lib/client.js +30378 -0
- package/lib/index.js +6897 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/chatcode-update.d.ts +47 -0
- package/lib/types/client/AccountMenu.d.ts +1 -0
- package/lib/types/client/AiRatioSection.d.ts +3 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/ChatcodeGate.d.ts +6 -0
- package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +13 -0
- package/lib/types/client/FenceErrorNotice.d.ts +5 -0
- package/lib/types/client/FileTree.d.ts +41 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +12 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/ReleaseNotesDialog.d.ts +11 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/Sidebar.d.ts +8 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +27 -0
- package/lib/types/client/UpdateDialog.d.ts +10 -0
- package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
- package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +345 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chat-presentation.d.ts +13 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +66 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/intercept.d.ts +44 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +571 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +21 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +52 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +445 -0
- package/lib/types/client/session-pins.d.ts +14 -0
- package/lib/types/client/settings-nav-icon.d.ts +27 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/split-pane.d.ts +38 -0
- package/lib/types/client/state.d.ts +480 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/ui-tweaks/appicons.d.ts +16 -0
- package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
- package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
- package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
- package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
- package/lib/types/client/ui-tweaks/index.d.ts +279 -0
- package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
- package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
- package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/useChatcodeAccount.d.ts +10 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/codingplan.d.ts +37 -0
- package/lib/types/config.d.ts +95 -0
- package/lib/types/context-types.d.ts +540 -0
- package/lib/types/fs-operations.d.ts +30 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/login-route.d.ts +2 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +26 -0
- package/lib/types/prefs-shared.d.ts +257 -0
- package/lib/types/pty-deps.d.ts +79 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/ui-tweaks/archive.d.ts +71 -0
- package/lib/types/ui-tweaks/config.d.ts +186 -0
- package/lib/types/ui-tweaks/git-web.d.ts +43 -0
- package/lib/types/ui-tweaks/git.d.ts +311 -0
- package/lib/types/ui-tweaks/index.d.ts +17 -0
- package/lib/types/ui-tweaks/mcp.d.ts +103 -0
- package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
- package/lib/types/ui-tweaks/web.d.ts +55 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +213 -0
- package/scripts/install.ps1 +310 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/chatcode-update.ts +193 -0
- package/src/client/AccountMenu.tsx +177 -0
- package/src/client/AiRatioSection.module.css +329 -0
- package/src/client/AiRatioSection.tsx +292 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/ChatcodeGate.tsx +35 -0
- package/src/client/CodingPlanModelsSettings.module.css +118 -0
- package/src/client/CodingPlanModelsSettings.tsx +101 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +563 -0
- package/src/client/FenceErrorNotice.tsx +40 -0
- package/src/client/FileTree.tsx +701 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +722 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/ReleaseNotesDialog.tsx +91 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +932 -0
- package/src/client/SideCardSection.tsx +1169 -0
- package/src/client/Sidebar.tsx +1857 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +279 -0
- package/src/client/UpdateDialog.tsx +186 -0
- package/src/client/UpdateSettingsPanel.tsx +29 -0
- package/src/client/UpdateSettingsSection.tsx +22 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/UsageHeatmapSection.module.css +440 -0
- package/src/client/UsageHeatmapSection.tsx +301 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +433 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +275 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chat-presentation.ts +95 -0
- package/src/client/chunk-loader.ts +350 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +170 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +501 -0
- package/src/client/intercept.tsx +172 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +37 -0
- package/src/client/layout.css +156 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +387 -0
- package/src/client/locales-de.ts +372 -0
- package/src/client/locales-fr.ts +379 -0
- package/src/client/locales-hi.ts +386 -0
- package/src/client/locales-id.ts +384 -0
- package/src/client/locales-it.ts +377 -0
- package/src/client/locales-ja.ts +486 -0
- package/src/client/locales-ko.ts +378 -0
- package/src/client/locales-nl.ts +384 -0
- package/src/client/locales-pl.ts +388 -0
- package/src/client/locales-pt.ts +369 -0
- package/src/client/locales-ru.ts +384 -0
- package/src/client/locales-sv.ts +369 -0
- package/src/client/locales-th.ts +386 -0
- package/src/client/locales-tr.ts +386 -0
- package/src/client/locales-vi.ts +386 -0
- package/src/client/locales-zh-HK.ts +401 -0
- package/src/client/locales-zh-MO.ts +401 -0
- package/src/client/locales-zh-TW.ts +401 -0
- package/src/client/locales.ts +1229 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +31 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +79 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +184 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +153 -0
- package/src/client/plugins-viewers.ts +44 -0
- package/src/client/prefs.ts +216 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +873 -0
- package/src/client/session-pins.tsx +334 -0
- package/src/client/settings-nav-icon.ts +55 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +3607 -0
- package/src/client/split-pane.tsx +333 -0
- package/src/client/state.ts +1646 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/ui-tweaks/appicons.ts +24 -0
- package/src/client/ui-tweaks/archive.tsx +281 -0
- package/src/client/ui-tweaks/branding.tsx +232 -0
- package/src/client/ui-tweaks/cachehit.tsx +122 -0
- package/src/client/ui-tweaks/gitbar.tsx +2559 -0
- package/src/client/ui-tweaks/index.tsx +1696 -0
- package/src/client/ui-tweaks/mcp.tsx +582 -0
- package/src/client/ui-tweaks/notifier.ts +514 -0
- package/src/client/ui-tweaks/whale.tsx +171 -0
- package/src/client/upload.ts +187 -0
- package/src/client/useChatcodeAccount.ts +103 -0
- package/src/client/wco.ts +120 -0
- package/src/codingplan.ts +146 -0
- package/src/config.ts +207 -0
- package/src/context-types.ts +559 -0
- package/src/fs-operations.ts +97 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1524 -0
- package/src/invariant.ts +32 -0
- package/src/login-route.ts +26 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +81 -0
- package/src/prefs-shared.ts +303 -0
- package/src/pty-deps.ts +241 -0
- package/src/pty-manager.ts +376 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/ui-tweaks/archive.ts +293 -0
- package/src/ui-tweaks/config.ts +257 -0
- package/src/ui-tweaks/git-web.ts +421 -0
- package/src/ui-tweaks/git.ts +1524 -0
- package/src/ui-tweaks/index.ts +61 -0
- package/src/ui-tweaks/mcp.ts +535 -0
- package/src/ui-tweaks/turn-outcome.ts +105 -0
- package/src/ui-tweaks/web.ts +225 -0
- package/src/wire.ts +110 -0
- package/vendor/addon-fit.js +2 -0
- package/vendor/xterm.css +218 -0
- package/vendor/xterm.js +5 -0
|
@@ -0,0 +1,1696 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-ui-tweaks — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Reads and writes the `ui-tweaks` settings namespace through the same-origin
|
|
5
|
+
* route served by the server half, applies the chosen font size / table style
|
|
6
|
+
* live via a runtime `<style>` element, and renders the Settings
|
|
7
|
+
* panel section that edits them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react'
|
|
11
|
+
import { createPortal } from 'react-dom'
|
|
12
|
+
import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client'
|
|
13
|
+
import type { Context as ClientContext } from '../../context-types.ts'
|
|
14
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
15
|
+
// Type-only import activates the dsh-client-ui-settings slot declarations
|
|
16
|
+
// (`settings.section`) and the client-side settings scope contract.
|
|
17
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
18
|
+
// Type-only import activates the dsh-client-ui-conversation slot declarations
|
|
19
|
+
// (`conversation.input.dock`) that host the git-bar warmup seat and the whale
|
|
20
|
+
// indicator, and the Context declaration for `ctx.conversation` (the /init
|
|
21
|
+
// command's scope-addressed send / input registry).
|
|
22
|
+
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
23
|
+
// Type-only imports activate the current global Session/Workspace standard
|
|
24
|
+
// slot props (`useSessions` / `useWorkspaces`). The removed pre-alpha
|
|
25
|
+
// dsh-client-runtime aggregate must not be restored by the merged UI Tweaks.
|
|
26
|
+
import type {} from '@deepseek-ai/dsh-client-ui-session/client'
|
|
27
|
+
import type {} from '@deepseek-ai/dsh-client-ui-workspace/client'
|
|
28
|
+
// Type-only import activates the Context declaration for `ctx.commandUi`
|
|
29
|
+
// (client slash-command contributions) and provides the contribution types.
|
|
30
|
+
import type { SelectOption } from '@deepseek-ai/dsh-client-ui-commands/client'
|
|
31
|
+
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
32
|
+
import { BranchChipEntry, GitWarmup, HeaderUtilities, installGitBarStyles, installHeroChip } from './gitbar.tsx'
|
|
33
|
+
import { ArchiveSection, installArchiveStyles } from './archive.tsx'
|
|
34
|
+
import { McpSection, installMcpStyles } from './mcp.tsx'
|
|
35
|
+
import { WhaleIndicator, installWhaleStyles } from './whale.tsx'
|
|
36
|
+
import { PreciseCacheHitEntry } from './cachehit.tsx'
|
|
37
|
+
import { installTaskNotifier, previewAlerts, requestNotifyPermission } from './notifier.ts'
|
|
38
|
+
import { HeroProjectLogo, installBranding, ProjectLogoIcon, SidebarProjectLogo, SidebarProjectName } from './branding.tsx'
|
|
39
|
+
|
|
40
|
+
const NS = 'ui-tweaks'
|
|
41
|
+
const SETTINGS_ROUTE = '/_dsh/ui-tweaks/settings'
|
|
42
|
+
|
|
43
|
+
/** Client context narrowed to the browser sessions face in this combined build. */
|
|
44
|
+
type UITweaksClientContext = ClientContext & { sessions: ISessions }
|
|
45
|
+
|
|
46
|
+
const DEFAULT_FONT_SIZE = 16
|
|
47
|
+
const MIN_FONT_SIZE = 10
|
|
48
|
+
const MAX_FONT_SIZE = 32
|
|
49
|
+
/** Legacy: code font as a percentage of the message font size (81% = stock 13/16). */
|
|
50
|
+
const DEFAULT_CODE_FONT_SCALE = 81
|
|
51
|
+
/** Absolute code font size (px): 13 matches the host default at a 16px body. */
|
|
52
|
+
const DEFAULT_CODE_FONT_SIZE = 13
|
|
53
|
+
const MIN_CODE_FONT_SIZE = 8
|
|
54
|
+
const MAX_CODE_FONT_SIZE = 32
|
|
55
|
+
/** 行高 base unit (px): message/block gaps, text line-height, paragraph & list margins all scale from it. */
|
|
56
|
+
const DEFAULT_LINE_HEIGHT = 16
|
|
57
|
+
const MIN_LINE_HEIGHT = 0
|
|
58
|
+
const MAX_LINE_HEIGHT = 64
|
|
59
|
+
|
|
60
|
+
interface TweaksValue {
|
|
61
|
+
fontSize?: number
|
|
62
|
+
/** Code font size as a percentage of the message font size (81 = stock). Legacy input. */
|
|
63
|
+
codeFontScale?: number
|
|
64
|
+
/** Absolute code font size in px; wins over the legacy percentage. */
|
|
65
|
+
codeFontSize?: number
|
|
66
|
+
/** 行高 base unit in px; scales message/block gaps, line-height and list/paragraph margins. */
|
|
67
|
+
lineHeight?: number
|
|
68
|
+
tableStyle?: 'default' | 'claude'
|
|
69
|
+
/** Whether the GitBar (branch / diff / commit pills) is shown. */
|
|
70
|
+
gitBarEnabled?: boolean
|
|
71
|
+
/** Whether the Archive manager (sidebar entry above Settings) is shown. */
|
|
72
|
+
archiveManagerEnabled?: boolean
|
|
73
|
+
/** Whether the MCP manager (Settings page listing MCP servers) is shown. */
|
|
74
|
+
mcpManagerEnabled?: boolean
|
|
75
|
+
/** Whether the /init slash command (AGENTS.md bootstrap prompt) is registered. */
|
|
76
|
+
initCommandEnabled?: boolean
|
|
77
|
+
/** Whether the whale working indicator above the input is shown. */
|
|
78
|
+
whaleIndicatorEnabled?: boolean
|
|
79
|
+
/** Whether the stats-line cache-hit figure keeps two decimals. Keep in sync with src/config.ts. */
|
|
80
|
+
preciseCacheHitEnabled?: boolean
|
|
81
|
+
/** Whether task notifications are active. Keep in sync with src/config.ts. */
|
|
82
|
+
notificationsEnabled?: boolean
|
|
83
|
+
/** Alert only while the tab is hidden or unfocused. */
|
|
84
|
+
notifyOnlyWhenHidden?: boolean
|
|
85
|
+
/** Alert when a session completes its turn. */
|
|
86
|
+
notifyOnComplete?: boolean
|
|
87
|
+
/** Alert when a session blocks on an approval, plan review or question. */
|
|
88
|
+
notifyOnInteraction?: boolean
|
|
89
|
+
/** Blink an unread counter into the tab title. */
|
|
90
|
+
notifyTitleFlash?: boolean
|
|
91
|
+
/** Fire desktop-level Web Notifications. */
|
|
92
|
+
notifySystemNotification?: boolean
|
|
93
|
+
/** Play the synthesized two-note chime. */
|
|
94
|
+
notifySound?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ResolvedTweaks {
|
|
98
|
+
fontSize: number
|
|
99
|
+
/** Effective absolute code font size in px (codeFontSize, else legacy %, else stock). */
|
|
100
|
+
codeFontSize: number
|
|
101
|
+
lineHeight: number
|
|
102
|
+
tableStyle: 'default' | 'claude'
|
|
103
|
+
gitBarEnabled: boolean
|
|
104
|
+
archiveManagerEnabled: boolean
|
|
105
|
+
mcpManagerEnabled: boolean
|
|
106
|
+
initCommandEnabled: boolean
|
|
107
|
+
whaleIndicatorEnabled: boolean
|
|
108
|
+
preciseCacheHitEnabled: boolean
|
|
109
|
+
notificationsEnabled: boolean
|
|
110
|
+
notifyOnlyWhenHidden: boolean
|
|
111
|
+
notifyOnComplete: boolean
|
|
112
|
+
notifyOnInteraction: boolean
|
|
113
|
+
notifyTitleFlash: boolean
|
|
114
|
+
notifySystemNotification: boolean
|
|
115
|
+
notifySound: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface UITweaksSnapshot {
|
|
119
|
+
writable: boolean
|
|
120
|
+
value: TweaksValue
|
|
121
|
+
revision: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ApiSuccess<T> { ok: true; value: T }
|
|
125
|
+
interface ApiFailure { ok: false; error: { code: string; message: string } }
|
|
126
|
+
|
|
127
|
+
const en = {
|
|
128
|
+
nav: 'UI Tweaks',
|
|
129
|
+
settingsTitle: 'UI Tweaks',
|
|
130
|
+
settingsIntro: 'Tune the conversation UI — text, tables and layout, plus optional features: git bar, archive & MCP managers and the /init command. Changes apply live.',
|
|
131
|
+
sectionText: 'Text & tables',
|
|
132
|
+
sectionLayout: 'Layout',
|
|
133
|
+
sectionFeatures: 'Features',
|
|
134
|
+
fontSize: 'Message font size',
|
|
135
|
+
fontSizeHint: `Number between ${MIN_FONT_SIZE} and ${MAX_FONT_SIZE}; applies to message text, headings, tables and code.`,
|
|
136
|
+
codeFontSize: 'Code font size',
|
|
137
|
+
codeFontSizeHint: `Absolute code size in px (${MIN_CODE_FONT_SIZE}–${MAX_CODE_FONT_SIZE}); ${DEFAULT_CODE_FONT_SIZE}px is ChatCode CLI's default at a 16px body. Applies to code blocks; inline code follows proportionally.`,
|
|
138
|
+
lineHeight: 'Line spacing',
|
|
139
|
+
lineHeightHint: `Base vertical spacing in px: gaps between message rows (Think ↔ tool cards) and blocks inside one reply, plus text line-height, paragraph and list margins, all scale from it; ${DEFAULT_LINE_HEIGHT} is ChatCode CLI's default.`,
|
|
140
|
+
tableStyle: 'Table style',
|
|
141
|
+
tableStyleHint: 'Cell look for markdown tables: stock borders, or the Claude Desktop card style.',
|
|
142
|
+
tableStyleDefault: 'Default',
|
|
143
|
+
tableStyleClaude: 'Claude Desktop',
|
|
144
|
+
gitBar: 'Git bar',
|
|
145
|
+
gitBarHint: 'Branch / diff pills above the input inside git repos, with branch management and commit & push; auto-hidden outside git.',
|
|
146
|
+
gitBarOn: 'On',
|
|
147
|
+
gitBarOff: 'Off',
|
|
148
|
+
archiveManager: 'Archive manager',
|
|
149
|
+
archiveManagerHint: 'An Archive settings page to restore or permanently delete archived sessions.',
|
|
150
|
+
archiveManagerOn: 'On',
|
|
151
|
+
archiveManagerOff: 'Off',
|
|
152
|
+
mcpManager: 'MCP manager',
|
|
153
|
+
mcpManagerHint: 'An MCP settings page listing servers with status and tools; edit and restart them.',
|
|
154
|
+
mcpManagerOn: 'On',
|
|
155
|
+
mcpManagerOff: 'Off',
|
|
156
|
+
mcpNav: 'MCP',
|
|
157
|
+
mcpTitle: 'MCP servers',
|
|
158
|
+
mcpEmpty: 'No MCP servers configured.',
|
|
159
|
+
mcpStatusActive: 'Running',
|
|
160
|
+
mcpStatusFailed: 'Failed',
|
|
161
|
+
mcpStatusLoading: 'Loading',
|
|
162
|
+
mcpStatusStopped: 'Stopped',
|
|
163
|
+
mcpStatusDisabled: 'Disabled',
|
|
164
|
+
mcpTools: 'tools',
|
|
165
|
+
mcpEnv: 'Env',
|
|
166
|
+
mcpAdd: 'Add server',
|
|
167
|
+
mcpAddTitle: 'Add MCP server',
|
|
168
|
+
mcpEditTitle: 'Edit MCP server',
|
|
169
|
+
mcpEdit: 'Edit',
|
|
170
|
+
mcpDelete: 'Delete',
|
|
171
|
+
mcpEnabledAction: 'Enable',
|
|
172
|
+
mcpDisabledAction: 'Disable',
|
|
173
|
+
mcpSaved: 'Saved.',
|
|
174
|
+
mcpRemoved: 'Removed.',
|
|
175
|
+
mcpFormTab: 'Form',
|
|
176
|
+
mcpYamlTab: 'YAML',
|
|
177
|
+
mcpYamlHint: 'Fill in the MCP config directly (serverName / transport / command / args / env / toolCallTimeoutMs / url / headers …). It is validated before saving.',
|
|
178
|
+
mcpYamlPlaceholder: 'serverName: my-server\ntransport: stdio\ncommand: npx\nargs:\n - "-y"\n - "@some/mcp-server"\nenv:\n KEY: value\ntoolCallTimeoutMs: 60000',
|
|
179
|
+
mcpFieldId: 'Instance ID',
|
|
180
|
+
mcpFieldIdHint: 'Loader entry id (letters, digits, - and _ only); it must be unique.',
|
|
181
|
+
mcpFieldName: 'Name',
|
|
182
|
+
mcpFieldNameHint: 'Tool namespace `mcp__<name>__*`; letters, digits, - and _ (1–32).',
|
|
183
|
+
mcpFieldType: 'Type',
|
|
184
|
+
mcpFieldTimeout: 'Timeout (ms)',
|
|
185
|
+
mcpFieldCommand: 'Command',
|
|
186
|
+
mcpFieldCommandPlaceholder: 'e.g. npx',
|
|
187
|
+
mcpFieldArgs: 'Arguments (one per line)',
|
|
188
|
+
mcpFieldEnv: 'Environment vars (optional, KEY=value per line)',
|
|
189
|
+
mcpFieldUrl: 'URL',
|
|
190
|
+
mcpFieldHeaders: 'Headers (optional, Key: value per line)',
|
|
191
|
+
mcpFieldEnabled: 'Enabled',
|
|
192
|
+
mcpSave: 'Save',
|
|
193
|
+
mcpCancel: 'Cancel',
|
|
194
|
+
mcpInvalidId: 'Instance ID may only contain letters, digits, - and _.',
|
|
195
|
+
mcpInvalidName: 'Name may only contain letters, digits, - and _ (1–32 chars).',
|
|
196
|
+
mcpInvalidTimeout: 'Timeout must be a positive integer (ms).',
|
|
197
|
+
mcpInvalidUrl: 'URL must start with http:// or https://.',
|
|
198
|
+
mcpInvalidCommand: 'Command is required.',
|
|
199
|
+
mcpUnavailable: 'MCP manager unavailable.',
|
|
200
|
+
mcpDisabledHint: 'MCP management is off. Turn it on in 界面调整 (UI Tweaks) to view and restart MCP servers here.',
|
|
201
|
+
mcpEnable: 'Enable MCP manager',
|
|
202
|
+
initCommand: '/init command',
|
|
203
|
+
initCommandHint: 'The /init slash command: pick a prompt language and ChatCode CLI analyzes the project and writes AGENTS.md.',
|
|
204
|
+
initCommandOn: 'On',
|
|
205
|
+
initCommandOff: 'Off',
|
|
206
|
+
whaleIndicator: 'Whale indicator',
|
|
207
|
+
whaleIndicatorHint: 'A little whale above the input box: translucent while idle; while the model works it swims and breathes blue.',
|
|
208
|
+
whaleIndicatorOn: 'On',
|
|
209
|
+
whaleIndicatorOff: 'Off',
|
|
210
|
+
preciseCacheHit: 'Precise cache hit',
|
|
211
|
+
preciseCacheHitHint: 'Rewrites the cache-hit figure in the stats line under the input box to two decimal places (e.g. 96.35%), computed from the raw cached-read / cached-write / uncached-input token buckets instead of the stock rounded integer.',
|
|
212
|
+
preciseCacheHitOn: 'On',
|
|
213
|
+
preciseCacheHitOff: 'Off',
|
|
214
|
+
sectionNotifications: 'Task alerts',
|
|
215
|
+
notifications: 'Enable alerts',
|
|
216
|
+
notificationsHint: 'Call you back while the tab is in the background: a session finishes its turn, or one starts waiting for your approval, plan review or answer. The switches below apply only while this is on.',
|
|
217
|
+
notifyOn: 'On',
|
|
218
|
+
notifyOff: 'Off',
|
|
219
|
+
notifyOnComplete: 'Alert on finish',
|
|
220
|
+
notifyOnCompleteHint: 'Fire when a session ends its turn — completed, interrupted or failed; the notification says which.',
|
|
221
|
+
notifyOnInteraction: 'Alert on interaction',
|
|
222
|
+
notifyOnInteractionHint: 'Fire when a session waits on you: an approval, a plan review, or a question from the agent.',
|
|
223
|
+
notifyOnlyWhenHidden: 'Only when hidden',
|
|
224
|
+
notifyOnlyWhenHiddenHint: 'Stay quiet while you are looking at this page; alert only once the tab is hidden or unfocused.',
|
|
225
|
+
notifyTitleFlash: 'Tab title flash',
|
|
226
|
+
notifyTitleFlashHint: 'Blink an unread counter into the tab title until you come back.',
|
|
227
|
+
notifySystemNotification: 'System notifications',
|
|
228
|
+
notifySystemNotificationHint: 'Desktop-level notifications; click one to jump straight to that session. Permission is requested when enabled.',
|
|
229
|
+
notifySound: 'Chime',
|
|
230
|
+
notifySoundHint: 'A soft two-note motif — rising when work finishes, falling when it needs you.',
|
|
231
|
+
notifyTest: 'Test',
|
|
232
|
+
notifyTitleDone: 'Task finished',
|
|
233
|
+
notifyTitleAborted: 'Task interrupted',
|
|
234
|
+
notifyTitleFailed: 'Request failed',
|
|
235
|
+
notifyTitlePending: 'Needs you',
|
|
236
|
+
bodyComplete: '✅ {title} finished its turn.',
|
|
237
|
+
bodyAborted: '⏹ {title} was interrupted before finishing.',
|
|
238
|
+
bodyFailed: '❌ {title} failed: {error}',
|
|
239
|
+
bodyApproval: '⏸ {title} is waiting for your approval.',
|
|
240
|
+
bodyPlan: '📋 {title} has a plan awaiting your review.',
|
|
241
|
+
bodyQuestion: '❓ {title} asked you a question.',
|
|
242
|
+
mcpServerDetail: 'Configured in the profile cordis.patch.yml as @deepseek-ai/dsh-mcp-client instances; add / edit / disable / delete write to that file and apply live.',
|
|
243
|
+
archiveNav: 'Archive',
|
|
244
|
+
archiveTitle: 'Archived sessions',
|
|
245
|
+
archiveEmpty: 'No archived sessions.',
|
|
246
|
+
archiveRestore: 'Restore',
|
|
247
|
+
archiveRestoring: 'Working…',
|
|
248
|
+
archiveDelete: 'Delete',
|
|
249
|
+
archiveDeleteAll: 'Delete all',
|
|
250
|
+
archiveRestoreAll: 'Restore all',
|
|
251
|
+
archiveCount: 'sessions',
|
|
252
|
+
archiveUnavailable: 'Archive unavailable.',
|
|
253
|
+
archiveLiveError: 'This session is running; close it before permanently deleting it.',
|
|
254
|
+
archiveDisabledHint: 'Archive management is off. Turn it on in 界面调整 (UI Tweaks) to restore or permanently delete archived sessions here.',
|
|
255
|
+
archiveEnable: 'Enable archive management',
|
|
256
|
+
archiveRestored: 'Restored.',
|
|
257
|
+
defaultAction: 'Default',
|
|
258
|
+
reset: 'Reset',
|
|
259
|
+
resetDone: 'Reset to default.',
|
|
260
|
+
applied: 'Applied',
|
|
261
|
+
unavailable: 'Settings unavailable.',
|
|
262
|
+
loading: 'Loading…',
|
|
263
|
+
readOnly: 'The active Settings provider is read-only.',
|
|
264
|
+
saved: 'Saved.',
|
|
265
|
+
commitMessage: 'Commit',
|
|
266
|
+
diffFiles: 'files',
|
|
267
|
+
branchLocal: 'Local branches',
|
|
268
|
+
branchRemote: 'Remote branches',
|
|
269
|
+
branchNew: 'New branch',
|
|
270
|
+
branchNewPlaceholder: 'Branch name, e.g. fix/typo',
|
|
271
|
+
branchCreate: 'Create',
|
|
272
|
+
diffTitle: 'Changes',
|
|
273
|
+
diffOnly: 'Hunks',
|
|
274
|
+
diffFull: 'Full file',
|
|
275
|
+
commitTitle: 'Commit changes',
|
|
276
|
+
commitPlaceholder: 'Describe your changes…',
|
|
277
|
+
commitHint: 'Committing stages the checked files (untracked included) and commits; unchecked files are excluded from this commit. “Commit & push” also pushes; a new branch gets an -u upstream.',
|
|
278
|
+
commitWillCommit: 'To commit',
|
|
279
|
+
commitViewDiff: 'View',
|
|
280
|
+
commitEmpty: 'Write a commit message first',
|
|
281
|
+
commitCancel: 'Cancel',
|
|
282
|
+
commitSubmit: 'Commit',
|
|
283
|
+
commitSubmitPush: 'Commit & push',
|
|
284
|
+
commitBusy: 'Task in progress — commit is unavailable while the agent is working.',
|
|
285
|
+
dirty: 'Uncommitted changes',
|
|
286
|
+
clean: 'Working tree clean',
|
|
287
|
+
noChanges: 'No changes here.',
|
|
288
|
+
branchDelete: 'Delete branch',
|
|
289
|
+
branchDeleteConfirm: 'Confirm?',
|
|
290
|
+
branchRemoteDelete: 'Delete remote branch',
|
|
291
|
+
branchFrom: 'From branch',
|
|
292
|
+
branchFromHead: 'Current HEAD (default)',
|
|
293
|
+
branchGraph: 'Graph',
|
|
294
|
+
branchCancel: 'Cancel',
|
|
295
|
+
branchRefresh: 'Refresh',
|
|
296
|
+
graphTitle: 'Commit graph',
|
|
297
|
+
graphColGraph: 'Graph',
|
|
298
|
+
graphColCommit: 'Commit',
|
|
299
|
+
graphColSubject: 'Description',
|
|
300
|
+
graphColAuthor: 'Author',
|
|
301
|
+
graphColDate: 'Date',
|
|
302
|
+
branchPushRemote: 'Push to remote',
|
|
303
|
+
includeFile: 'Include in commit',
|
|
304
|
+
excludeFile: 'Exclude from commit',
|
|
305
|
+
openProject: 'Open project',
|
|
306
|
+
terminal: 'Terminal',
|
|
307
|
+
openExplorer: 'Explorer',
|
|
308
|
+
openVscode: 'VS Code',
|
|
309
|
+
openIdea: 'IntelliJ IDEA',
|
|
310
|
+
openGoland: 'GoLand',
|
|
311
|
+
openWebstorm: 'WebStorm',
|
|
312
|
+
openPycharm: 'PyCharm',
|
|
313
|
+
termConnecting: 'connecting…',
|
|
314
|
+
termExited: 'shell exited',
|
|
315
|
+
termUnavailable: 'PTY unavailable: node-pty failed to load on the host.',
|
|
316
|
+
termLost: 'connection lost',
|
|
317
|
+
initDesc: 'Analyze this project and generate an AGENTS.md for future coding agents',
|
|
318
|
+
initOptionZh: 'AGENTS.md — Chinese prompt',
|
|
319
|
+
initOptionZhDetail: 'Submit a Chinese prompt asking the agent to analyze the project and write or improve AGENTS.md.',
|
|
320
|
+
initOptionEn: 'AGENTS.md — English prompt',
|
|
321
|
+
initOptionEnDetail: 'Submit an English prompt asking the agent to analyze the project and write or improve AGENTS.md.',
|
|
322
|
+
initFailed: '/init failed to send the prompt',
|
|
323
|
+
} as const
|
|
324
|
+
|
|
325
|
+
type LocaleKey = keyof typeof en
|
|
326
|
+
|
|
327
|
+
const zh: Record<LocaleKey, string> = {
|
|
328
|
+
nav: '界面调整',
|
|
329
|
+
settingsTitle: '界面调整',
|
|
330
|
+
settingsIntro: '调整对话界面——文本、表格与布局,以及 Git 状态栏、归档 / MCP 管理、/init 命令等功能开关,修改即时生效。',
|
|
331
|
+
sectionText: '文本与表格',
|
|
332
|
+
sectionLayout: '布局',
|
|
333
|
+
sectionFeatures: '功能',
|
|
334
|
+
fontSize: '消息字体大小',
|
|
335
|
+
fontSizeHint: `取值 ${MIN_FONT_SIZE}–${MAX_FONT_SIZE},作用于消息正文、标题、表格与代码。`,
|
|
336
|
+
codeFontSize: '代码字号',
|
|
337
|
+
codeFontSizeHint: `代码绝对字号,取值 ${MIN_CODE_FONT_SIZE}–${MAX_CODE_FONT_SIZE}px;${DEFAULT_CODE_FONT_SIZE}px 为 ChatCode CLI 默认(正文 16 时)。作用于代码块,行内代码按比例跟随。`,
|
|
338
|
+
lineHeight: '行高',
|
|
339
|
+
lineHeightHint: `回复区垂直间距的基准值:消息之间(如 Think ↔ 工具卡片)、回复内块之间、正文行高、段落与列表边距都按它缩放;取值 ${MIN_LINE_HEIGHT}–${MAX_LINE_HEIGHT}px,${DEFAULT_LINE_HEIGHT} 为 ChatCode CLI 默认。`,
|
|
340
|
+
tableStyle: '表格样式',
|
|
341
|
+
tableStyleHint: 'Markdown 表格的外观:默认边框,或 Claude Desktop 卡片风格。',
|
|
342
|
+
tableStyleDefault: '默认',
|
|
343
|
+
tableStyleClaude: 'Claude Desktop',
|
|
344
|
+
gitBar: 'Git 状态栏',
|
|
345
|
+
gitBarHint: 'git 仓库内时在输入框上方显示 分支 / 差异 胶囊,支持分支管理与提交推送;非 git 目录自动隐藏。',
|
|
346
|
+
gitBarOn: '开启',
|
|
347
|
+
gitBarOff: '关闭',
|
|
348
|
+
archiveManager: '归档管理',
|
|
349
|
+
archiveManagerHint: '在设置中显示「归档」页面:恢复或彻底删除已归档会话。',
|
|
350
|
+
archiveManagerOn: '开启',
|
|
351
|
+
archiveManagerOff: '关闭',
|
|
352
|
+
mcpManager: 'MCP 管理',
|
|
353
|
+
mcpManagerHint: '在设置中显示「MCP 管理」页面:查看服务器状态与工具,可编辑并重启。',
|
|
354
|
+
mcpManagerOn: '开启',
|
|
355
|
+
mcpManagerOff: '关闭',
|
|
356
|
+
mcpNav: 'MCP 管理',
|
|
357
|
+
mcpTitle: 'MCP 服务器',
|
|
358
|
+
mcpEmpty: '未配置任何 MCP 服务器。',
|
|
359
|
+
mcpStatusActive: '运行中',
|
|
360
|
+
mcpStatusFailed: '错误',
|
|
361
|
+
mcpStatusLoading: '加载中',
|
|
362
|
+
mcpStatusStopped: '未运行',
|
|
363
|
+
mcpStatusDisabled: '已停用',
|
|
364
|
+
mcpTools: '个工具',
|
|
365
|
+
mcpEnv: '环境变量',
|
|
366
|
+
mcpAdd: '添加服务器',
|
|
367
|
+
mcpAddTitle: '添加 MCP 服务器',
|
|
368
|
+
mcpEditTitle: '编辑 MCP 服务器',
|
|
369
|
+
mcpEdit: '编辑',
|
|
370
|
+
mcpDelete: '删除',
|
|
371
|
+
mcpEnabledAction: '启用',
|
|
372
|
+
mcpDisabledAction: '停用',
|
|
373
|
+
mcpSaved: '已保存。',
|
|
374
|
+
mcpRemoved: '已删除。',
|
|
375
|
+
mcpFormTab: '表单',
|
|
376
|
+
mcpYamlTab: 'YAML',
|
|
377
|
+
mcpYamlHint: '直接填写 MCP 配置(serverName / transport / command / args / env / toolCallTimeoutMs / url / headers …),保存前会校验格式。',
|
|
378
|
+
mcpYamlPlaceholder: 'serverName: my-server\ntransport: stdio\ncommand: npx\nargs:\n - "-y"\n - "@some/mcp-server"\nenv:\n KEY: value\ntoolCallTimeoutMs: 60000',
|
|
379
|
+
mcpFieldId: '实例 ID',
|
|
380
|
+
mcpFieldIdHint: '加载器条目 ID(仅字母、数字、- 和 _),需唯一。',
|
|
381
|
+
mcpFieldName: '名称',
|
|
382
|
+
mcpFieldNameHint: '工具命名空间 `mcp__<名称>__*`;字母、数字、- 和 _(1–32 字符)。',
|
|
383
|
+
mcpFieldType: '类型',
|
|
384
|
+
mcpFieldTimeout: '超时时间 (ms)',
|
|
385
|
+
mcpFieldCommand: '命令',
|
|
386
|
+
mcpFieldCommandPlaceholder: '如 npx',
|
|
387
|
+
mcpFieldArgs: '参数(每行一个)',
|
|
388
|
+
mcpFieldEnv: '环境变量(可选,每行 KEY=值)',
|
|
389
|
+
mcpFieldUrl: 'URL',
|
|
390
|
+
mcpFieldHeaders: '请求头(可选,每行 Key: 值)',
|
|
391
|
+
mcpFieldEnabled: '启用',
|
|
392
|
+
mcpSave: '保存',
|
|
393
|
+
mcpCancel: '取消',
|
|
394
|
+
mcpInvalidId: '实例 ID 只能包含字母、数字、- 和 _。',
|
|
395
|
+
mcpInvalidName: '名称只能包含字母、数字、- 和 _(1–32 字符)。',
|
|
396
|
+
mcpInvalidTimeout: '超时时间必须是正整数(毫秒)。',
|
|
397
|
+
mcpInvalidUrl: 'URL 必须以 http:// 或 https:// 开头。',
|
|
398
|
+
mcpInvalidCommand: '命令不能为空。',
|
|
399
|
+
mcpUnavailable: 'MCP 管理暂不可用。',
|
|
400
|
+
mcpDisabledHint: 'MCP 管理尚未开启。在「界面调整」中开启“MCP 管理”后,可在此查看并重启 MCP 服务器。',
|
|
401
|
+
mcpEnable: '开启 MCP 管理',
|
|
402
|
+
initCommand: '/init 命令',
|
|
403
|
+
initCommandHint: '注册 /init 斜杠命令:选择提示词语言后,让 ChatCode CLI 分析项目并生成 AGENTS.md。',
|
|
404
|
+
initCommandOn: '开启',
|
|
405
|
+
initCommandOff: '关闭',
|
|
406
|
+
whaleIndicator: '鲸鱼指示器',
|
|
407
|
+
whaleIndicatorHint: '输入框上方的小鲸鱼:空闲时半透明静止,模型工作时游动并在蓝黑之间呼吸变幻。',
|
|
408
|
+
whaleIndicatorOn: '开启',
|
|
409
|
+
whaleIndicatorOff: '关闭',
|
|
410
|
+
preciseCacheHit: '缓存命中率两位小数',
|
|
411
|
+
preciseCacheHitHint: '把输入框下方统计条里的缓存命中百分比改写为两位小数(如 96.35%)——用原始 token 数(缓存读取 ÷ 计费输入)计算,而不是宿主取整后的整数。',
|
|
412
|
+
preciseCacheHitOn: '开启',
|
|
413
|
+
preciseCacheHitOff: '关闭',
|
|
414
|
+
sectionNotifications: '任务提醒',
|
|
415
|
+
notifications: '启用提醒',
|
|
416
|
+
notificationsHint: '标签页在后台时唤你回来:会话完成了任务,或开始等待你的审批、计划确认或回答。下方开关仅在总开关开启时生效。',
|
|
417
|
+
notifyOn: '开启',
|
|
418
|
+
notifyOff: '关闭',
|
|
419
|
+
notifyOnComplete: '完成提醒',
|
|
420
|
+
notifyOnCompleteHint: '会话结束一轮任务时提醒——完成、被中断或出错都会通知,并注明是哪种情况。',
|
|
421
|
+
notifyOnInteraction: '交互提醒',
|
|
422
|
+
notifyOnInteractionHint: '会话等待你操作时提醒:审批、计划确认,或模型向你提问。',
|
|
423
|
+
notifyOnlyWhenHidden: '仅页面不可见时',
|
|
424
|
+
notifyOnlyWhenHiddenHint: '你正盯着本页时保持安静;切走标签页或最小化窗口后才开始提醒。',
|
|
425
|
+
notifyTitleFlash: '标题闪烁',
|
|
426
|
+
notifyTitleFlashHint: '在浏览器标签页标题中闪烁未读计数,直到你回到页面。',
|
|
427
|
+
notifySystemNotification: '系统通知',
|
|
428
|
+
notifySystemNotificationHint: '桌面级通知;点击通知可直达对应会话。开启时会向浏览器申请通知权限。',
|
|
429
|
+
notifySound: '提示音',
|
|
430
|
+
notifySoundHint: '轻柔的双音提示——上行表示完成,下行表示需要你处理。',
|
|
431
|
+
notifyTest: '测试',
|
|
432
|
+
notifyTitleDone: '任务完成',
|
|
433
|
+
notifyTitleAborted: '任务已中断',
|
|
434
|
+
notifyTitleFailed: '请求失败',
|
|
435
|
+
notifyTitlePending: '需要你处理',
|
|
436
|
+
bodyComplete: '✅ 「{title}」的任务已完成。',
|
|
437
|
+
bodyAborted: '⏹ 「{title}」的任务被中断了。',
|
|
438
|
+
bodyFailed: '❌ 「{title}」的任务出错了:{error}',
|
|
439
|
+
bodyApproval: '⏸ 「{title}」正在等待你的审批。',
|
|
440
|
+
bodyPlan: '📋 「{title}」有计划待确认。',
|
|
441
|
+
bodyQuestion: '❓ 「{title}」向你提问了。',
|
|
442
|
+
mcpServerDetail: 'MCP 服务器配置在 profile 的 cordis.patch.yml(@deepseek-ai/dsh-mcp-client 实例);添加 / 编辑 / 停用 / 删除会写入该文件,改动实时生效。',
|
|
443
|
+
archiveNav: '归档',
|
|
444
|
+
archiveTitle: '已归档会话',
|
|
445
|
+
archiveEmpty: '暂无归档会话。',
|
|
446
|
+
archiveRestore: '恢复',
|
|
447
|
+
archiveRestoring: '处理中…',
|
|
448
|
+
archiveDelete: '删除',
|
|
449
|
+
archiveDeleteAll: '全部删除',
|
|
450
|
+
archiveRestoreAll: '全部恢复',
|
|
451
|
+
archiveCount: '个会话',
|
|
452
|
+
archiveUnavailable: '归档暂不可用。',
|
|
453
|
+
archiveLiveError: '该会话正在运行,无法彻底删除,请先关闭该会话。',
|
|
454
|
+
archiveDisabledHint: '归档管理尚未开启。在「界面调整」中开启“归档管理”后,可在此查看、恢复或彻底删除已归档会话。',
|
|
455
|
+
archiveEnable: '开启归档管理',
|
|
456
|
+
archiveRestored: '已恢复。',
|
|
457
|
+
defaultAction: '默认',
|
|
458
|
+
reset: '重置',
|
|
459
|
+
resetDone: '已重置为默认。',
|
|
460
|
+
applied: '已应用',
|
|
461
|
+
unavailable: '设置暂不可用。',
|
|
462
|
+
loading: '加载中…',
|
|
463
|
+
readOnly: '当前设置提供方为只读。',
|
|
464
|
+
saved: '已保存。',
|
|
465
|
+
commitMessage: 'Commit',
|
|
466
|
+
diffFiles: '个文件',
|
|
467
|
+
branchLocal: '本地分支',
|
|
468
|
+
branchRemote: '远程分支',
|
|
469
|
+
branchNew: '新建分支',
|
|
470
|
+
branchNewPlaceholder: '分支名,如 fix/typo',
|
|
471
|
+
branchCreate: '创建',
|
|
472
|
+
diffTitle: '变更',
|
|
473
|
+
diffOnly: '仅差异',
|
|
474
|
+
diffFull: '完整文件',
|
|
475
|
+
commitTitle: '提交变更',
|
|
476
|
+
commitPlaceholder: '描述你的改动…',
|
|
477
|
+
commitHint: '提交会暂存勾选的文件(含未跟踪新文件);取消勾选的文件将不包含在本次提交中。「提交并推送」提交后自动 push,新分支自动 -u 设上游。',
|
|
478
|
+
commitWillCommit: '将提交',
|
|
479
|
+
commitViewDiff: '查看',
|
|
480
|
+
commitEmpty: '请先填写提交说明',
|
|
481
|
+
commitCancel: '取消',
|
|
482
|
+
commitSubmit: '提交',
|
|
483
|
+
commitSubmitPush: '提交并推送',
|
|
484
|
+
commitBusy: '任务进行中,暂不能提交。',
|
|
485
|
+
dirty: '有未提交改动',
|
|
486
|
+
clean: '工作区干净',
|
|
487
|
+
noChanges: '这里没有差异。',
|
|
488
|
+
branchDelete: '删除分支',
|
|
489
|
+
branchDeleteConfirm: '确认删除?',
|
|
490
|
+
branchRemoteDelete: '删除远程分支',
|
|
491
|
+
branchFrom: '基于分支',
|
|
492
|
+
branchFromHead: '当前 HEAD(默认)',
|
|
493
|
+
branchGraph: '图谱',
|
|
494
|
+
branchCancel: '取消',
|
|
495
|
+
branchRefresh: '刷新',
|
|
496
|
+
graphTitle: '提交图谱',
|
|
497
|
+
graphColGraph: '图',
|
|
498
|
+
graphColCommit: '提交',
|
|
499
|
+
graphColSubject: '描述',
|
|
500
|
+
graphColAuthor: '作者',
|
|
501
|
+
graphColDate: '日期',
|
|
502
|
+
branchPushRemote: '推送到远程',
|
|
503
|
+
includeFile: '提交包含此文件',
|
|
504
|
+
excludeFile: '提交排除此文件',
|
|
505
|
+
openProject: '打开项目',
|
|
506
|
+
terminal: '终端',
|
|
507
|
+
openExplorer: '资源管理器',
|
|
508
|
+
openVscode: 'VS Code',
|
|
509
|
+
openIdea: 'IntelliJ IDEA',
|
|
510
|
+
openGoland: 'GoLand',
|
|
511
|
+
openWebstorm: 'WebStorm',
|
|
512
|
+
openPycharm: 'PyCharm',
|
|
513
|
+
termConnecting: '连接中…',
|
|
514
|
+
termExited: 'shell 已退出',
|
|
515
|
+
termUnavailable: '终端不可用:宿主加载 node-pty 失败,无法启动 PTY 会话。',
|
|
516
|
+
termLost: '连接已断开',
|
|
517
|
+
initDesc: '分析当前项目并生成 AGENTS.md,供未来的 AI 编码代理使用',
|
|
518
|
+
initOptionZh: 'AGENTS.md(中文提示词)',
|
|
519
|
+
initOptionZhDetail: '向会话提交中文提示词,让代理分析项目并生成或改进 AGENTS.md。',
|
|
520
|
+
initOptionEn: 'AGENTS.md(英文提示词)',
|
|
521
|
+
initOptionEnDetail: '向会话提交英文提示词,让代理分析项目并生成或改进 AGENTS.md。',
|
|
522
|
+
initFailed: '/init 提示词发送失败',
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
type Translate = (key: LocaleKey) => string
|
|
526
|
+
|
|
527
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
528
|
+
interface LocaleNamespaceMap {
|
|
529
|
+
/** dsh-ui-tweaks Settings copy. */
|
|
530
|
+
'ui-tweaks': LocaleKey
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function resolveValue(value: TweaksValue | undefined): ResolvedTweaks {
|
|
535
|
+
const fontSize = typeof value?.fontSize === 'number' ? value.fontSize : DEFAULT_FONT_SIZE
|
|
536
|
+
// Effective code size: the absolute px input wins; otherwise derive px from
|
|
537
|
+
// the legacy percentage at the resolved body size; otherwise stock.
|
|
538
|
+
const codeFontSize = typeof value?.codeFontSize === 'number'
|
|
539
|
+
? Math.min(MAX_CODE_FONT_SIZE, Math.max(MIN_CODE_FONT_SIZE, value.codeFontSize))
|
|
540
|
+
: Math.max(8, Math.round(fontSize * (13 / 16) * ((value?.codeFontScale ?? DEFAULT_CODE_FONT_SCALE) / DEFAULT_CODE_FONT_SCALE)))
|
|
541
|
+
return {
|
|
542
|
+
fontSize,
|
|
543
|
+
codeFontSize,
|
|
544
|
+
lineHeight: typeof value?.lineHeight === 'number' ? value.lineHeight : DEFAULT_LINE_HEIGHT,
|
|
545
|
+
tableStyle: value?.tableStyle === 'claude' ? 'claude' : 'default',
|
|
546
|
+
gitBarEnabled: value?.gitBarEnabled ?? false,
|
|
547
|
+
archiveManagerEnabled: value?.archiveManagerEnabled ?? false,
|
|
548
|
+
mcpManagerEnabled: value?.mcpManagerEnabled ?? false,
|
|
549
|
+
initCommandEnabled: value?.initCommandEnabled ?? false,
|
|
550
|
+
whaleIndicatorEnabled: value?.whaleIndicatorEnabled ?? false,
|
|
551
|
+
preciseCacheHitEnabled: value?.preciseCacheHitEnabled ?? false,
|
|
552
|
+
notificationsEnabled: value?.notificationsEnabled ?? false,
|
|
553
|
+
notifyOnlyWhenHidden: value?.notifyOnlyWhenHidden ?? true,
|
|
554
|
+
notifyOnComplete: value?.notifyOnComplete ?? true,
|
|
555
|
+
notifyOnInteraction: value?.notifyOnInteraction ?? true,
|
|
556
|
+
notifyTitleFlash: value?.notifyTitleFlash ?? true,
|
|
557
|
+
notifySystemNotification: value?.notifySystemNotification ?? true,
|
|
558
|
+
notifySound: value?.notifySound ?? false,
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Scale one theme px value proportionally to the chosen base size. */
|
|
563
|
+
function rel(base: number, fontSize: number): number {
|
|
564
|
+
return Math.max(8, Math.round((base / DEFAULT_FONT_SIZE) * fontSize))
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Rebuild the markdown font tokens for the chosen base size, keeping the theme faces. */
|
|
568
|
+
function buildFontCss(fontSize: number, lineHeight: number, codeFontSize: number): string {
|
|
569
|
+
const cs = getComputedStyle(document.body)
|
|
570
|
+
const fam = (name: string, fallback: string): string => {
|
|
571
|
+
const value = cs.getPropertyValue(name).trim()
|
|
572
|
+
return value.length > 0 ? value : fallback
|
|
573
|
+
}
|
|
574
|
+
const base = fam('--dsw-font-markdown-base-font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif')
|
|
575
|
+
const code = fam('--dsw-font-markdown-code-font-family', '"SF Mono", Consolas, monospace')
|
|
576
|
+
const codeBlock = fam('--dsw-font-markdown-code-block-font-family', '"SF Mono", Consolas, monospace')
|
|
577
|
+
|
|
578
|
+
const parts: string[] = []
|
|
579
|
+
// `baseLine` is the token's line-height at the 16px baseline; it scales with
|
|
580
|
+
// both the chosen font size and the 行高 unit, so at the default (16) it
|
|
581
|
+
// reproduces the stock rhythm exactly.
|
|
582
|
+
const token = (shorthand: string, size: number, baseLine: number, family: string): void => {
|
|
583
|
+
const line = Math.max(12, Math.round((baseLine / DEFAULT_FONT_SIZE) * fontSize * (lineHeight / DEFAULT_LINE_HEIGHT)))
|
|
584
|
+
parts.push(`--${shorthand}:${size}px/${line}px ${family}`)
|
|
585
|
+
parts.push(`--${shorthand}-font-size:${size}px`)
|
|
586
|
+
parts.push(`--${shorthand}-line-height:${line}px`)
|
|
587
|
+
}
|
|
588
|
+
// Code fonts hang off the absolute code-block size (stock: 13px at a 16px
|
|
589
|
+
// body), with inline code slightly larger and the small variant slightly
|
|
590
|
+
// smaller, preserving DSH's hierarchy (14/13 and 12/13 of the block).
|
|
591
|
+
const codePx = (blockRatio: number): number => Math.max(8, Math.round(codeFontSize * blockRatio))
|
|
592
|
+
token('dsw-font-markdown-base', fontSize, 28, base)
|
|
593
|
+
token('dsw-font-markdown-base-strong', fontSize, 28, base)
|
|
594
|
+
token('dsw-font-markdown-base-italic', fontSize, 28, base)
|
|
595
|
+
token('dsw-font-markdown-base-strong-italic', fontSize, 28, base)
|
|
596
|
+
token('dsw-font-markdown-h1', rel(24, fontSize), 34, base)
|
|
597
|
+
token('dsw-font-markdown-h2', rel(22, fontSize), 32, base)
|
|
598
|
+
token('dsw-font-markdown-h3', rel(20, fontSize), 30, base)
|
|
599
|
+
token('dsw-font-markdown-h4', fontSize, 28, base)
|
|
600
|
+
token('dsw-font-markdown-code', codePx(14 / 13), 22, code)
|
|
601
|
+
token('dsw-font-markdown-code-block', codePx(1), 22, codeBlock)
|
|
602
|
+
token('dsw-font-markdown-code-block-small', codePx(12 / 13), 18, codeBlock)
|
|
603
|
+
return `body{${parts.join(';')}}`
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Claude Desktop-ish markdown table look: light-gray rounded cell cards with
|
|
608
|
+
* small gaps, no borders. Cells share the theme's inline-code background and
|
|
609
|
+
* follow the message font-size setting. Alignment is left to the markdown
|
|
610
|
+
* renderer, so headers and cells always match.
|
|
611
|
+
*/
|
|
612
|
+
const CLAUDE_TABLE_CSS = `
|
|
613
|
+
div[data-slot="conversation.chat.node"] table{
|
|
614
|
+
border-collapse:separate !important;
|
|
615
|
+
border-spacing:3px !important;
|
|
616
|
+
width:100% !important;
|
|
617
|
+
/* Fill the column like Claude Desktop, but never squeeze below the natural
|
|
618
|
+
(no-wrap) width: a table too wide to fit keeps its real width and scrolls
|
|
619
|
+
inside the stock table wrapper instead of hiding columns. */
|
|
620
|
+
min-width:max-content !important;
|
|
621
|
+
border:none !important;
|
|
622
|
+
font-size:var(--dsw-font-markdown-base-font-size) !important;
|
|
623
|
+
}
|
|
624
|
+
div[data-slot="conversation.chat.node"] table thead th{
|
|
625
|
+
background:var(--dsw-alias-markdown-inline-code) !important;
|
|
626
|
+
color:inherit !important;
|
|
627
|
+
font-weight:400 !important;
|
|
628
|
+
font-size:inherit !important;
|
|
629
|
+
padding:7px 10px !important;
|
|
630
|
+
border:none !important;
|
|
631
|
+
border-radius:6px !important;
|
|
632
|
+
}
|
|
633
|
+
div[data-slot="conversation.chat.node"] table tbody td{
|
|
634
|
+
background:var(--dsw-alias-markdown-inline-code) !important;
|
|
635
|
+
color:inherit !important;
|
|
636
|
+
font-size:inherit !important;
|
|
637
|
+
padding:7px 10px !important;
|
|
638
|
+
vertical-align:top !important;
|
|
639
|
+
border:none !important;
|
|
640
|
+
border-radius:6px !important;
|
|
641
|
+
}
|
|
642
|
+
div[data-slot="conversation.chat.node"] table code,
|
|
643
|
+
div[data-slot="conversation.chat.node"] table pre{
|
|
644
|
+
background:transparent !important;
|
|
645
|
+
border:none !important;
|
|
646
|
+
box-shadow:none !important;
|
|
647
|
+
}
|
|
648
|
+
`
|
|
649
|
+
|
|
650
|
+
function buildRuntimeCss(value: ResolvedTweaks): string {
|
|
651
|
+
const rules: string[] = []
|
|
652
|
+
rules.push(buildFontCss(value.fontSize, value.lineHeight, value.codeFontSize))
|
|
653
|
+
// User-sent messages use their own fixed font-size (not the markdown tokens);
|
|
654
|
+
// route them through the same base so they follow the fontSize setting too.
|
|
655
|
+
// `steering` messages are sent while the agent is busy (busyEnter: steer);
|
|
656
|
+
// `[data-pending-steering]` is their in-flight bubble before it becomes durable.
|
|
657
|
+
rules.push(`[data-chat-flow-kind="user"] [class^="_text_"],[data-chat-flow-kind="steering"] [class^="_text_"],[data-pending-steering] [class^="_text_"]{font-size:var(--dsw-font-markdown-base-font-size) !important}`)
|
|
658
|
+
// Composer input follows the same base size. The visible text is rendered by
|
|
659
|
+
// the hidden-textarea + backdrop/mirror pattern: the textarea is transparent,
|
|
660
|
+
// the `data-input-backdrop` paints the text you see, and `data-input-mirror`
|
|
661
|
+
// drives auto-grow. All three (plus the placeholder) must share the size.
|
|
662
|
+
// Only font-size is touched: line-height is part of the auto-grow/caret
|
|
663
|
+
// metrics, so overriding it misplaces the caret.
|
|
664
|
+
rules.push(`[data-composer-card="true"] textarea,[data-composer-card="true"] [data-input-backdrop],[data-composer-card="true"] [data-input-mirror]{font-size:var(--dsw-font-markdown-base-font-size) !important}`)
|
|
665
|
+
rules.push(`[data-composer-card="true"] textarea::placeholder{font-size:var(--dsw-font-markdown-base-font-size) !important}`)
|
|
666
|
+
// Markdown table cells are pinned by DSH (15px); let every table style follow
|
|
667
|
+
// the fontSize setting as well.
|
|
668
|
+
rules.push(`div[data-slot="conversation.chat.node"] table th,div[data-slot="conversation.chat.node"] table td{font-size:var(--dsw-font-markdown-base-font-size) !important}`)
|
|
669
|
+
// Inline code is pinned by DSH to 0.875em of the surrounding text (it ignores
|
|
670
|
+
// the code token); scale that em by how far the chosen code size sits from
|
|
671
|
+
// the stock ratio (a 13px block at this body size).
|
|
672
|
+
const stockCodeBlock = value.fontSize * (13 / 16)
|
|
673
|
+
if (Math.abs(value.codeFontSize - stockCodeBlock) > 0.5) {
|
|
674
|
+
const em = (0.875 * (value.codeFontSize / stockCodeBlock)).toFixed(3)
|
|
675
|
+
rules.push(`div[data-slot="conversation.chat.node"] div[class*="_markdown_"] :not(pre)>code{font-size:${em}em !important}`)
|
|
676
|
+
}
|
|
677
|
+
// 行高: the base vertical rhythm of the reply area. When it differs from
|
|
678
|
+
// the stock 16px, every vertical spacing scales from it proportionally:
|
|
679
|
+
// ① gaps between message rows (Think ↔ tool cards like pwsh, user ↔
|
|
680
|
+
// assistant) — the chat flow column;
|
|
681
|
+
// ② gaps between content blocks inside one assistant reply (Think ↔ text) —
|
|
682
|
+
// the Assistant Markdown block container (`Sxvs8a_body`, CSS-module
|
|
683
|
+
// hashed, stable within the pinned rc.8 conversation package);
|
|
684
|
+
// ③ the markdown typography rhythm: paragraphs (`p` has a stock `margin:
|
|
685
|
+
// 16px 0` that must be overridden on both axes, not just bottom, or it
|
|
686
|
+
// can never tighten below 16), list margins, list-item gaps (stock 6px),
|
|
687
|
+
// paragraphs inside list items (stock 8px), code blocks, headings and
|
|
688
|
+
// rules — all scaled from the 行高 unit.
|
|
689
|
+
// Text line-height itself scales in buildFontCss above.
|
|
690
|
+
if (value.lineHeight !== DEFAULT_LINE_HEIGHT) {
|
|
691
|
+
const n = value.lineHeight
|
|
692
|
+
const md = `div[data-slot="conversation.chat.node"] div[class*="_markdown_"]`
|
|
693
|
+
const liGap = Math.max(2, Math.round((6 * n) / DEFAULT_LINE_HEIGHT))
|
|
694
|
+
const liPGap = Math.max(2, Math.round((8 * n) / DEFAULT_LINE_HEIGHT))
|
|
695
|
+
rules.push(`div[data-slot="conversation.view"] .Md3f7G_column{gap:${n}px !important}`)
|
|
696
|
+
rules.push(`div[data-slot="conversation.chat.node"] .Sxvs8a_body{gap:${n}px !important}`)
|
|
697
|
+
rules.push(`${md} p{margin:${n}px 0 !important}`)
|
|
698
|
+
rules.push(`${md} ul,${md} ol{margin:${n}px 0 !important}`)
|
|
699
|
+
rules.push(`${md} pre{margin:${n}px 0 !important}`)
|
|
700
|
+
// Code blocks: the pre's inner vertical padding is stock 16px (shiki
|
|
701
|
+
// default); scale it so the block's own height follows 行高 too. Only
|
|
702
|
+
// top/bottom is touched, horizontal padding stays as the theme sets it.
|
|
703
|
+
const codePad = Math.max(4, Math.round((16 * n) / DEFAULT_LINE_HEIGHT))
|
|
704
|
+
rules.push(`${md} pre{padding-top:${codePad}px !important;padding-bottom:${codePad}px !important}`)
|
|
705
|
+
// Table cells: vertical cell padding is stock 10px. Scale it for the
|
|
706
|
+
// default table style; the Claude preset keeps its own compact 7px cell
|
|
707
|
+
// padding (that look is the point of the preset).
|
|
708
|
+
if (value.tableStyle !== 'claude') {
|
|
709
|
+
const cellPad = Math.max(2, Math.round((10 * n) / DEFAULT_LINE_HEIGHT))
|
|
710
|
+
rules.push(`${md} table th,${md} table td{padding-top:${cellPad}px !important;padding-bottom:${cellPad}px !important}`)
|
|
711
|
+
}
|
|
712
|
+
rules.push(`${md} li:not(:first-child){margin-top:${liGap}px !important}`)
|
|
713
|
+
rules.push(`${md} li>p{margin:${liPGap}px 0 !important}`)
|
|
714
|
+
rules.push(`${md} h1,${md} h2,${md} h3{margin:${2 * n}px 0 ${n}px !important}`)
|
|
715
|
+
rules.push(`${md} h4,${md} h5,${md} h6{margin:${n}px 0 !important}`)
|
|
716
|
+
rules.push(`${md} hr{margin:${2 * n}px 0 !important}`)
|
|
717
|
+
rules.push(`${md} blockquote{margin:${n}px 0 0 !important}`)
|
|
718
|
+
// Keep DSH's flush edges: the first/last block of a reply has zero outer
|
|
719
|
+
// margin (stock `>*:first-child` / `>*:last-child`), so a message starts
|
|
720
|
+
// and ends tight and the spacing to the next node stays the column gap.
|
|
721
|
+
rules.push(`${md} > :first-child{margin-top:0 !important}`)
|
|
722
|
+
rules.push(`${md} > :last-child{margin-bottom:0 !important}`)
|
|
723
|
+
}
|
|
724
|
+
if (value.tableStyle === 'claude') {
|
|
725
|
+
rules.push(CLAUDE_TABLE_CSS)
|
|
726
|
+
}
|
|
727
|
+
return rules.join('\n')
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function runtimeStyleElement(): HTMLStyleElement {
|
|
731
|
+
const id = 'dsh-ui-tweaks-runtime'
|
|
732
|
+
let style = document.querySelector<HTMLStyleElement>(`style[data-plugin-css="${id}"]`)
|
|
733
|
+
if (style === null) {
|
|
734
|
+
style = document.createElement('style')
|
|
735
|
+
style.dataset.plugin = 'dsh-ui-tweaks'
|
|
736
|
+
style.dataset.pluginCss = id
|
|
737
|
+
document.head.appendChild(style)
|
|
738
|
+
}
|
|
739
|
+
return style
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const BASE_CSS = `
|
|
743
|
+
.dut-settings{display:grid;gap:8px;max-width:680px;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
744
|
+
.dut-settings-header{display:flex;align-items:flex-start;gap:10px;padding:2px 2px 0}
|
|
745
|
+
.dut-logo{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:9px;border:1px solid var(--dsw-alias-border-l1);background:linear-gradient(135deg,color-mix(in srgb,var(--dsw-alias-state-business-primary) 16%,transparent),transparent);line-height:1}
|
|
746
|
+
.dut-settings-header h2{font-size:16px;letter-spacing:-.01em;margin:0 0 2px}
|
|
747
|
+
.dut-settings-header p{max-width:600px;margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45}
|
|
748
|
+
.dut-panel{display:grid;gap:0;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv1);overflow:hidden}
|
|
749
|
+
.dut-section-label{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);padding:9px 16px 4px}
|
|
750
|
+
.dut-field{display:grid;gap:6px;padding:7px 16px 10px}
|
|
751
|
+
.dut-field+.dut-field{border-top:1px solid var(--dsw-alias-border-l1)}
|
|
752
|
+
/* Two-column toggle grid: hairline dividers come from the 1px gap painting the
|
|
753
|
+
panel's border color through; cells repaint the panel background above it. */
|
|
754
|
+
.dut-grid{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1px;background:var(--dsw-alias-border-l1)}
|
|
755
|
+
.dut-grid>.dut-section-label{grid-column:1/-1;background:var(--dsw-alias-bg-layer-1)}
|
|
756
|
+
.dut-grid .dut-field{background:var(--dsw-alias-bg-layer-1)}
|
|
757
|
+
.dut-grid .dut-field+.dut-field{border-top:none}
|
|
758
|
+
/* Subordinate rows (task-alert sub-toggles under the master switch): dimmed
|
|
759
|
+
whole-row so the dependency reads at a glance; buttons disable separately. */
|
|
760
|
+
.dut-grid .dut-field.dut-sub-off{opacity:.5}
|
|
761
|
+
/* Task-alerts section packs its six sub-toggle cells TWO per row (the master
|
|
762
|
+
row and the section label span all columns); very narrow panels fall back
|
|
763
|
+
to a single column so nothing squeezes. */
|
|
764
|
+
.dut-grid.dut-grid-half{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
765
|
+
.dut-grid .dut-field.dut-span-all{grid-column:1/-1}
|
|
766
|
+
@media (max-width:640px){.dut-grid.dut-grid-half{grid-template-columns:minmax(0,1fr)}}
|
|
767
|
+
.dut-field-top{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
|
|
768
|
+
.dut-field-top>span{font-size:13.5px;font-weight:600}
|
|
769
|
+
.dut-label{display:inline-flex;align-items:center;gap:6px}
|
|
770
|
+
.dut-hint{flex:none;display:inline-grid;place-items:center;width:15px;height:15px;border-radius:50%;border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:9.5px;font-weight:600;font-style:normal;line-height:1;cursor:help;user-select:none;transition:color .15s ease,border-color .15s ease}
|
|
771
|
+
.dut-hint:hover,.dut-hint:focus-visible{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}
|
|
772
|
+
.dut-hint-pop{position:fixed;z-index:9999;width:max-content;max-width:300px;padding:8px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:11.5px;line-height:1.5;box-shadow:0 4px 16px rgba(0,0,0,.14);pointer-events:none}
|
|
773
|
+
.dut-controls{display:flex;align-items:center;gap:8px}
|
|
774
|
+
.dut-stepper{display:inline-flex;align-items:center;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}
|
|
775
|
+
.dut-stepper button{width:28px;height:28px;border:none;background:transparent;color:inherit;font-size:15px;font-weight:500;line-height:1;cursor:pointer;display:grid;place-items:center;transition:background .15s ease}
|
|
776
|
+
.dut-stepper button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
777
|
+
.dut-stepper button:disabled{opacity:.35;cursor:default}
|
|
778
|
+
.dut-stepper input{box-sizing:border-box;width:60px;height:28px;border:none;border-left:1px solid var(--dsw-alias-border-l1);border-right:1px solid var(--dsw-alias-border-l1);background:transparent;color:inherit;font:inherit;font-size:13px;text-align:center;-moz-appearance:textfield}
|
|
779
|
+
.dut-stepper input::-webkit-outer-spin-button,.dut-stepper input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
|
|
780
|
+
.dut-stepper input:focus{outline:none}
|
|
781
|
+
.dut-seg{display:inline-flex;padding:3px;gap:3px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
|
|
782
|
+
.dut-seg button{border:none;border-radius:7px;padding:5px 12px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;cursor:pointer;transition:background .15s ease,color .15s ease}
|
|
783
|
+
.dut-seg button:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
784
|
+
.dut-seg button.dut-seg-active{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-state-business-primary);font-weight:600;box-shadow:none}
|
|
785
|
+
.dut-seg button.dut-seg-active:hover:not(:disabled){color:var(--dsw-alias-state-business-primary)}
|
|
786
|
+
.dut-seg button:disabled{opacity:.45;cursor:default}
|
|
787
|
+
/* The selected Settings-section tab — the stock shell paints a barely-there
|
|
788
|
+
grey; brand-tint it so the selection reads clearly (and in the README shot). */
|
|
789
|
+
[role="dialog"] nav button[aria-selected="true"],[role="dialog"] nav button[aria-current="true"]{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-state-business-primary);font-weight:600}
|
|
790
|
+
.dut-btn{display:inline-flex;align-items:center;height:26px;padding:0 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11.5px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}
|
|
791
|
+
.dut-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
792
|
+
.dut-btn.dut-btn-active{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 45%,transparent);color:var(--dsw-alias-state-business-primary)}
|
|
793
|
+
.dut-btn.dut-btn-active:hover{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent);color:var(--dsw-alias-state-business-primary)}
|
|
794
|
+
.dut-btn:disabled{opacity:.4;cursor:default}
|
|
795
|
+
.dut-status{justify-self:start;font-size:11.5px;padding:3px 10px;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary);animation:dut-fadein .18s ease}
|
|
796
|
+
@keyframes dut-fadein{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}
|
|
797
|
+
.dut-loading{padding:16px;border-radius:12px;background:var(--dsw-alias-bg-layer-2);font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
798
|
+
.dut-alert{padding:10px 12px;border-radius:10px;font-size:12px;line-height:1.5}
|
|
799
|
+
.dut-alert.warning{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}
|
|
800
|
+
.dut-alert.error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
801
|
+
`
|
|
802
|
+
|
|
803
|
+
function installBaseStyles(): () => void {
|
|
804
|
+
const id = 'dsh-ui-tweaks-base'
|
|
805
|
+
const existing = document.querySelector<HTMLStyleElement>(`style[data-plugin-css="${id}"]`)
|
|
806
|
+
if (existing !== null) return () => {}
|
|
807
|
+
const style = document.createElement('style')
|
|
808
|
+
style.dataset.plugin = 'dsh-ui-tweaks'
|
|
809
|
+
style.dataset.pluginCss = id
|
|
810
|
+
style.textContent = BASE_CSS
|
|
811
|
+
document.head.appendChild(style)
|
|
812
|
+
return () => { style.remove() }
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async function apiRequest<T>(init?: RequestInit): Promise<T> {
|
|
816
|
+
const response = await fetch(SETTINGS_ROUTE, { credentials: 'same-origin', ...init })
|
|
817
|
+
const body = await response.json() as ApiSuccess<T> | ApiFailure
|
|
818
|
+
if (!response.ok || !body.ok) {
|
|
819
|
+
const failure = body as ApiFailure
|
|
820
|
+
throw new Error(failure.error?.message ?? `UI Tweaks request failed with HTTP ${response.status}`)
|
|
821
|
+
}
|
|
822
|
+
return body.value
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/** Client-side snapshot store fed by the same-origin Settings route. */
|
|
826
|
+
interface SettingsState {
|
|
827
|
+
status: 'loading' | 'ready' | 'error'
|
|
828
|
+
writable: boolean
|
|
829
|
+
value: TweaksValue | undefined
|
|
830
|
+
revision: number | undefined
|
|
831
|
+
error?: string
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Small external store shared by the Settings route and the CSS engine. */
|
|
835
|
+
export class SettingsClient {
|
|
836
|
+
private state: SettingsState = { status: 'loading', writable: false, value: undefined, revision: undefined }
|
|
837
|
+
private listeners = new Set<() => void>()
|
|
838
|
+
private generation = 0
|
|
839
|
+
|
|
840
|
+
subscribe = (listener: () => void): (() => void) => {
|
|
841
|
+
this.listeners.add(listener)
|
|
842
|
+
return () => { this.listeners.delete(listener) }
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
getSnapshot = (): SettingsState => this.state
|
|
846
|
+
|
|
847
|
+
private publish(next: SettingsState): void {
|
|
848
|
+
this.state = next
|
|
849
|
+
for (const listener of this.listeners) listener()
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async load(): Promise<void> {
|
|
853
|
+
const generation = ++this.generation
|
|
854
|
+
if (this.state.status === 'loading') this.publish({ ...this.state, status: 'loading' })
|
|
855
|
+
try {
|
|
856
|
+
const snapshot = await apiRequest<UITweaksSnapshot>()
|
|
857
|
+
if (generation !== this.generation) return
|
|
858
|
+
this.publish({
|
|
859
|
+
status: 'ready',
|
|
860
|
+
writable: snapshot.writable,
|
|
861
|
+
value: snapshot.value,
|
|
862
|
+
revision: snapshot.revision,
|
|
863
|
+
})
|
|
864
|
+
} catch (error) {
|
|
865
|
+
if (generation !== this.generation) return
|
|
866
|
+
this.publish({ ...this.state, status: 'error', error: error instanceof Error ? error.message : String(error) })
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
private async post(payload: unknown): Promise<void> {
|
|
871
|
+
const generation = ++this.generation
|
|
872
|
+
const snapshot = await apiRequest<UITweaksSnapshot>({
|
|
873
|
+
method: 'POST',
|
|
874
|
+
headers: { 'Content-Type': 'application/json' },
|
|
875
|
+
body: JSON.stringify(payload),
|
|
876
|
+
})
|
|
877
|
+
if (generation !== this.generation) return
|
|
878
|
+
this.publish({
|
|
879
|
+
status: 'ready',
|
|
880
|
+
writable: snapshot.writable,
|
|
881
|
+
value: snapshot.value,
|
|
882
|
+
revision: snapshot.revision,
|
|
883
|
+
})
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async set(field: string, value: unknown): Promise<void> {
|
|
887
|
+
await this.post({ action: 'set', field, value, expectedRevision: this.state.revision ?? 0 })
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async unset(field: string): Promise<void> {
|
|
891
|
+
await this.post({ action: 'unset', field, expectedRevision: this.state.revision ?? 0 })
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/** Required client services: slots (settings.section), locale, sessions (git bar, whale, archive, notifier), the slash-command registry, and the scope-addressed conversation face. */
|
|
896
|
+
export const inject = ['slots', 'locale', 'sessions', 'commandUi', 'conversation']
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Hover/focus hint: a small ⓘ next to the field label; the hint text renders
|
|
900
|
+
* in a fixed-position bubble portaled to <body> (so panel `overflow:hidden`
|
|
901
|
+
* can never clip it), measured in a layout effect to prefer the space above
|
|
902
|
+
* the anchor and flip below near the viewport top. No layout shift: hints
|
|
903
|
+
* never occupy flow height.
|
|
904
|
+
*/
|
|
905
|
+
function Hint({ text }: { text: string }) {
|
|
906
|
+
const anchorRef = useRef<HTMLSpanElement>(null)
|
|
907
|
+
const popRef = useRef<HTMLDivElement>(null)
|
|
908
|
+
const [open, setOpen] = useState(false)
|
|
909
|
+
const [pos, setPos] = useState<{ top: number; left: number }>({ top: -9999, left: -9999 })
|
|
910
|
+
useLayoutEffect(() => {
|
|
911
|
+
if (!open) return
|
|
912
|
+
const anchor = anchorRef.current?.getBoundingClientRect()
|
|
913
|
+
const pop = popRef.current
|
|
914
|
+
if (anchor === undefined || pop === null) return
|
|
915
|
+
let left = Math.min(Math.max(8, anchor.left), window.innerWidth - pop.offsetWidth - 8)
|
|
916
|
+
let top = anchor.top - pop.offsetHeight - 8
|
|
917
|
+
if (top < 8) top = anchor.bottom + 8
|
|
918
|
+
setPos({ top, left })
|
|
919
|
+
}, [open])
|
|
920
|
+
return (
|
|
921
|
+
<>
|
|
922
|
+
<span
|
|
923
|
+
ref={anchorRef}
|
|
924
|
+
className="dut-hint"
|
|
925
|
+
role="note"
|
|
926
|
+
aria-label={text}
|
|
927
|
+
tabIndex={0}
|
|
928
|
+
onMouseEnter={() => { setOpen(true) }}
|
|
929
|
+
onMouseLeave={() => { setOpen(false) }}
|
|
930
|
+
onFocus={() => { setOpen(true) }}
|
|
931
|
+
onBlur={() => { setOpen(false) }}
|
|
932
|
+
>i</span>
|
|
933
|
+
{open && createPortal(
|
|
934
|
+
<div ref={popRef} className="dut-hint-pop" style={{ top: pos.top, left: pos.left }}>{text}</div>,
|
|
935
|
+
document.body,
|
|
936
|
+
)}
|
|
937
|
+
</>
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
type SettingsSectionProps = PropsRuntime<'settings.section'> & {
|
|
942
|
+
controller: SettingsClient
|
|
943
|
+
t: Translate
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function SettingsSection({ controller, t }: SettingsSectionProps) {
|
|
947
|
+
const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot)
|
|
948
|
+
const resolved = resolveValue(state.value)
|
|
949
|
+
const writable = state.writable
|
|
950
|
+
const [draft, setDraft] = useState<string>(String(resolved.fontSize))
|
|
951
|
+
const [codeDraft, setCodeDraft] = useState<string>(String(resolved.codeFontSize))
|
|
952
|
+
const [lineHeightDraft, setLineHeightDraft] = useState<string>(String(resolved.lineHeight))
|
|
953
|
+
const [status, setStatus] = useState<LocaleKey | undefined>(undefined)
|
|
954
|
+
|
|
955
|
+
useEffect(() => { if (state.status === 'loading' && state.value === undefined) void controller.load() }, [controller, state.status, state.value])
|
|
956
|
+
useEffect(() => { setDraft(String(resolved.fontSize)) }, [resolved.fontSize])
|
|
957
|
+
useEffect(() => { setCodeDraft(String(resolved.codeFontSize)) }, [resolved.codeFontSize])
|
|
958
|
+
useEffect(() => { setLineHeightDraft(String(resolved.lineHeight)) }, [resolved.lineHeight])
|
|
959
|
+
useEffect(() => {
|
|
960
|
+
if (status === undefined) return
|
|
961
|
+
const timer = setTimeout(() => { setStatus(undefined) }, 1800)
|
|
962
|
+
return () => { clearTimeout(timer) }
|
|
963
|
+
}, [status])
|
|
964
|
+
|
|
965
|
+
const commitFontSize = (raw: string): void => {
|
|
966
|
+
setDraft(raw)
|
|
967
|
+
const parsed = Number(raw)
|
|
968
|
+
if (!Number.isFinite(parsed)) return
|
|
969
|
+
const clamped = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, Math.round(parsed)))
|
|
970
|
+
setDraft(String(clamped))
|
|
971
|
+
void controller.set('fontSize', clamped).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const commitLineHeight = (raw: string): void => {
|
|
975
|
+
setLineHeightDraft(raw)
|
|
976
|
+
const parsed = Number(raw)
|
|
977
|
+
if (!Number.isFinite(parsed)) return
|
|
978
|
+
const clamped = Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, Math.round(parsed)))
|
|
979
|
+
setLineHeightDraft(String(clamped))
|
|
980
|
+
void controller.set('lineHeight', clamped).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const commitCodeSize = (raw: string): void => {
|
|
984
|
+
setCodeDraft(raw)
|
|
985
|
+
const parsed = Number(raw)
|
|
986
|
+
if (!Number.isFinite(parsed)) return
|
|
987
|
+
const clamped = Math.min(MAX_CODE_FONT_SIZE, Math.max(MIN_CODE_FONT_SIZE, Math.round(parsed)))
|
|
988
|
+
setCodeDraft(String(clamped))
|
|
989
|
+
void controller.set('codeFontSize', clamped).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const pickTableStyle = (raw: string): void => {
|
|
993
|
+
void controller.set('tableStyle', raw === 'claude' ? 'claude' : 'default').then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const stepFontSize = (delta: number): void => {
|
|
997
|
+
const next = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, resolved.fontSize + delta))
|
|
998
|
+
setDraft(String(next))
|
|
999
|
+
void controller.set('fontSize', next).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const stepLineHeight = (delta: number): void => {
|
|
1003
|
+
const next = Math.min(MAX_LINE_HEIGHT, Math.max(MIN_LINE_HEIGHT, resolved.lineHeight + delta))
|
|
1004
|
+
setLineHeightDraft(String(next))
|
|
1005
|
+
void controller.set('lineHeight', next).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const stepCodeSize = (delta: number): void => {
|
|
1009
|
+
const next = Math.min(MAX_CODE_FONT_SIZE, Math.max(MIN_CODE_FONT_SIZE, resolved.codeFontSize + delta))
|
|
1010
|
+
setCodeDraft(String(next))
|
|
1011
|
+
void controller.set('codeFontSize', next).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const setGitBar = (value: boolean): void => {
|
|
1015
|
+
void controller.set('gitBarEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const setArchiveManager = (value: boolean): void => {
|
|
1019
|
+
void controller.set('archiveManagerEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const setMcpManager = (value: boolean): void => {
|
|
1023
|
+
void controller.set('mcpManagerEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const setInitCommand = (value: boolean): void => {
|
|
1027
|
+
void controller.set('initCommandEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const setWhaleIndicator = (value: boolean): void => {
|
|
1031
|
+
void controller.set('whaleIndicatorEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const setPreciseCacheHit = (value: boolean): void => {
|
|
1035
|
+
void controller.set('preciseCacheHitEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/** Master switch; enabling also asks for notification permission inside this click gesture. */
|
|
1039
|
+
const setNotifications = (value: boolean): void => {
|
|
1040
|
+
if (value) requestNotifyPermission()
|
|
1041
|
+
void controller.set('notificationsEnabled', value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/** Channel/event toggles share one setter shape; the system-notification one requests permission too. */
|
|
1045
|
+
const setNotifyField = (field: 'notifyOnlyWhenHidden' | 'notifyOnComplete' | 'notifyOnInteraction' | 'notifyTitleFlash' | 'notifySystemNotification' | 'notifySound', value: boolean): void => {
|
|
1046
|
+
if (field === 'notifySystemNotification' && value) requestNotifyPermission()
|
|
1047
|
+
void controller.set(field, value).then(() => { setStatus('applied') }).catch(() => { setStatus('unavailable') })
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const testNotifications = (): void => {
|
|
1051
|
+
requestNotifyPermission()
|
|
1052
|
+
previewAlerts(
|
|
1053
|
+
{
|
|
1054
|
+
titleFlash: resolved.notifyTitleFlash,
|
|
1055
|
+
systemNotification: resolved.notifySystemNotification,
|
|
1056
|
+
sound: resolved.notifySound,
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
notifyTitleDone: t('notifyTitleDone'),
|
|
1060
|
+
notifyTitleAborted: t('notifyTitleAborted'),
|
|
1061
|
+
notifyTitleFailed: t('notifyTitleFailed'),
|
|
1062
|
+
notifyTitlePending: t('notifyTitlePending'),
|
|
1063
|
+
bodyComplete: t('bodyComplete'),
|
|
1064
|
+
bodyAborted: t('bodyAborted'),
|
|
1065
|
+
bodyFailed: t('bodyFailed'),
|
|
1066
|
+
bodyApproval: t('bodyApproval'),
|
|
1067
|
+
bodyPlan: t('bodyPlan'),
|
|
1068
|
+
bodyQuestion: t('bodyQuestion'),
|
|
1069
|
+
},
|
|
1070
|
+
)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const reset = (field: 'fontSize' | 'lineHeight' | 'tableStyle' | 'gitBarEnabled' | 'archiveManagerEnabled' | 'mcpManagerEnabled'): void => {
|
|
1074
|
+
void controller.unset(field).then(() => { setStatus('resetDone') }).catch(() => { setStatus('unavailable') })
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/** Code size reset clears BOTH keys: the px input and the legacy percentage. */
|
|
1078
|
+
const resetCodeSize = (): void => {
|
|
1079
|
+
void (async () => {
|
|
1080
|
+
try {
|
|
1081
|
+
await controller.unset('codeFontSize')
|
|
1082
|
+
await controller.unset('codeFontScale')
|
|
1083
|
+
setStatus('resetDone')
|
|
1084
|
+
} catch {
|
|
1085
|
+
setStatus('unavailable')
|
|
1086
|
+
}
|
|
1087
|
+
})()
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (state.status === 'loading' && state.value === undefined) {
|
|
1091
|
+
return <div className="dut-settings"><div className="dut-loading">{t('loading')}</div></div>
|
|
1092
|
+
}
|
|
1093
|
+
if (state.status === 'error') {
|
|
1094
|
+
return <div className="dut-settings"><div className="dut-alert error">{t('unavailable')}</div></div>
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return (
|
|
1098
|
+
<div className="dut-settings">
|
|
1099
|
+
<header className="dut-settings-header">
|
|
1100
|
+
<div className="dut-logo"><ProjectLogoIcon className="dut-settings-logo" /></div>
|
|
1101
|
+
<div>
|
|
1102
|
+
<h2>{t('settingsTitle')}</h2>
|
|
1103
|
+
<p>{t('settingsIntro')}</p>
|
|
1104
|
+
</div>
|
|
1105
|
+
</header>
|
|
1106
|
+
{!writable ? <div className="dut-alert warning">{t('readOnly')}</div> : null}
|
|
1107
|
+
{status === undefined ? null : <div className="dut-status">{t(status)}</div>}
|
|
1108
|
+
|
|
1109
|
+
<section className="dut-panel">
|
|
1110
|
+
<div className="dut-section-label">{t('sectionText')}</div>
|
|
1111
|
+
<div className="dut-field">
|
|
1112
|
+
<div className="dut-field-top">
|
|
1113
|
+
<span className="dut-label">{t('fontSize')}<Hint text={t('fontSizeHint')} /></span>
|
|
1114
|
+
<div className="dut-controls">
|
|
1115
|
+
<div className="dut-stepper">
|
|
1116
|
+
<button type="button" aria-label="−" disabled={!writable || resolved.fontSize <= MIN_FONT_SIZE} onClick={() => { stepFontSize(-1) }}>−</button>
|
|
1117
|
+
<input
|
|
1118
|
+
type="number"
|
|
1119
|
+
min={MIN_FONT_SIZE}
|
|
1120
|
+
max={MAX_FONT_SIZE}
|
|
1121
|
+
step={1}
|
|
1122
|
+
value={draft}
|
|
1123
|
+
disabled={!writable}
|
|
1124
|
+
onChange={(event) => { setDraft(event.target.value) }}
|
|
1125
|
+
onBlur={(event) => { commitFontSize(event.target.value) }}
|
|
1126
|
+
onKeyDown={(event) => { if (event.key === 'Enter') commitFontSize((event.target as HTMLInputElement).value) }}
|
|
1127
|
+
/>
|
|
1128
|
+
<button type="button" aria-label="+" disabled={!writable || resolved.fontSize >= MAX_FONT_SIZE} onClick={() => { stepFontSize(1) }}>+</button>
|
|
1129
|
+
</div>
|
|
1130
|
+
<button type="button" className={'dut-btn' + (resolved.fontSize === DEFAULT_FONT_SIZE ? ' dut-btn-active' : '')} disabled={!writable} onClick={() => { reset('fontSize') }}>{t('defaultAction')}</button>
|
|
1131
|
+
</div>
|
|
1132
|
+
</div>
|
|
1133
|
+
</div>
|
|
1134
|
+
<div className="dut-field">
|
|
1135
|
+
<div className="dut-field-top">
|
|
1136
|
+
<span className="dut-label">{t('lineHeight')}<Hint text={t('lineHeightHint')} /></span>
|
|
1137
|
+
<div className="dut-controls">
|
|
1138
|
+
<div className="dut-stepper">
|
|
1139
|
+
<button type="button" aria-label="−" disabled={!writable || resolved.lineHeight <= MIN_LINE_HEIGHT} onClick={() => { stepLineHeight(-2) }}>−</button>
|
|
1140
|
+
<input
|
|
1141
|
+
type="number"
|
|
1142
|
+
min={MIN_LINE_HEIGHT}
|
|
1143
|
+
max={MAX_LINE_HEIGHT}
|
|
1144
|
+
step={2}
|
|
1145
|
+
value={lineHeightDraft}
|
|
1146
|
+
disabled={!writable}
|
|
1147
|
+
onChange={(event) => { setLineHeightDraft(event.target.value) }}
|
|
1148
|
+
onBlur={(event) => { commitLineHeight(event.target.value) }}
|
|
1149
|
+
onKeyDown={(event) => { if (event.key === 'Enter') commitLineHeight((event.target as HTMLInputElement).value) }}
|
|
1150
|
+
/>
|
|
1151
|
+
<button type="button" aria-label="+" disabled={!writable || resolved.lineHeight >= MAX_LINE_HEIGHT} onClick={() => { stepLineHeight(2) }}>+</button>
|
|
1152
|
+
</div>
|
|
1153
|
+
<button type="button" className={'dut-btn' + (resolved.lineHeight === DEFAULT_LINE_HEIGHT ? ' dut-btn-active' : '')} disabled={!writable} onClick={() => { reset('lineHeight') }}>{t('defaultAction')}</button>
|
|
1154
|
+
</div>
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>
|
|
1157
|
+
<div className="dut-field">
|
|
1158
|
+
<div className="dut-field-top">
|
|
1159
|
+
<span className="dut-label">{t('codeFontSize')}<Hint text={t('codeFontSizeHint')} /></span>
|
|
1160
|
+
<div className="dut-controls">
|
|
1161
|
+
<div className="dut-stepper">
|
|
1162
|
+
<button type="button" aria-label="−" disabled={!writable || resolved.codeFontSize <= MIN_CODE_FONT_SIZE} onClick={() => { stepCodeSize(-1) }}>−</button>
|
|
1163
|
+
<input
|
|
1164
|
+
type="number"
|
|
1165
|
+
min={MIN_CODE_FONT_SIZE}
|
|
1166
|
+
max={MAX_CODE_FONT_SIZE}
|
|
1167
|
+
step={1}
|
|
1168
|
+
value={codeDraft}
|
|
1169
|
+
disabled={!writable}
|
|
1170
|
+
onChange={(event) => { setCodeDraft(event.target.value) }}
|
|
1171
|
+
onBlur={(event) => { commitCodeSize(event.target.value) }}
|
|
1172
|
+
onKeyDown={(event) => { if (event.key === 'Enter') commitCodeSize((event.target as HTMLInputElement).value) }}
|
|
1173
|
+
/>
|
|
1174
|
+
<button type="button" aria-label="+" disabled={!writable || resolved.codeFontSize >= MAX_CODE_FONT_SIZE} onClick={() => { stepCodeSize(1) }}>+</button>
|
|
1175
|
+
</div>
|
|
1176
|
+
<button type="button" className={'dut-btn' + (resolved.codeFontSize === DEFAULT_CODE_FONT_SIZE ? ' dut-btn-active' : '')} disabled={!writable} onClick={() => { resetCodeSize() }}>{t('defaultAction')}</button>
|
|
1177
|
+
</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
</div>
|
|
1180
|
+
<div className="dut-field">
|
|
1181
|
+
<div className="dut-field-top">
|
|
1182
|
+
<span className="dut-label">{t('tableStyle')}<Hint text={t('tableStyleHint')} /></span>
|
|
1183
|
+
<div className="dut-controls">
|
|
1184
|
+
<div className="dut-seg">
|
|
1185
|
+
<button type="button" className={resolved.tableStyle === 'claude' ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { pickTableStyle('claude') }}>{t('tableStyleClaude')}</button>
|
|
1186
|
+
<button type="button" className={resolved.tableStyle === 'default' ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { pickTableStyle('default') }}>{t('tableStyleDefault')}</button>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
</section>
|
|
1192
|
+
|
|
1193
|
+
<section className="dut-panel">
|
|
1194
|
+
<div className="dut-section-label">{t('sectionLayout')}</div>
|
|
1195
|
+
<div className="dut-field">
|
|
1196
|
+
<div className="dut-field-top">
|
|
1197
|
+
<span className="dut-label">{t('preciseCacheHit')}<Hint text={t('preciseCacheHitHint')} /></span>
|
|
1198
|
+
<div className="dut-controls">
|
|
1199
|
+
<div className="dut-seg">
|
|
1200
|
+
<button type="button" className={resolved.preciseCacheHitEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setPreciseCacheHit(true) }}>{t('preciseCacheHitOn')}</button>
|
|
1201
|
+
<button type="button" className={!resolved.preciseCacheHitEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setPreciseCacheHit(false) }}>{t('preciseCacheHitOff')}</button>
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>
|
|
1204
|
+
</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
</section>
|
|
1207
|
+
|
|
1208
|
+
<section className="dut-panel dut-grid">
|
|
1209
|
+
<div className="dut-section-label">{t('sectionFeatures')}</div>
|
|
1210
|
+
<div className="dut-field">
|
|
1211
|
+
<div className="dut-field-top">
|
|
1212
|
+
<span className="dut-label">{t('gitBar')}<Hint text={t('gitBarHint')} /></span>
|
|
1213
|
+
<div className="dut-controls">
|
|
1214
|
+
<div className="dut-seg">
|
|
1215
|
+
<button type="button" className={resolved.gitBarEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setGitBar(true) }}>{t('gitBarOn')}</button>
|
|
1216
|
+
<button type="button" className={!resolved.gitBarEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setGitBar(false) }}>{t('gitBarOff')}</button>
|
|
1217
|
+
</div>
|
|
1218
|
+
</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
</div>
|
|
1221
|
+
<div className="dut-field">
|
|
1222
|
+
<div className="dut-field-top">
|
|
1223
|
+
<span className="dut-label">{t('archiveManager')}<Hint text={t('archiveManagerHint')} /></span>
|
|
1224
|
+
<div className="dut-controls">
|
|
1225
|
+
<div className="dut-seg">
|
|
1226
|
+
<button type="button" className={resolved.archiveManagerEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setArchiveManager(true) }}>{t('archiveManagerOn')}</button>
|
|
1227
|
+
<button type="button" className={!resolved.archiveManagerEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setArchiveManager(false) }}>{t('archiveManagerOff')}</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
<div className="dut-field">
|
|
1233
|
+
<div className="dut-field-top">
|
|
1234
|
+
<span className="dut-label">{t('mcpManager')}<Hint text={t('mcpManagerHint')} /></span>
|
|
1235
|
+
<div className="dut-controls">
|
|
1236
|
+
<div className="dut-seg">
|
|
1237
|
+
<button type="button" className={resolved.mcpManagerEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setMcpManager(true) }}>{t('mcpManagerOn')}</button>
|
|
1238
|
+
<button type="button" className={!resolved.mcpManagerEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setMcpManager(false) }}>{t('mcpManagerOff')}</button>
|
|
1239
|
+
</div>
|
|
1240
|
+
</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
</div>
|
|
1243
|
+
<div className="dut-field">
|
|
1244
|
+
<div className="dut-field-top">
|
|
1245
|
+
<span className="dut-label">{t('initCommand')}<Hint text={t('initCommandHint')} /></span>
|
|
1246
|
+
<div className="dut-controls">
|
|
1247
|
+
<div className="dut-seg">
|
|
1248
|
+
<button type="button" className={resolved.initCommandEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setInitCommand(true) }}>{t('initCommandOn')}</button>
|
|
1249
|
+
<button type="button" className={!resolved.initCommandEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setInitCommand(false) }}>{t('initCommandOff')}</button>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
<div className="dut-field">
|
|
1255
|
+
<div className="dut-field-top">
|
|
1256
|
+
<span className="dut-label">{t('whaleIndicator')}<Hint text={t('whaleIndicatorHint')} /></span>
|
|
1257
|
+
<div className="dut-controls">
|
|
1258
|
+
<div className="dut-seg">
|
|
1259
|
+
<button type="button" className={resolved.whaleIndicatorEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setWhaleIndicator(true) }}>{t('whaleIndicatorOn')}</button>
|
|
1260
|
+
<button type="button" className={!resolved.whaleIndicatorEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setWhaleIndicator(false) }}>{t('whaleIndicatorOff')}</button>
|
|
1261
|
+
</div>
|
|
1262
|
+
</div>
|
|
1263
|
+
</div>
|
|
1264
|
+
</div>
|
|
1265
|
+
</section>
|
|
1266
|
+
<section className="dut-panel dut-grid dut-grid-half">
|
|
1267
|
+
<div className="dut-section-label">{t('sectionNotifications')}</div>
|
|
1268
|
+
<div className="dut-field dut-span-all">
|
|
1269
|
+
<div className="dut-field-top">
|
|
1270
|
+
<span className="dut-label">{t('notifications')}<Hint text={t('notificationsHint')} /></span>
|
|
1271
|
+
<div className="dut-controls">
|
|
1272
|
+
<div className="dut-seg">
|
|
1273
|
+
<button type="button" className={resolved.notificationsEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setNotifications(true) }}>{t('notifyOn')}</button>
|
|
1274
|
+
<button type="button" className={!resolved.notificationsEnabled ? 'dut-seg-active' : ''} disabled={!writable} onClick={() => { setNotifications(false) }}>{t('notifyOff')}</button>
|
|
1275
|
+
</div>
|
|
1276
|
+
<button type="button" className="dut-btn" disabled={!resolved.notificationsEnabled} onClick={() => { testNotifications() }}>{t('notifyTest')}</button>
|
|
1277
|
+
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1281
|
+
<div className="dut-field-top">
|
|
1282
|
+
<span className="dut-label">{t('notifyOnComplete')}<Hint text={t('notifyOnCompleteHint')} /></span>
|
|
1283
|
+
<div className="dut-controls">
|
|
1284
|
+
<div className="dut-seg">
|
|
1285
|
+
<button type="button" className={resolved.notifyOnComplete ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnComplete', true) }}>{t('notifyOn')}</button>
|
|
1286
|
+
<button type="button" className={!resolved.notifyOnComplete ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnComplete', false) }}>{t('notifyOff')}</button>
|
|
1287
|
+
</div>
|
|
1288
|
+
</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
</div>
|
|
1291
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1292
|
+
<div className="dut-field-top">
|
|
1293
|
+
<span className="dut-label">{t('notifyOnInteraction')}<Hint text={t('notifyOnInteractionHint')} /></span>
|
|
1294
|
+
<div className="dut-controls">
|
|
1295
|
+
<div className="dut-seg">
|
|
1296
|
+
<button type="button" className={resolved.notifyOnInteraction ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnInteraction', true) }}>{t('notifyOn')}</button>
|
|
1297
|
+
<button type="button" className={!resolved.notifyOnInteraction ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnInteraction', false) }}>{t('notifyOff')}</button>
|
|
1298
|
+
</div>
|
|
1299
|
+
</div>
|
|
1300
|
+
</div>
|
|
1301
|
+
</div>
|
|
1302
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1303
|
+
<div className="dut-field-top">
|
|
1304
|
+
<span className="dut-label">{t('notifyOnlyWhenHidden')}<Hint text={t('notifyOnlyWhenHiddenHint')} /></span>
|
|
1305
|
+
<div className="dut-controls">
|
|
1306
|
+
<div className="dut-seg">
|
|
1307
|
+
<button type="button" className={resolved.notifyOnlyWhenHidden ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnlyWhenHidden', true) }}>{t('notifyOn')}</button>
|
|
1308
|
+
<button type="button" className={!resolved.notifyOnlyWhenHidden ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyOnlyWhenHidden', false) }}>{t('notifyOff')}</button>
|
|
1309
|
+
</div>
|
|
1310
|
+
</div>
|
|
1311
|
+
</div>
|
|
1312
|
+
</div>
|
|
1313
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1314
|
+
<div className="dut-field-top">
|
|
1315
|
+
<span className="dut-label">{t('notifyTitleFlash')}<Hint text={t('notifyTitleFlashHint')} /></span>
|
|
1316
|
+
<div className="dut-controls">
|
|
1317
|
+
<div className="dut-seg">
|
|
1318
|
+
<button type="button" className={resolved.notifyTitleFlash ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyTitleFlash', true) }}>{t('notifyOn')}</button>
|
|
1319
|
+
<button type="button" className={!resolved.notifyTitleFlash ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifyTitleFlash', false) }}>{t('notifyOff')}</button>
|
|
1320
|
+
</div>
|
|
1321
|
+
</div>
|
|
1322
|
+
</div>
|
|
1323
|
+
</div>
|
|
1324
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1325
|
+
<div className="dut-field-top">
|
|
1326
|
+
<span className="dut-label">{t('notifySystemNotification')}<Hint text={t('notifySystemNotificationHint')} /></span>
|
|
1327
|
+
<div className="dut-controls">
|
|
1328
|
+
<div className="dut-seg">
|
|
1329
|
+
<button type="button" className={resolved.notifySystemNotification ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifySystemNotification', true) }}>{t('notifyOn')}</button>
|
|
1330
|
+
<button type="button" className={!resolved.notifySystemNotification ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifySystemNotification', false) }}>{t('notifyOff')}</button>
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
</div>
|
|
1334
|
+
</div>
|
|
1335
|
+
<div className={'dut-field' + (!resolved.notificationsEnabled ? ' dut-sub-off' : '')}>
|
|
1336
|
+
<div className="dut-field-top">
|
|
1337
|
+
<span className="dut-label">{t('notifySound')}<Hint text={t('notifySoundHint')} /></span>
|
|
1338
|
+
<div className="dut-controls">
|
|
1339
|
+
<div className="dut-seg">
|
|
1340
|
+
<button type="button" className={resolved.notifySound ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifySound', true) }}>{t('notifyOn')}</button>
|
|
1341
|
+
<button type="button" className={!resolved.notifySound ? 'dut-seg-active' : ''} disabled={!writable || !resolved.notificationsEnabled} onClick={() => { setNotifyField('notifySound', false) }}>{t('notifyOff')}</button>
|
|
1342
|
+
</div>
|
|
1343
|
+
</div>
|
|
1344
|
+
</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</section>
|
|
1347
|
+
</div>
|
|
1348
|
+
)
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* The /init bootstrap prompts, submitted verbatim into the current session on
|
|
1353
|
+
* pick. Modeled after the classic coding-agent `/init`: explore the project,
|
|
1354
|
+
* then write (or improve) a root AGENTS.md addressed to future AI agents.
|
|
1355
|
+
*/
|
|
1356
|
+
const INIT_PROMPT_ZH = [
|
|
1357
|
+
'你是 ChatCode CLI 编码代理。请为本项目生成一份面向未来编码代理的 AGENTS.md,放在仓库根目录:',
|
|
1358
|
+
'',
|
|
1359
|
+
'1. 先自行探索项目——阅读 README、清单文件(package.json / pyproject.toml 等)、构建脚本与关键源码目录,不要向我追问这些信息;',
|
|
1360
|
+
'2. AGENTS.md 用中文撰写,包含:项目简介、常用命令(安装 / 构建 / 测试 / 检查)、代码风格与约定、目录结构导览、已知注意事项;',
|
|
1361
|
+
'3. 只写从代码中验证过的事实,保持简洁(建议不超过 150 行),不要臆测;',
|
|
1362
|
+
'4. 如果已存在 AGENTS.md,在其基础上改进补充,不要丢失已有内容;',
|
|
1363
|
+
'5. 项目共享规则写入 AGENTS.md;如果发现只应在本机生效的私有说明,只建议用户放入 CHATCODE-CLI.local.md,不要自动创建或覆盖该文件;',
|
|
1364
|
+
'6. 完成后简要汇报你写了什么。',
|
|
1365
|
+
].join('\n')
|
|
1366
|
+
|
|
1367
|
+
const INIT_PROMPT_EN = [
|
|
1368
|
+
'You are the ChatCode CLI coding agent. Generate an AGENTS.md for this project at the repository root, addressed to future coding agents:',
|
|
1369
|
+
'',
|
|
1370
|
+
'1. Explore the project first — read the README, manifest files (package.json / pyproject.toml etc.), build scripts and key source directories; do not ask me about them.',
|
|
1371
|
+
'2. Write the AGENTS.md in English covering: project overview, common commands (install / build / test / lint), code style and conventions, a directory guide, and known gotchas.',
|
|
1372
|
+
'3. Only state facts verified from the code; keep it concise (~150 lines max); no speculation.',
|
|
1373
|
+
'4. If an AGENTS.md already exists, improve it in place without losing existing content.',
|
|
1374
|
+
'5. Put shared project instructions in AGENTS.md. If you discover machine-private guidance, only recommend CHATCODE-CLI.local.md; do not create or overwrite it automatically.',
|
|
1375
|
+
'6. Briefly report what you wrote when done.',
|
|
1376
|
+
].join('\n')
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Register the `/init` slash command: a client-owned contribution that pops a
|
|
1380
|
+
* language picker and submits the matching AGENTS.md bootstrap prompt into
|
|
1381
|
+
* the picked session via the scope-addressed conversation face
|
|
1382
|
+
* (`ctx.sessions.scope(id).conversation.send` — the same hop DSH's own
|
|
1383
|
+
* packages use). Contribution rows merge into the host catalog by name; the
|
|
1384
|
+
* description string is captured at registration time, so a mid-session
|
|
1385
|
+
* language switch refreshes it only after reload.
|
|
1386
|
+
*/
|
|
1387
|
+
function registerInitCommand(ctx: UITweaksClientContext): () => void {
|
|
1388
|
+
const t = ctx.locale.bind(NS)
|
|
1389
|
+
return ctx.commandUi.register({
|
|
1390
|
+
name: 'init',
|
|
1391
|
+
description: t('initDesc'),
|
|
1392
|
+
available: () => true,
|
|
1393
|
+
ui: {
|
|
1394
|
+
kind: 'popupSelect',
|
|
1395
|
+
options: (): Promise<readonly SelectOption[]> => Promise.resolve([
|
|
1396
|
+
{ id: 'zh', label: t('initOptionZh'), detail: t('initOptionZhDetail') },
|
|
1397
|
+
{ id: 'en', label: t('initOptionEn'), detail: t('initOptionEnDetail') },
|
|
1398
|
+
]),
|
|
1399
|
+
onSelect: async (option, session) => {
|
|
1400
|
+
const prompt = option.id === 'en' ? INIT_PROMPT_EN : INIT_PROMPT_ZH
|
|
1401
|
+
const scoped = ctx.sessions.scope(session.sessionId)
|
|
1402
|
+
if (scoped === undefined) {
|
|
1403
|
+
console.error(`[ChatCode CLI UI Tweaks] ${t('initFailed')}: session ${session.sessionId} is not scoped`)
|
|
1404
|
+
return
|
|
1405
|
+
}
|
|
1406
|
+
// Route through the session's input facade (draft + submit) instead of
|
|
1407
|
+
// a bare conversation.send: the hub's own send choreography then
|
|
1408
|
+
// handles first-message materialization for a brand-new session, plus
|
|
1409
|
+
// queue/steer policy while a turn is running. A direct send cannot
|
|
1410
|
+
// start an unmaterialized session and fails silently there.
|
|
1411
|
+
const input = ctx.conversation.input.for(scoped)
|
|
1412
|
+
input.setDraft(prompt)
|
|
1413
|
+
input.submit()
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1416
|
+
})
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Register a Settings section only while its enable flag is on, so the
|
|
1421
|
+
* Settings nav row appears / disappears live with the toggle in the UI Tweaks
|
|
1422
|
+
* section. Waits for the `settings.section` declaration (like the static
|
|
1423
|
+
* inject), then keeps the section registered exactly while
|
|
1424
|
+
* `isEnabled(controller value)` holds: toggling the switch in the UI Tweaks
|
|
1425
|
+
* section mounts or disposes the section, and the settings shell's nav
|
|
1426
|
+
* (projected from the section ledger) updates in place.
|
|
1427
|
+
*/
|
|
1428
|
+
function installConditionalSection(
|
|
1429
|
+
ctx: ClientContext,
|
|
1430
|
+
controller: SettingsClient,
|
|
1431
|
+
isEnabled: (value: TweaksValue | undefined) => boolean,
|
|
1432
|
+
registerSection: () => () => void,
|
|
1433
|
+
): void {
|
|
1434
|
+
ctx.slots.inject('settings.section', () => {
|
|
1435
|
+
let dispose: (() => void) | undefined
|
|
1436
|
+
const sync = (): void => {
|
|
1437
|
+
const enabled = isEnabled(controller.getSnapshot().value)
|
|
1438
|
+
if (enabled && dispose === undefined) {
|
|
1439
|
+
dispose = registerSection()
|
|
1440
|
+
} else if (!enabled && dispose !== undefined) {
|
|
1441
|
+
dispose()
|
|
1442
|
+
dispose = undefined
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
sync()
|
|
1446
|
+
const unsubscribe = controller.subscribe(sync)
|
|
1447
|
+
return () => {
|
|
1448
|
+
unsubscribe()
|
|
1449
|
+
dispose?.()
|
|
1450
|
+
}
|
|
1451
|
+
})
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
export function apply(ctx: UITweaksClientContext): void {
|
|
1455
|
+
ctx.effect(installBranding, 'dsh-ui-tweaks: ChatCode CLI branding')
|
|
1456
|
+
ctx.effect(installBaseStyles, 'dsh-ui-tweaks: base styles')
|
|
1457
|
+
ctx.effect(installGitBarStyles, 'dsh-ui-tweaks: gitbar styles')
|
|
1458
|
+
ctx.effect(installArchiveStyles, 'dsh-ui-tweaks: archive styles')
|
|
1459
|
+
ctx.effect(installMcpStyles, 'dsh-ui-tweaks: mcp styles')
|
|
1460
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-ui-tweaks: locale')
|
|
1461
|
+
const t = ctx.locale.bind(NS)
|
|
1462
|
+
|
|
1463
|
+
const controller = new SettingsClient()
|
|
1464
|
+
|
|
1465
|
+
ctx.effect(() => {
|
|
1466
|
+
const applyCss = (): void => {
|
|
1467
|
+
const state = controller.getSnapshot()
|
|
1468
|
+
if (state.status === 'ready') {
|
|
1469
|
+
runtimeStyleElement().textContent = buildRuntimeCss(resolveValue(state.value))
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
applyCss()
|
|
1473
|
+
const dispose = controller.subscribe(applyCss)
|
|
1474
|
+
void controller.load()
|
|
1475
|
+
return dispose
|
|
1476
|
+
}, 'dsh-ui-tweaks: runtime css')
|
|
1477
|
+
|
|
1478
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1479
|
+
name: 'settings.section',
|
|
1480
|
+
id: NS,
|
|
1481
|
+
order: 40,
|
|
1482
|
+
label: () => t('nav'),
|
|
1483
|
+
inject: () => ({ controller, t }),
|
|
1484
|
+
}, SettingsSection))
|
|
1485
|
+
|
|
1486
|
+
// The new-session hero exposes a dedicated brand-mark slot, so use the same
|
|
1487
|
+
// packaged logo there rather than restyling the host's fallback FishLogo.
|
|
1488
|
+
ctx.slots.inject('conversation.hero.brand.mark', () => ctx.slots.register({
|
|
1489
|
+
name: 'conversation.hero.brand.mark',
|
|
1490
|
+
priority: 10,
|
|
1491
|
+
}, HeroProjectLogo))
|
|
1492
|
+
|
|
1493
|
+
// The sidebar renders this same slot in its expanded header and collapsed
|
|
1494
|
+
// rail. Registering the packaged China Unicom knot here prevents the rail
|
|
1495
|
+
// from falling back to the host FishLogo after the sidebar is collapsed.
|
|
1496
|
+
ctx.slots.inject('sidebar.brand.mark', () => ctx.slots.register({
|
|
1497
|
+
name: 'sidebar.brand.mark',
|
|
1498
|
+
priority: 10,
|
|
1499
|
+
}, SidebarProjectLogo))
|
|
1500
|
+
|
|
1501
|
+
// Newer DSH hosts expose the expanded wordmark as its own slot. Owning it
|
|
1502
|
+
// directly avoids inheriting the host's brand typography; older hosts that
|
|
1503
|
+
// do not render this slot still use installBranding's DOM compatibility
|
|
1504
|
+
// path above.
|
|
1505
|
+
ctx.slots.inject('sidebar.brand.name', () => ctx.slots.register({
|
|
1506
|
+
name: 'sidebar.brand.name',
|
|
1507
|
+
priority: 10,
|
|
1508
|
+
}, SidebarProjectName))
|
|
1509
|
+
|
|
1510
|
+
// GitBar v3: the branch chip lives in the session header's action row
|
|
1511
|
+
// (beside the title, AFTER the mode badge) and the 打开项目/终端/差异 icon
|
|
1512
|
+
// group sits in the header's right-aligned utilities. Both are strict
|
|
1513
|
+
// session slots; each renders null outside git repos.
|
|
1514
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
1515
|
+
name: 'conversation.session.header.actions',
|
|
1516
|
+
id: 'gitbar-branch',
|
|
1517
|
+
order: 10,
|
|
1518
|
+
locale: NS,
|
|
1519
|
+
inject: () => ({ controller, sessionsService: ctx.sessions }),
|
|
1520
|
+
}, BranchChipEntry))
|
|
1521
|
+
|
|
1522
|
+
// Warmup: the header chips mount only after the conversation projection
|
|
1523
|
+
// finishes loading (seconds on large inactive sessions). The input dock
|
|
1524
|
+
// mounts immediately on session switch, so a null-rendering seat there
|
|
1525
|
+
// prefetches the status into the shared cache right away — the chips then
|
|
1526
|
+
// paint folder+branch from the warm cache the moment they appear.
|
|
1527
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
1528
|
+
name: 'conversation.input.dock',
|
|
1529
|
+
id: 'gitbar-warmup',
|
|
1530
|
+
order: 41,
|
|
1531
|
+
inject: () => ({}),
|
|
1532
|
+
}, GitWarmup))
|
|
1533
|
+
|
|
1534
|
+
ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
|
|
1535
|
+
name: 'conversation.session.header.utilities',
|
|
1536
|
+
id: 'gitbar-utils',
|
|
1537
|
+
order: 10,
|
|
1538
|
+
locale: NS,
|
|
1539
|
+
inject: () => ({ controller }),
|
|
1540
|
+
}, HeaderUtilities))
|
|
1541
|
+
|
|
1542
|
+
// Hero (new-session screen): the session header does not mount there, so a
|
|
1543
|
+
// floating branch chip anchors beside the workspace picker instead — only
|
|
1544
|
+
// when the picked workspace is a git repo.
|
|
1545
|
+
ctx.effect(() => installHeroChip(controller, t), 'dsh-ui-tweaks: hero branch chip')
|
|
1546
|
+
|
|
1547
|
+
// Archive manager: a Settings section ("归档") that lists archived sessions
|
|
1548
|
+
// and can restore or permanently delete them. Registered only while the
|
|
1549
|
+
// archiveManagerEnabled toggle in the UI Tweaks section is on, so the nav
|
|
1550
|
+
// row appears / disappears live with the switch (the settings shell
|
|
1551
|
+
// projects its nav from the section ledger).
|
|
1552
|
+
installConditionalSection(ctx, controller, (value) => value?.archiveManagerEnabled === true, () => ctx.slots.register({
|
|
1553
|
+
name: 'settings.section',
|
|
1554
|
+
id: 'archive',
|
|
1555
|
+
order: 50,
|
|
1556
|
+
label: () => t('archiveNav'),
|
|
1557
|
+
locale: NS,
|
|
1558
|
+
inject: () => ({ controller, t, sessionsService: ctx.sessions }),
|
|
1559
|
+
}, ArchiveSection))
|
|
1560
|
+
|
|
1561
|
+
// MCP manager: a Settings section ("MCP 管理") that lists the configured MCP
|
|
1562
|
+
// servers with their status and tools, and restarts them. Registered only
|
|
1563
|
+
// while the mcpManagerEnabled toggle in the UI Tweaks section is on, so the
|
|
1564
|
+
// nav row appears / disappears live with the switch.
|
|
1565
|
+
installConditionalSection(ctx, controller, (value) => value?.mcpManagerEnabled === true, () => ctx.slots.register({
|
|
1566
|
+
name: 'settings.section',
|
|
1567
|
+
id: 'mcp',
|
|
1568
|
+
order: 60,
|
|
1569
|
+
label: () => t('mcpNav'),
|
|
1570
|
+
locale: NS,
|
|
1571
|
+
inject: () => ({ controller, t }),
|
|
1572
|
+
}, McpSection))
|
|
1573
|
+
|
|
1574
|
+
// /init slash command: registered only while the initCommandEnabled toggle
|
|
1575
|
+
// in the UI Tweaks section is on, so `/init` appears in the slash menu only
|
|
1576
|
+
// while the feature is enabled — same live register/dispose choreography as
|
|
1577
|
+
// the conditional Settings sections above.
|
|
1578
|
+
ctx.effect(() => {
|
|
1579
|
+
let dispose: (() => void) | undefined
|
|
1580
|
+
const sync = (): void => {
|
|
1581
|
+
const enabled = controller.getSnapshot().value?.initCommandEnabled === true
|
|
1582
|
+
if (enabled && dispose === undefined) {
|
|
1583
|
+
dispose = registerInitCommand(ctx)
|
|
1584
|
+
} else if (!enabled && dispose !== undefined) {
|
|
1585
|
+
dispose()
|
|
1586
|
+
dispose = undefined
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
sync()
|
|
1590
|
+
return controller.subscribe(sync)
|
|
1591
|
+
}, 'dsh-ui-tweaks: /init command')
|
|
1592
|
+
|
|
1593
|
+
// Whale working indicator: the brand whale centered above the composer
|
|
1594
|
+
// card — translucent and still while idle, swimming while the model works.
|
|
1595
|
+
// The dock entry is registered only while the whaleIndicatorEnabled toggle
|
|
1596
|
+
// is on, so off costs nothing; styles ride the same toggle so an unused
|
|
1597
|
+
// keyframes rule never lingers in <head>.
|
|
1598
|
+
ctx.effect(() => {
|
|
1599
|
+
let disposeEntry: (() => void) | undefined
|
|
1600
|
+
let disposeStyles: (() => void) | undefined
|
|
1601
|
+
const sync = (): void => {
|
|
1602
|
+
const enabled = controller.getSnapshot().value?.whaleIndicatorEnabled === true
|
|
1603
|
+
if (enabled && disposeEntry === undefined) {
|
|
1604
|
+
disposeStyles = installWhaleStyles()
|
|
1605
|
+
disposeEntry = ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
1606
|
+
name: 'conversation.input.dock',
|
|
1607
|
+
id: 'whale',
|
|
1608
|
+
order: 30,
|
|
1609
|
+
inject: () => ({ sessionsService: ctx.sessions }),
|
|
1610
|
+
}, WhaleIndicator))
|
|
1611
|
+
} else if (!enabled && disposeEntry !== undefined) {
|
|
1612
|
+
disposeEntry()
|
|
1613
|
+
disposeEntry = undefined
|
|
1614
|
+
disposeStyles?.()
|
|
1615
|
+
disposeStyles = undefined
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
sync()
|
|
1619
|
+
return controller.subscribe(sync)
|
|
1620
|
+
}, 'dsh-ui-tweaks: whale indicator')
|
|
1621
|
+
|
|
1622
|
+
// Precise cache hit: rewrites the stats line's cache-hit figure to two
|
|
1623
|
+
// decimals from the raw tokenUsage projection. The composer-dock entry
|
|
1624
|
+
// renders nothing itself — it is a live reader that patches the stock span
|
|
1625
|
+
// in place — and it is registered only while the preciseCacheHitEnabled
|
|
1626
|
+
// toggle is on, so off costs nothing and toggling restores the stock text.
|
|
1627
|
+
ctx.effect(() => {
|
|
1628
|
+
let disposeEntry: (() => void) | undefined
|
|
1629
|
+
const sync = (): void => {
|
|
1630
|
+
const enabled = controller.getSnapshot().value?.preciseCacheHitEnabled === true
|
|
1631
|
+
if (enabled && disposeEntry === undefined) {
|
|
1632
|
+
disposeEntry = ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
1633
|
+
name: 'conversation.composer.dock',
|
|
1634
|
+
id: 'precise-cache-hit',
|
|
1635
|
+
order: 90,
|
|
1636
|
+
inject: () => ({}),
|
|
1637
|
+
}, PreciseCacheHitEntry))
|
|
1638
|
+
} else if (!enabled && disposeEntry !== undefined) {
|
|
1639
|
+
disposeEntry()
|
|
1640
|
+
disposeEntry = undefined
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
sync()
|
|
1644
|
+
return controller.subscribe(sync)
|
|
1645
|
+
}, 'dsh-ui-tweaks: precise cache hit')
|
|
1646
|
+
|
|
1647
|
+
// Task notifications: watch every session on the list feed and raise
|
|
1648
|
+
// tab-title / system-notification / chime alerts when one finishes its turn
|
|
1649
|
+
// or starts blocking on the user. Registered only while the master toggle
|
|
1650
|
+
// is on; sub-toggles are re-read live through readState, so flipping them
|
|
1651
|
+
// never reinstalls the watcher.
|
|
1652
|
+
ctx.effect(() => {
|
|
1653
|
+
let disposeNotifier: (() => void) | undefined
|
|
1654
|
+
const sync = (): void => {
|
|
1655
|
+
const enabled = controller.getSnapshot().value?.notificationsEnabled === true
|
|
1656
|
+
if (enabled && disposeNotifier === undefined) {
|
|
1657
|
+
disposeNotifier = installTaskNotifier({
|
|
1658
|
+
sessionsService: ctx.sessions,
|
|
1659
|
+
pendingInteractions: ctx.uiSession.pendingInteractions,
|
|
1660
|
+
text: {
|
|
1661
|
+
notifyTitleDone: t('notifyTitleDone'),
|
|
1662
|
+
notifyTitleAborted: t('notifyTitleAborted'),
|
|
1663
|
+
notifyTitleFailed: t('notifyTitleFailed'),
|
|
1664
|
+
notifyTitlePending: t('notifyTitlePending'),
|
|
1665
|
+
bodyComplete: t('bodyComplete'),
|
|
1666
|
+
bodyAborted: t('bodyAborted'),
|
|
1667
|
+
bodyFailed: t('bodyFailed'),
|
|
1668
|
+
bodyApproval: t('bodyApproval'),
|
|
1669
|
+
bodyPlan: t('bodyPlan'),
|
|
1670
|
+
bodyQuestion: t('bodyQuestion'),
|
|
1671
|
+
},
|
|
1672
|
+
readState: () => {
|
|
1673
|
+
const value = controller.getSnapshot().value
|
|
1674
|
+
return {
|
|
1675
|
+
options: {
|
|
1676
|
+
onlyWhenHidden: value?.notifyOnlyWhenHidden ?? true,
|
|
1677
|
+
onComplete: value?.notifyOnComplete ?? true,
|
|
1678
|
+
onInteraction: value?.notifyOnInteraction ?? true,
|
|
1679
|
+
},
|
|
1680
|
+
channels: {
|
|
1681
|
+
titleFlash: value?.notifyTitleFlash ?? true,
|
|
1682
|
+
systemNotification: value?.notifySystemNotification ?? true,
|
|
1683
|
+
sound: value?.notifySound ?? false,
|
|
1684
|
+
},
|
|
1685
|
+
}
|
|
1686
|
+
},
|
|
1687
|
+
})
|
|
1688
|
+
} else if (!enabled && disposeNotifier !== undefined) {
|
|
1689
|
+
disposeNotifier()
|
|
1690
|
+
disposeNotifier = undefined
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
sync()
|
|
1694
|
+
return controller.subscribe(sync)
|
|
1695
|
+
}, 'dsh-ui-tweaks: task notifications')
|
|
1696
|
+
}
|