dsh-focus-overlay 0.12.3 → 0.13.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/README.en.md +29 -12
- package/README.md +29 -12
- package/lib/client.js +448 -67
- package/package.json +4 -2
package/README.en.md
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
A <b>focus mode</b> for the DeepSeek Harness (DSH) web GUI: one click to a full-screen reading overlay that hides the header, composer and sidebars, folding the AI tool-call flow into official-style count summaries — leaving only the conversation between you and the AI.<br>
|
|
8
|
+
A <b>focus mode</b> for the DeepSeek Harness (DSH) web GUI and the macOS / Windows desktop app: one click to a full-screen reading overlay that hides the header, composer and sidebars, folding the AI tool-call flow into official-style count summaries — leaving only the conversation between you and the AI.<br>
|
|
9
9
|
Text, images, the turn rail, width handles and the answer card all reuse or 1:1 replicate official components, matching the chat view's look.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
|
|
14
|
-
<img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
|
|
15
|
-
<img src="https://
|
|
16
|
-
<img src="https://
|
|
13
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
|
|
15
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
|
|
16
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
|
|
17
|
+
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
|
|
17
18
|
</p>
|
|
18
19
|
|
|
19
20
|
## Features
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
- **Auto-focus & reminders** — after a reply completes normally, optionally auto-enter focus at your question; while in focus, get a toast for a new reply or when the AI is waiting on you
|
|
26
27
|
- **Width handles** — draggable strips beside the content column (official WidthHandle replica) that resize the reading column live and persist
|
|
27
28
|
- **Hotkey F** — press `F` anywhere to enter focus mode instantly (toggleable in settings); typing in an input never triggers it
|
|
29
|
+
- **Desktop window chrome** — adapts to the dsh desktop app (macOS / Windows): the top of the overlay reserves the official frame clearance for the macOS traffic lights and the Windows native titlebar, that top band drags the window while focused, and the reservation shrinks automatically in native fullscreen
|
|
28
30
|
- **Focus counter** — the settings card keeps a running total of your focus-mode entries; past 100 entries a GitHub Star nudge appears, is remembered after the first use, and then becomes a direct "open the project page" button
|
|
29
31
|
|
|
30
32
|
These features are designed to give **small screens** more content space: tucking away the persistent header/composer and tool steps lets the conversation use as much of the screen as possible.
|
|
@@ -55,7 +57,9 @@ Click the line to expand that turn's full work detail (per-category one-line sum
|
|
|
55
57
|
| Feature | Description |
|
|
56
58
|
| --- | --- |
|
|
57
59
|
| Full-screen overlay | Registered into `shell.overlay` (additive, `replaceRisk: none`) — covers header / composer / sidebar; the thin top bar keeps only the session title and an exit button |
|
|
58
|
-
| Official rendering primitives | Assistant text through official `MarkdownText` (GFM + code highlighting + TeX + code copy buttons + footnotes); user messages
|
|
60
|
+
| Official rendering primitives | Assistant text through official `MarkdownText` (GFM + code highlighting + TeX + code copy buttons + footnotes); user messages pick the official user-text projection the running dsh exports: `projectUserText` on 0.1.2–0.2.0 (plain runs plus `@`/`/` reference chips), and the official `UserTextRun` plain run after the 0.2.2 desktop runtime removed that function — a missing projection can never crash the overlay; buttons, modals and icons are all official primitives |
|
|
61
|
+
| Desktop window frame | Reserves top space from the official `--dsh-frame-overlay-top` / `--dsh-frame-top-clearance` / `--dsh-frame-chrome-top` variables for the macOS traffic lights (~48px) and the Windows native titlebar (40px), and provides a top drag strip matching the official drag region (`data-window-drag` + `-webkit-app-region: drag`) so the window stays draggable while focused; native fullscreen (`data-fullscreen`) drops the reservation to 20px; on Windows it also hides the official sidebar-collapse and new-session controls that the window chrome promotes into the titlebar layer when the sidebar is collapsed / the window is narrow — otherwise they painted above the focus layer and a stray click switched sessions while reading; both come back with the chrome on exit. In a plain browser the variables and markers are absent, so the 0px fallback keeps the old pixel-identical layout |
|
|
62
|
+
| File-mention chips | Inline-code paths in AI replies resolve through the official `chatFileMentions` into clickable chips: on dsh 0.2.x the overlay passes the same real Turn face (`deliverables` projection) the official chat turn tail uses and opens through the sidebar resource controller's `dsh-resource://file/session/…` address; on dsh ≤ 0.1.x it keeps the legacy owner shape and `workspaces.openPath`. No chip without projection data, and a resolver failure never crashes the overlay |
|
|
59
63
|
| Image resolution | Assistant `image` blocks resolve through `uiConversation.imageUrl` (the dsh 0.1.2+ session-authorized image cache), falling back to the legacy resolver on older dsh |
|
|
60
64
|
| Tool-call folding | Follows the official "transcript view" preference (normal / compact); compact mode folds each turn's work part into a "N tool calls · M messages · K subagents" disclosure line ("Thought for a while" when nothing is countable), clickable to expand the full detail; steering messages always stay top-level, never buried; the last turn stays unfolded while streaming |
|
|
61
65
|
| Categorized summaries | The expanded detail counts tools by family: commands / edits / searches / reads / directory listings / subagents / todos / goals / workflows / skills / questions / plans / background jobs / context injections |
|
|
@@ -75,16 +79,17 @@ Click the line to expand that turn's full work detail (per-category one-line sum
|
|
|
75
79
|
| Focus counter | A row at the bottom of the settings card totals your focus-mode entries (auto-entries included), persisted to `localStorage`; past 100 entries a Star nudge appears — after the first use it is remembered and replaced by a direct "open the project page" button |
|
|
76
80
|
| Esc peels layer by layer | Answer card → input bar → focus mode, one layer per press; Esc mid-IME-composition cancels the composition first |
|
|
77
81
|
| i18n | Chinese / English copy registered under the `focus` namespace, follows the UI language |
|
|
78
|
-
| Plugin configuration
|
|
79
|
-
| First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag |
|
|
80
|
-
| Official preference sync | Reads the official "transcript view" preference (normal / compact); flipping it applies immediately |
|
|
82
|
+
| Plugin configuration | On dsh 0.2 and newer, open this plugin's detail page from the sidebar "Plugins" page — the configuration renders between the package description and its component rows (`plugins.bundle.config`). On dsh ≤ 0.1.x it stays the collapsible "Settings → Plugins → Plugin configuration" card (`settings.plugin.item`). Preferences persist to `localStorage`; the Node half registers the same settings namespace (schemastery schema) |
|
|
83
|
+
| First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag; its "open settings" action jumps to whichever configuration surface the running dsh declares |
|
|
84
|
+
| Official preference sync | Reads the official "transcript view" preference (normal / compact on dsh ≤ 0.1.x; compact / standard / detailed / verbose on dsh 0.2); flipping it applies immediately |
|
|
81
85
|
| Render error boundary | The overlay content is wrapped in an error boundary: one render crash never bricks the Focus button — the next open retries |
|
|
82
86
|
| DSH-better-sidebar compatibility | While in focus, a `<body>` marker makes the package stylesheet hide better-sidebar's top-right panel toggles and any open right/bottom panels (and release the squeezed layout); restored on exit |
|
|
83
87
|
| Legacy compatibility mode | On dsh < 0.1.2 missing the new services, the overlay degrades gracefully (legacy snapshot adapters) with a one-time console notice — no crash |
|
|
88
|
+
| Cross-release primitive compatibility | Official primitives renamed between major releases fall back by availability (dsh 0.2 removed the fixed-size `…14` / `…16` icons in favour of `…Regular` + `size`; the dsh 0.2.2 desktop runtime removed `projectUserText` in favour of `UserTextRun`), so an `undefined` component can never throw React #130 |
|
|
84
89
|
|
|
85
90
|
## Install
|
|
86
91
|
|
|
87
|
-
Requires
|
|
92
|
+
Requires `dsh` (Web CLI or the desktop app, `>= 0.1.2-rc.1`) and, for source installs, Node `>= 22.19.0`.
|
|
88
93
|
|
|
89
94
|
**From npm (recommended)**
|
|
90
95
|
|
|
@@ -119,7 +124,15 @@ dsh web
|
|
|
119
124
|
|
|
120
125
|
Restart `dsh web` after install.
|
|
121
126
|
|
|
122
|
-
|
|
127
|
+
**DSH desktop app (macOS / Windows)**
|
|
128
|
+
|
|
129
|
+
The desktop app manages plugins itself, no CLI needed: **Plugins → Add plugin** in the sidebar, enter `dsh-focus-overlay` (or a local directory / `link:` path such as `link:/path/to/dsh-focus-overlay`), then configure it on this plugin's detail page. Reload the desktop window (or restart the app) after installing or updating.
|
|
130
|
+
|
|
131
|
+
Desktop support is verified against **dsh desktop 0.2.2-rc.1**: macOS and Windows share the same Electron renderer and client contracts; the window-chrome differences (macOS traffic lights / Windows native titlebar) are driven by the official `data-platform` / `data-windows-titlebar` / `data-fullscreen` markers, and the plugin adapts through the official frame variables — one install serves both platforms. In focus mode the top of the overlay is a draggable title band; native fullscreen (macOS green button / Windows maximize) shrinks the reservation automatically.
|
|
132
|
+
|
|
133
|
+
> The desktop app uses its own `desktop` profile: installing the plugin only into the Web CLI's `web` profile does not install it in the desktop app — add it once on the Plugins page there too.
|
|
134
|
+
|
|
135
|
+
> **First run**: after restarting, the welcome page shows a one-time "Focus Mode" intro that explains the features and lets you configure them in place; all of these settings can be changed anytime — on newer dsh open this plugin's detail page from the sidebar "Plugins" page, on older dsh use "Settings → Plugins → Plugin configuration".
|
|
123
136
|
|
|
124
137
|
## Usage
|
|
125
138
|
|
|
@@ -133,7 +146,11 @@ Restart `dsh web` after install.
|
|
|
133
146
|
|
|
134
147
|
## Settings
|
|
135
148
|
|
|
136
|
-
|
|
149
|
+
**On dsh 0.2 and newer**: sidebar "Plugins" page → find `dsh-focus-overlay` → the configuration shows on its detail page, between the package description and the component rows.
|
|
150
|
+
|
|
151
|
+
**On dsh ≤ 0.1.x**: sidebar "Settings → Plugins → Plugin configuration" → expand the "Focus Mode" card.
|
|
152
|
+
|
|
153
|
+
Both locations present the same options:
|
|
137
154
|
|
|
138
155
|
| Option | Description |
|
|
139
156
|
| --- | --- |
|
package/README.md
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
<p align="center">中文 | <a href="README.en.md">English</a></p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
为 DeepSeek Harness(DSH)Web GUI
|
|
6
|
+
为 DeepSeek Harness(DSH)Web GUI 与桌面版(macOS / Windows)提供的<b>专注模式</b>:一键进入全屏阅读遮罩,隐藏标题区、输入区与侧栏,把 AI 的工具调用过程折叠成官方样式的计数摘要,只保留「你与 AI 的对话」本身。<br>
|
|
7
7
|
文本、图片、导航条、宽度调节与答题卡均复用或 1:1 复刻官方组件,观感与聊天视图一致。
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
|
|
12
|
-
<img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
|
|
13
|
-
<img src="https://
|
|
14
|
-
<img src="https://
|
|
11
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
|
|
13
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
|
|
14
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
|
|
15
|
+
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
## 功能
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
- **自动专注与提醒** —— 回复正常完成后可自动进入专注并定位到你的提问;专注中收到新回复、或 AI 等待你回答时弹出提醒
|
|
24
25
|
- **宽度调节** —— 正文两侧的拖拽把手(官方 WidthHandle 复刻),实时拖动阅读列宽并持久化
|
|
25
26
|
- **快捷键 F** —— 任意界面按 `F` 一键进入专注模式,可在设置中关闭;输入框内打字不会误触发
|
|
27
|
+
- **桌面版窗口适配** —— 适配 dsh 桌面版(macOS / Windows):顶部按官方窗口框架变量为 macOS 红绿灯与 Windows 原生标题栏让出空间,专注时这段顶部空白条可直接拖动窗口,进入全屏后自动收起预留空间
|
|
26
28
|
- **专注计数** —— 设置卡片最底部累计展示你进入专注模式的次数;超过 100 次后出现 GitHub Star 引导,点过一次即被记住,之后提供「前往 GitHub 项目页面」直达按钮
|
|
27
29
|
|
|
28
30
|
这些功能专为**小屏幕**提供更多内容呈现空间:收起常驻的标题 / 输入区与工具步骤,让有限的屏幕尽可能多地展示对话。
|
|
@@ -53,7 +55,9 @@
|
|
|
53
55
|
| 功能 | 说明 |
|
|
54
56
|
| --- | --- |
|
|
55
57
|
| 全屏专注遮罩 | 经 `shell.overlay` 注册(纯增量、`replaceRisk: none`),覆盖标题区 / 输入区 / 侧栏;顶部只留会话标题与「退出专注」按钮 |
|
|
56
|
-
| 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 +
|
|
58
|
+
| 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 + 脚注);用户消息按运行版本选择官方用户文本投影:dsh 0.1.2–0.2.0 用 `projectUserText`(纯文本 + `@`/`/` 引用 chip),dsh 0.2.2 桌面版移除了该函数后自动回退到官方 `UserTextRun`(纯文本 run),不会因 `undefined` 组件崩溃;按钮、模态、图标均为官方原语 |
|
|
59
|
+
| 桌面版窗口框架 | 按官方 `--dsh-frame-overlay-top` / `--dsh-frame-top-clearance` / `--dsh-frame-chrome-top` 变量为 macOS 红绿灯(≈48px)与 Windows 原生标题栏(40px)预留顶部空间,并提供一个与官方拖拽区一致的顶部拖拽条(`data-window-drag` + `-webkit-app-region: drag`),专注时可拖动窗口;原生全屏(`data-fullscreen`)时预留自动降为 20px;Windows 上还会隐藏被官方提升到标题栏层的「侧边栏开关」与「新建会话」按钮(侧栏折叠 / 窄窗口时出现),避免它们浮在专注层之上、误触后在你阅读时切换会话;退出专注后随窗口 chrome 一起恢复。普通浏览器中这些变量与标记不存在,回退 0px、与旧版逐像素一致 |
|
|
60
|
+
| 文件引用 chip | AI 回复中的行内代码路径由官方 `chatFileMentions` 解析为可点击 chip:dsh 0.2.x 传入官方同款的真实 Turn face(`deliverables` 投影),点击经右侧栏资源控制器以 `dsh-resource://file/session/…` 打开;dsh ≤ 0.1.x 继续使用旧的 owner 形状与 `workspaces.openPath`;无投影数据时不显示 chip,绝不因解析异常崩溃 |
|
|
57
61
|
| 图片解析 | assistant 的 `image` 块经 `uiConversation.imageUrl`(dsh 0.1.2+ 的会话授权图片缓存)解析,旧版 dsh 自动回退到 legacy 解析器 |
|
|
58
62
|
| 工具调用折叠 | 遵循官方「对话显示」偏好(normal / compact);compact 模式把每轮的工作部分折叠成「N 次工具调用 · M 条消息 · K 个 subagent」披露行(无内容时显示「已思考」),点击可展开完整明细;steering 消息始终独立成行不被折叠;流式中的最后一轮保持展开直到结束 |
|
|
59
63
|
| 分类摘要明细 | 展开后按工具类别计数:命令 / 编辑 / 搜索 / 读取 / 列目录 / 子代理 / 待办 / 目标 / 工作流 / 技能 / 提问 / 计划 / 后台任务 / 上下文注入 |
|
|
@@ -73,16 +77,17 @@
|
|
|
73
77
|
| 专注计数 | 设置卡片最底部一行累计进入专注模式的次数(含自动进入),持久化到 `localStorage`;超过 100 次出现 Star 引导按钮,点击前往仓库页面并记住状态,之后改为「前往 GitHub 项目页面」直达按钮 |
|
|
74
78
|
| Esc 逐层退出 | 答题卡 → 输入条 → 专注模式,一层一层退;输入法组词中的 Esc 优先取消组词 |
|
|
75
79
|
| i18n | 中 / 英文案,注册到 `focus` 命名空间,跟随界面语言 |
|
|
76
|
-
| 插件配置卡片 |
|
|
77
|
-
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding`
|
|
78
|
-
| 官方偏好联动 |
|
|
80
|
+
| 插件配置卡片 | dsh 0.2 起在侧栏「Plugins」页面打开本插件详情页,配置直接渲染在包描述与组件列表之间(`plugins.bundle.config`);dsh ≤ 0.1.x 仍为「设置 → 插件 → 插件配置」中的可折叠卡片(`settings.plugin.item`)。偏好持久化到 `localStorage`;Node 半区以 schemastery schema 注册同名设置 namespace |
|
|
81
|
+
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding` 步骤自带已读标记与版本号;「打开设置」按当前 dsh 版本跳到对应配置页 |
|
|
82
|
+
| 官方偏好联动 | 读取官方「对话显示」偏好(dsh ≤ 0.1.x:normal / compact;dsh 0.2:compact / standard / detailed / verbose),切换即时生效 |
|
|
79
83
|
| 渲染错误兜底 | 遮罩内容包裹错误边界:一次渲染崩溃不会永久打死「专注」按钮,下次打开自动重试 |
|
|
80
84
|
| 兼容 DSH-better-sidebar | 进入专注时在 `<body>` 打标记,样式层自动隐藏其右上角的收放面板按钮与已开启的右侧 / 底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
|
|
81
85
|
| 旧版兼容模式 | dsh < 0.1.2 缺少新服务时降级运行(legacy 快照适配),控制台一次性提示,不会崩溃 |
|
|
86
|
+
| 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`;dsh 0.2.2 桌面版移除了 `projectUserText`,回退到 `UserTextRun`),不会因为 `undefined` 组件触发 React #130 |
|
|
82
87
|
|
|
83
88
|
## 安装
|
|
84
89
|
|
|
85
|
-
需要 `dsh
|
|
90
|
+
需要 `dsh`(Web CLI 或桌面版,`>= 0.1.2-rc.1`),Node `>= 22.19.0`(仅源码安装时需要 Node)。
|
|
86
91
|
|
|
87
92
|
**从 npm(推荐)**
|
|
88
93
|
|
|
@@ -117,7 +122,15 @@ dsh web
|
|
|
117
122
|
|
|
118
123
|
安装后重启 `dsh web` 生效。
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
**DSH 桌面版(macOS / Windows)**
|
|
126
|
+
|
|
127
|
+
dsh 桌面版自带插件管理,不需要 CLI:侧栏 **Plugins → 添加插件**,填入 `dsh-focus-overlay`(或本机目录 / `link:` 路径,例如 `link:/path/to/dsh-focus-overlay`),安装后在本插件详情页中配置。安装或更新后重新加载桌面窗口(或重启桌面应用)生效。
|
|
128
|
+
|
|
129
|
+
桌面版适配基于 **dsh 桌面版 0.2.2-rc.1** 验证:macOS 与 Windows 共用同一套 Electron 渲染层与客户端契约,窗口框架差异(macOS 红绿灯 / Windows 原生标题栏)由官方 `data-platform` / `data-windows-titlebar` / `data-fullscreen` 标记驱动,插件按官方窗口框架变量自适应,无需按平台分别安装。专注模式下顶部为可拖动窗口的标题空白条;按原生全屏切换后(macOS 绿灯 / Windows 最大化按钮)自动收起预留空间。
|
|
130
|
+
|
|
131
|
+
> 桌面版使用独立的 `desktop` profile:如果只在 Web CLI 的 `web` profile 里装过本插件,桌面版仍需在 Plugins 页面安装一次。
|
|
132
|
+
|
|
133
|
+
> **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可以更改——新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。
|
|
121
134
|
|
|
122
135
|
## 使用
|
|
123
136
|
|
|
@@ -131,7 +144,11 @@ dsh web
|
|
|
131
144
|
|
|
132
145
|
## 设置
|
|
133
146
|
|
|
134
|
-
|
|
147
|
+
**dsh 0.2 起**:侧栏「Plugins」页面 → 找到 `dsh-focus-overlay` → 详情页中,配置直接显示在包描述与组件列表之间。
|
|
148
|
+
|
|
149
|
+
**dsh ≤ 0.1.x**:侧栏「设置 → 插件 → 插件配置」→ 展开「专注模式」卡片。
|
|
150
|
+
|
|
151
|
+
两种位置呈现同一组选项:
|
|
135
152
|
|
|
136
153
|
| 选项 | 说明 |
|
|
137
154
|
| --- | --- |
|
package/lib/client.js
CHANGED
|
@@ -3,9 +3,102 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
module.exports;
|
|
6
|
+
//#region \0rolldown/runtime.js
|
|
7
|
+
var __create = Object.create;
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
|
+
get: ((k) => from[k]).bind(null, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
//#endregion
|
|
6
28
|
let react = require("react");
|
|
7
29
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
30
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
8
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
//#region src/client/primitives.ts
|
|
33
|
+
/** Cross-release primitives resolution.
|
|
34
|
+
*
|
|
35
|
+
* dsh keeps renaming/removing symbols in `@deepseek-ai/dsh-client-ui-primitives`
|
|
36
|
+
* between releases, and a stale named import resolves to `undefined` — React
|
|
37
|
+
* then throws #130 ("element type is invalid") the moment it renders. Two
|
|
38
|
+
* such breaks are already in this repo's history: `MessageText` (0.1.3) and
|
|
39
|
+
* the fixed-size icon variants (0.2). The 0.2.2 desktop runtime adds a third:
|
|
40
|
+
* `projectUserText` is gone.
|
|
41
|
+
*
|
|
42
|
+
* The official user-text pipeline changed shape in 0.2.2: the conversation
|
|
43
|
+
* surface now parses text itself and renders inline nodes, falling back to
|
|
44
|
+
* the `UserTextRun` primitive (a plain inline run) for unmatched text. This
|
|
45
|
+
* module picks whichever projection the running primitives module actually
|
|
46
|
+
* exports, so one client bundle serves 0.1.2 through 0.2.2:
|
|
47
|
+
*
|
|
48
|
+
* - dsh 0.1.2 … 0.2.0: `projectUserText(text, sessionLabels, …)` returns
|
|
49
|
+
* inline plain runs plus `@session` / `/skill` chips (the richer path).
|
|
50
|
+
* - dsh 0.2.2+: `UserTextRun` renders the text as a plain inline run; the
|
|
51
|
+
* overlay's bubble chrome still supplies `white-space: pre-wrap`, so the
|
|
52
|
+
* text keeps its shape. Reference chips in *user* text are the only
|
|
53
|
+
* cosmetic loss on that generation.
|
|
54
|
+
* - Anything else: return the raw string (React renders it as a text node).
|
|
55
|
+
*
|
|
56
|
+
* Import the namespace once (not named symbols) so the resolver can never
|
|
57
|
+
* crash at module-evaluation time. */
|
|
58
|
+
/** Render one user prompt's text through the projection the running dsh
|
|
59
|
+
* exports. `sessionLabels` feeds the older chip projection; newer
|
|
60
|
+
* generations ignore it. */
|
|
61
|
+
function projectUserTextCompat(text, sessionLabels = []) {
|
|
62
|
+
const primitives = _deepseek_ai_dsh_client_ui_primitives;
|
|
63
|
+
const project = primitives.projectUserText;
|
|
64
|
+
if (typeof project === "function") try {
|
|
65
|
+
return project(text, sessionLabels);
|
|
66
|
+
} catch {}
|
|
67
|
+
const UserTextRun = primitives.UserTextRun;
|
|
68
|
+
if (typeof UserTextRun === "function") return (0, react.createElement)(UserTextRun, { text });
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/client/icons.ts
|
|
73
|
+
/** Cross-release product-icon resolution.
|
|
74
|
+
*
|
|
75
|
+
* The official primitives package renamed its icon set in dsh 0.2: the
|
|
76
|
+
* fixed-size artwork variants (`IconCheckOutline14`, `IconChevronDownOutline14`,
|
|
77
|
+
* `IconEditOutline16`, `IconSendOutline14`) were removed, leaving one
|
|
78
|
+
* `…OutlineRegular` / `…OutlineMedium` component per glyph that takes an
|
|
79
|
+
* explicit `size` prop. A stale named import therefore resolves to
|
|
80
|
+
* `undefined`, and React throws #130 ("element type is invalid") the moment
|
|
81
|
+
* the component renders — the same failure mode the 0.1.3 `MessageText`
|
|
82
|
+
* removal produced.
|
|
83
|
+
*
|
|
84
|
+
* Import the namespace once and pick the first name the running build exports,
|
|
85
|
+
* so one bundle serves both generations. Each fallback carries the size the
|
|
86
|
+
* removed variant was drawn at, keeping layout identical across releases. */
|
|
87
|
+
function pick(names, size) {
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
const component = _deepseek_ai_dsh_client_ui_primitives[name];
|
|
90
|
+
if (typeof component === "function") return (props) => component({
|
|
91
|
+
size,
|
|
92
|
+
...props
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return () => null;
|
|
96
|
+
}
|
|
97
|
+
const IconCheck = pick(["IconCheckOutline14", "IconCheckOutlineRegular"], 14);
|
|
98
|
+
const IconChevronDown = pick(["IconChevronDownOutline14", "IconChevronDownOutlineRegular"], 14);
|
|
99
|
+
const IconEdit = pick(["IconEditOutline16", "IconEditOutlineRegular"], 16);
|
|
100
|
+
const IconSend = pick(["IconSendOutline14", "IconSendOutlineRegular"], 14);
|
|
101
|
+
//#endregion
|
|
9
102
|
//#region src/client/model.ts
|
|
10
103
|
function legacySliceOf(chatView, snap) {
|
|
11
104
|
const legacy = chatView && chatView.legacy ? chatView.legacy : null;
|
|
@@ -25,6 +118,61 @@ window.__ModuleLoader__.load({
|
|
|
25
118
|
} catch {}
|
|
26
119
|
return null;
|
|
27
120
|
}
|
|
121
|
+
/** dsh 0.2 moved the pending interaction off the uiSession
|
|
122
|
+
* `pendingInteractions` map and onto the unified `sessionStatus` snapshot
|
|
123
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
124
|
+
* Both carriers answer with the same interaction face, so the overlay's answer
|
|
125
|
+
* card resolves through either. Null when nothing is pending. */
|
|
126
|
+
function pendingInteractionFromStatus(status, sessionId) {
|
|
127
|
+
try {
|
|
128
|
+
const entry = status && typeof status.get === "function" ? status.get(sessionId) : null;
|
|
129
|
+
return entry && entry.pendingInteraction || null;
|
|
130
|
+
} catch {}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/** The current session id, resolved across dsh generations.
|
|
134
|
+
*
|
|
135
|
+
* dsh <= 0.1.x carried the viewer's selection on the session-list snapshot
|
|
136
|
+
* (`sessions.list.getSnapshot().current`). dsh 0.2 dropped that field: the
|
|
137
|
+
* selection is the session the main view retains, published on the list rows
|
|
138
|
+
* themselves (`byId[id].retainedBy.mainView > 0`) — the same public read the
|
|
139
|
+
* official cordis inspector uses. `mainBinding` is the uiSession main-binding
|
|
140
|
+
* snapshot, kept as a guarded last resort: it carries the same id, but that
|
|
141
|
+
* member is typed private, so the public list data stays primary. */
|
|
142
|
+
function currentSessionIdOf(listState, mainBinding) {
|
|
143
|
+
const fromList = listState ? listState.current : void 0;
|
|
144
|
+
if (fromList !== void 0 && fromList !== null) return fromList;
|
|
145
|
+
const byId = listState && listState.byId;
|
|
146
|
+
if (byId && typeof byId === "object") {
|
|
147
|
+
for (const row of Object.values(byId)) if (row && (row.retainedBy && row.retainedBy.mainView || 0) > 0) return row.id;
|
|
148
|
+
}
|
|
149
|
+
if (mainBinding) {
|
|
150
|
+
const id = mainBinding.key ?? (mainBinding.props ? mainBinding.props.sessionId : void 0);
|
|
151
|
+
if (id !== void 0 && id !== null) return id;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** The overlay fold mode derived from the official "对话显示" preference.
|
|
155
|
+
*
|
|
156
|
+
* dsh <= 0.1.x served it through the `settingsScope` service bound to the
|
|
157
|
+
* `ui-chat` namespace (`{ transcriptView: 'normal' | 'compact' }`). dsh 0.2
|
|
158
|
+
* retired `settingsScope`; the same preference is a Host settings field read
|
|
159
|
+
* through `configForms.get('ui-chat')`, whose snapshot nests it under `value`
|
|
160
|
+
* (and widened the accepted set to compact | standard | detailed | verbose,
|
|
161
|
+
* with `normal` / `expanded` kept as legacy saved values). Only `compact`
|
|
162
|
+
* folds the tool flow; every other explicit mode reads as the expanded
|
|
163
|
+
* presentation. Absence keeps the overlay's own compact default. */
|
|
164
|
+
function transcriptViewOf(scopeOrForm) {
|
|
165
|
+
try {
|
|
166
|
+
if (!scopeOrForm || typeof scopeOrForm.getSnapshot !== "function") return "compact";
|
|
167
|
+
const snapshot = scopeOrForm.getSnapshot();
|
|
168
|
+
const section = snapshot && snapshot.value && typeof snapshot.value === "object" ? snapshot.value : snapshot;
|
|
169
|
+
const mode = section ? section.transcriptView : null;
|
|
170
|
+
if (mode === void 0 || mode === null) return "compact";
|
|
171
|
+
return mode === "compact" ? "compact" : "normal";
|
|
172
|
+
} catch {
|
|
173
|
+
return "compact";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
28
176
|
/** Legacy pending interaction — pre-0.1.2 dsh carried a flat `pending` array
|
|
29
177
|
* on the session snapshot, with a different carrier shape (`payload`
|
|
30
178
|
* envelope, answers via `wait.respond({ok, value})` returning a `receipt`).
|
|
@@ -70,6 +218,66 @@ window.__ModuleLoader__.load({
|
|
|
70
218
|
});
|
|
71
219
|
return shaped;
|
|
72
220
|
}
|
|
221
|
+
/** Build the `dsh-resource://file/session/<id>/<path>` address that dsh 0.2's
|
|
222
|
+
* Sidebar resource controller opens a file through.
|
|
223
|
+
*
|
|
224
|
+
* The official `fileAddressFor` lives in `@deepseek-ai/dsh-util-workspace-path`,
|
|
225
|
+
* but the client bundle purity gate keeps feature bundles from importing other
|
|
226
|
+
* plugins' values across the boundary — the official Chat package inlines its
|
|
227
|
+
* own copy of the same grammar, and so does this one. Absolute paths inside the
|
|
228
|
+
* session workspace are reduced to workspace-relative; every id and path
|
|
229
|
+
* segment is component-encoded, with `:` kept literal for Windows drives. */
|
|
230
|
+
function sessionFileAddressOf(sessionId, cwd, path) {
|
|
231
|
+
const encode = (segment) => encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
232
|
+
const normalized = String(path).replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
233
|
+
const absolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
|
|
234
|
+
let relative = normalized;
|
|
235
|
+
if (absolute) {
|
|
236
|
+
const root = cwd === void 0 || cwd === null ? "" : String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
237
|
+
if (root !== "" && normalized === root) relative = "";
|
|
238
|
+
else if (root !== "" && normalized.startsWith(root + "/")) relative = normalized.slice(root.length + 1);
|
|
239
|
+
}
|
|
240
|
+
const body = relative.split("/").map(encode).join("/");
|
|
241
|
+
return `dsh-resource://file/session/${encode(String(sessionId))}/${body}`;
|
|
242
|
+
}
|
|
243
|
+
/** Resolve the owner object the official `chatFileMentions.forClosing` expects.
|
|
244
|
+
*
|
|
245
|
+
* dsh 0.2.x's provider resolves a turn's produced / presented files through
|
|
246
|
+
* `owner.turn.data.get('deliverables')` — the owner the official ui-chat turn
|
|
247
|
+
* tail passes — and throws on the plain `{turn: {turn, step}}` owner shape the
|
|
248
|
+
* overlay used to pass; the caller's catch then silently disabled the inline
|
|
249
|
+
* file-mention chips (pre-existing on every 0.2 build, not a desktop
|
|
250
|
+
* regression). The Chat snapshot's `timeline.turns` carries the real Turn
|
|
251
|
+
* faces, so look one up by the assistant node's turn number. Chips belong to
|
|
252
|
+
* the settled closing reply only, so a missing or still-running turn resolves
|
|
253
|
+
* to null. Generations without a `timeline` (0.1.x) keep the legacy shape
|
|
254
|
+
* their provider accepts. */
|
|
255
|
+
function fileMentionOwnerOf(chatView, node, openFile) {
|
|
256
|
+
const turns = chatView && chatView.timeline ? chatView.timeline.turns : null;
|
|
257
|
+
if (turns && typeof turns.get === "function") {
|
|
258
|
+
const turnFace = turns.get(node && node.turn);
|
|
259
|
+
if (!turnFace || turnFace.status !== "closed") return null;
|
|
260
|
+
return {
|
|
261
|
+
owner: {
|
|
262
|
+
turn: turnFace,
|
|
263
|
+
seq: node.seq,
|
|
264
|
+
openFile
|
|
265
|
+
},
|
|
266
|
+
modern: true
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
owner: {
|
|
271
|
+
turn: {
|
|
272
|
+
turn: node.turn,
|
|
273
|
+
step: node.step
|
|
274
|
+
},
|
|
275
|
+
seq: node.seq,
|
|
276
|
+
openFile
|
|
277
|
+
},
|
|
278
|
+
modern: false
|
|
279
|
+
};
|
|
280
|
+
}
|
|
73
281
|
function flattenText(content) {
|
|
74
282
|
if (!content || !content.length) return "";
|
|
75
283
|
let out = "";
|
|
@@ -933,7 +1141,7 @@ window.__ModuleLoader__.load({
|
|
|
933
1141
|
"onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
|
|
934
1142
|
"onboarding.feature.hotkey": "快捷键 F —— 任意界面按 F 一键进入专注,可在设置中关闭",
|
|
935
1143
|
"onboarding.configure.title": "快速配置",
|
|
936
|
-
"onboarding.laterNote": "
|
|
1144
|
+
"onboarding.laterNote": "这些设置随时可以更改:新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。",
|
|
937
1145
|
"onboarding.openSettings": "打开设置",
|
|
938
1146
|
"onboarding.done": "完成"
|
|
939
1147
|
};
|
|
@@ -1019,7 +1227,7 @@ window.__ModuleLoader__.load({
|
|
|
1019
1227
|
"onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
|
|
1020
1228
|
"onboarding.feature.hotkey": "Hotkey F — press F anywhere to enter focus mode (toggleable in settings)",
|
|
1021
1229
|
"onboarding.configure.title": "Quick setup",
|
|
1022
|
-
"onboarding.laterNote": "
|
|
1230
|
+
"onboarding.laterNote": "Change these anytime: on newer dsh open this plugin’s detail page from the sidebar Plugins page; on older dsh use Settings → Plugins → Plugin configuration.",
|
|
1023
1231
|
"onboarding.openSettings": "Open settings",
|
|
1024
1232
|
"onboarding.done": "Done"
|
|
1025
1233
|
};
|
|
@@ -1109,7 +1317,7 @@ window.__ModuleLoader__.load({
|
|
|
1109
1317
|
"aria-pressed": on,
|
|
1110
1318
|
children: [multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1111
1319
|
className: "fm-opt-check" + (on ? " fm-opt-check-on" : ""),
|
|
1112
|
-
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1320
|
+
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, {}) : null
|
|
1113
1321
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1114
1322
|
className: "fm-opt-num",
|
|
1115
1323
|
children: index + 1
|
|
@@ -1220,7 +1428,7 @@ window.__ModuleLoader__.load({
|
|
|
1220
1428
|
title: t("answer.close"),
|
|
1221
1429
|
"aria-label": t("answer.close"),
|
|
1222
1430
|
onClick: onClose,
|
|
1223
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1431
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1224
1432
|
})]
|
|
1225
1433
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1226
1434
|
className: "fm-card-head",
|
|
@@ -1236,7 +1444,7 @@ window.__ModuleLoader__.load({
|
|
|
1236
1444
|
title: t("answer.close"),
|
|
1237
1445
|
"aria-label": t("answer.close"),
|
|
1238
1446
|
onClick: onClose,
|
|
1239
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1447
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1240
1448
|
})
|
|
1241
1449
|
]
|
|
1242
1450
|
}),
|
|
@@ -1434,7 +1642,7 @@ window.__ModuleLoader__.load({
|
|
|
1434
1642
|
disabled: empty,
|
|
1435
1643
|
onMouseDown: (e) => e.preventDefault(),
|
|
1436
1644
|
onClick: onSend,
|
|
1437
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1645
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSend, {})
|
|
1438
1646
|
})
|
|
1439
1647
|
]
|
|
1440
1648
|
})
|
|
@@ -1471,7 +1679,7 @@ window.__ModuleLoader__.load({
|
|
|
1471
1679
|
title: t("composer.expand"),
|
|
1472
1680
|
"aria-label": t("composer.expand"),
|
|
1473
1681
|
onClick: onExpand,
|
|
1474
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1682
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEdit, {}), hasDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "fm-pill-dot" }) : null]
|
|
1475
1683
|
});
|
|
1476
1684
|
}
|
|
1477
1685
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1480,7 +1688,7 @@ window.__ModuleLoader__.load({
|
|
|
1480
1688
|
title: t("backToLatest"),
|
|
1481
1689
|
"aria-label": t("backToLatest"),
|
|
1482
1690
|
onClick: onJumpBottom,
|
|
1483
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1691
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 16 })
|
|
1484
1692
|
});
|
|
1485
1693
|
}
|
|
1486
1694
|
//#endregion
|
|
@@ -1653,7 +1861,34 @@ window.__ModuleLoader__.load({
|
|
|
1653
1861
|
//#region src/client/styles.ts
|
|
1654
1862
|
/** Package-owned stylesheet text (injected by the client apply, cleaned up on unload). */
|
|
1655
1863
|
const FOCUS_CSS = `
|
|
1656
|
-
.fm-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#1a1a1a);display:flex;flex-direction:column;pointer-events:auto;font-family:var(--dsw-font-family,sans-serif);outline:none}
|
|
1864
|
+
.fm-overlay{position:fixed;inset:0;z-index:2147483000;box-sizing:border-box;padding-top:var(--dsh-frame-overlay-top,0px);background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#1a1a1a);display:flex;flex-direction:column;pointer-events:auto;font-family:var(--dsw-font-family,sans-serif);outline:none}
|
|
1865
|
+
/* Desktop window frame (dsh desktop 0.2.2+, macOS and Windows): the shell's
|
|
1866
|
+
hidden native titlebar area is web content owned by whatever fills the
|
|
1867
|
+
viewport, so this overlay reserves it via *.fm-overlay's padding-top and
|
|
1868
|
+
provides the drag surface itself. Heights come from the official layout
|
|
1869
|
+
variables set on <html> by dsh-client-ui-layout (+ the desktop preload for
|
|
1870
|
+
--dsh-windows-titlebar-height); the 0px fallback keeps the plain web
|
|
1871
|
+
generation pixel-identical. On macOS the shell's own
|
|
1872
|
+
[data-window-drag]{-webkit-app-region:drag} applies; the explicit
|
|
1873
|
+
app-region also makes the strip draggable in the Windows renderer.
|
|
1874
|
+
pointer-events stays enabled: a drag region must be hit-testable. */
|
|
1875
|
+
.fm-frame-strip{position:absolute;top:0;left:0;right:0;height:var(--dsh-frame-overlay-top,0px);flex:none;-webkit-app-region:drag;-webkit-user-select:none;user-select:none;z-index:0}
|
|
1876
|
+
/* Windows desktop: dsh-client-ui-sidebar promotes the sidebar collapse toggle
|
|
1877
|
+
to a fixed z-30 layer itself, above the official shell.overlay layer (z-20)
|
|
1878
|
+
that hosts this overlay — so without this rule the toggle floats above focus
|
|
1879
|
+
mode inside the titlebar band and steals clicks/drags from the drag strip
|
|
1880
|
+
(macOS keeps it behind the overlay). Hide it exactly while focus is on; it
|
|
1881
|
+
comes back with the rest of the chrome when focus exits. */
|
|
1882
|
+
html[data-windows-titlebar] body[data-fm-focus] [data-sidebar-toggle]{display:none!important}
|
|
1883
|
+
/* Same Windows titlebar band: when the official sidebar is collapsed (user
|
|
1884
|
+
collapsed it, or the window is narrower than 768px — Windows has no drawer),
|
|
1885
|
+
dsh-client-ui-sidebar promotes its "new session" control to the same fixed
|
|
1886
|
+
z-30 titlebar layer (left:48px, top:6px). It would sit above the overlay and
|
|
1887
|
+
click-through would start a new session under the reader, so hide it with
|
|
1888
|
+
the rest of the window chrome while focus is on. The hashed class name is
|
|
1889
|
+
the only stable hook the shipped CSS exposes; the e2e desktop-frame leak
|
|
1890
|
+
guard fails loudly if a future build renames it. */
|
|
1891
|
+
html[data-windows-titlebar] body[data-fm-focus] [class*="newSession"]{display:none!important}
|
|
1657
1892
|
.fm-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));flex:0 0 auto}
|
|
1658
1893
|
.fm-title{font-size:calc(14px + var(--dsh-content-font-delta,0px));font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1659
1894
|
.fm-body-wrap{position:relative;flex:1 1 auto;display:flex;min-height:0}
|
|
@@ -1813,6 +2048,9 @@ window.__ModuleLoader__.load({
|
|
|
1813
2048
|
.fm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
|
|
1814
2049
|
.fm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
1815
2050
|
.fm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:16px}
|
|
2051
|
+
/* dsh 0.2 Plugins-page bundle configuration: the page draws its own title,
|
|
2052
|
+
icon and crumb, so this surface is body-only — no card border or collapse. */
|
|
2053
|
+
.fm-plugin-page{flex-direction:column;display:flex}
|
|
1816
2054
|
.fm-plugin-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
1817
2055
|
.fm-plugin-field+.fm-plugin-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
1818
2056
|
/* The stats row ends the card body, so it carries NO bottom padding of its
|
|
@@ -2266,16 +2504,18 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2266
2504
|
}
|
|
2267
2505
|
/** Body of a sent user message / steering row.
|
|
2268
2506
|
*
|
|
2269
|
-
* dsh 0.1.3-alpha.2
|
|
2270
|
-
*
|
|
2271
|
-
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
*
|
|
2275
|
-
*
|
|
2276
|
-
* `
|
|
2507
|
+
* The official user-text projection has now changed twice: dsh 0.1.3-alpha.2
|
|
2508
|
+
* removed `MessageText` in favour of `projectUserText` (which this component
|
|
2509
|
+
* then imported as a named binding — React #130 the moment a user row
|
|
2510
|
+
* rendered when the later removal made it `undefined`), and dsh 0.2.2's
|
|
2511
|
+
* desktop runtime removed `projectUserText` itself (the conversation surface
|
|
2512
|
+
* parses user text and falls back to the `UserTextRun` primitive).
|
|
2513
|
+
* `projectUserTextCompat` resolves the projection the running primitives
|
|
2514
|
+
* module exports (`projectUserText` → `UserTextRun` → raw text), so one
|
|
2515
|
+
* bundle serves 0.1.2 through 0.2.2. `.fm-user` still supplies the bubble
|
|
2516
|
+
* chrome and `white-space: pre-wrap`, so the text keeps its shape. */
|
|
2277
2517
|
function UserText({ text }) {
|
|
2278
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: (
|
|
2518
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: projectUserTextCompat(text) });
|
|
2279
2519
|
}
|
|
2280
2520
|
let bodyEl = null;
|
|
2281
2521
|
const anchors = {};
|
|
@@ -2301,13 +2541,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2301
2541
|
return snap;
|
|
2302
2542
|
}
|
|
2303
2543
|
/** The session's pending user interaction (question / plan review / approval),
|
|
2304
|
-
* read from the uiSession
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
2544
|
+
* read from the uiSession services. dsh 0.1.2 exposed the root-level
|
|
2545
|
+
* `pendingInteractions` map; dsh 0.2 unified it into `sessionStatus`
|
|
2546
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
2547
|
+
* When neither service is present (older dsh), falls back to the session
|
|
2548
|
+
* snapshot's flat `pending` array through the `legacyPendingOf` adapter.
|
|
2549
|
+
* Returns null when this session has nothing pending. */
|
|
2309
2550
|
function usePendingInteraction(uiSession, sessionId, snap) {
|
|
2310
|
-
const
|
|
2551
|
+
const legacyMap = uiSession && uiSession.pendingInteractions;
|
|
2552
|
+
const statusMap = uiSession && uiSession.sessionStatus;
|
|
2553
|
+
const source = legacyMap || statusMap;
|
|
2311
2554
|
const read = () => {
|
|
2312
2555
|
try {
|
|
2313
2556
|
return source ? source.getSnapshot() : null;
|
|
@@ -2328,7 +2571,47 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2328
2571
|
return;
|
|
2329
2572
|
}
|
|
2330
2573
|
}, [source]);
|
|
2331
|
-
return pendingInteractionOf(map, sessionId) || legacyPendingOf(snap);
|
|
2574
|
+
return (legacyMap ? pendingInteractionOf(map, sessionId) : pendingInteractionFromStatus(map, sessionId)) || legacyPendingOf(snap);
|
|
2575
|
+
}
|
|
2576
|
+
/** The viewed session id, resolved across dsh generations. dsh <= 0.1.x carried
|
|
2577
|
+
* the selection on the session-list snapshot (`listState.current`); dsh 0.2
|
|
2578
|
+
* dropped it and publishes the main view's retained session as the uiSession
|
|
2579
|
+
* main binding. Subscribes to whichever source exists (both, when both do), so
|
|
2580
|
+
* switching sessions re-resolves either way. */
|
|
2581
|
+
function useCurrentSessionId(sessions, uiSession, listState) {
|
|
2582
|
+
const read = () => {
|
|
2583
|
+
if (!sessions || typeof sessions.list?.getSnapshot !== "function") return void 0;
|
|
2584
|
+
let mainBinding = null;
|
|
2585
|
+
try {
|
|
2586
|
+
mainBinding = uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
2587
|
+
} catch {
|
|
2588
|
+
mainBinding = null;
|
|
2589
|
+
}
|
|
2590
|
+
return currentSessionIdOf(sessions.list.getSnapshot(), mainBinding);
|
|
2591
|
+
};
|
|
2592
|
+
const [id, setId] = (0, react.useState)(read);
|
|
2593
|
+
(0, react.useEffect)(() => {
|
|
2594
|
+
if (!sessions || typeof sessions.list?.subscribe !== "function") {
|
|
2595
|
+
setId(void 0);
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const sync = () => setId(read());
|
|
2599
|
+
sync();
|
|
2600
|
+
const unsubs = [];
|
|
2601
|
+
try {
|
|
2602
|
+
unsubs.push(sessions.list.subscribe(sync));
|
|
2603
|
+
} catch {}
|
|
2604
|
+
const main = uiSession && uiSession.current;
|
|
2605
|
+
if (main && typeof main.subscribe === "function") try {
|
|
2606
|
+
unsubs.push(main.subscribe(sync));
|
|
2607
|
+
} catch {}
|
|
2608
|
+
return () => {
|
|
2609
|
+
for (const u of unsubs) try {
|
|
2610
|
+
u();
|
|
2611
|
+
} catch {}
|
|
2612
|
+
};
|
|
2613
|
+
}, [sessions, uiSession]);
|
|
2614
|
+
return id;
|
|
2332
2615
|
}
|
|
2333
2616
|
/** The session's Chat view snapshot (dsh 0.1.2 `ChatSnapshot`) from the
|
|
2334
2617
|
* uiConversation assembly — the first subscriber activates the chat target,
|
|
@@ -2355,20 +2638,25 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2355
2638
|
}, [uiConversation, sessionId]);
|
|
2356
2639
|
return chat;
|
|
2357
2640
|
}
|
|
2358
|
-
/** The official "对话显示" preference (`ui-chat.transcriptView
|
|
2359
|
-
*
|
|
2360
|
-
* TranscriptViewPolicy binds
|
|
2361
|
-
*
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2641
|
+
/** The official "对话显示" preference (`ui-chat.transcriptView`), read
|
|
2642
|
+
* reactively. dsh <= 0.1.x serves it through the settings scope the official
|
|
2643
|
+
* TranscriptViewPolicy binds (values normal | compact); dsh 0.2 retired that
|
|
2644
|
+
* service and serves the same field through the `configForms` controller for
|
|
2645
|
+
* the `ui-chat` namespace (compact | standard | detailed | verbose, with the
|
|
2646
|
+
* legacy normal | expanded values still accepted). Both are read through the
|
|
2647
|
+
* shared `transcriptViewOf` projection; only `compact` folds the tool flow.
|
|
2648
|
+
* Defaults to compact when no preference has arrived yet. */
|
|
2649
|
+
function useTranscriptViewMode(settingsScope, configForms) {
|
|
2650
|
+
const form = (0, react.useMemo)(() => {
|
|
2651
|
+
if (settingsScope) return settingsScope;
|
|
2365
2652
|
try {
|
|
2366
|
-
|
|
2367
|
-
return (section ? section.transcriptView : null) === "normal" ? "normal" : "compact";
|
|
2653
|
+
return configForms && typeof configForms.get === "function" ? configForms.get("ui-chat") : null;
|
|
2368
2654
|
} catch {
|
|
2369
|
-
return
|
|
2655
|
+
return null;
|
|
2370
2656
|
}
|
|
2371
|
-
};
|
|
2657
|
+
}, [settingsScope, configForms]);
|
|
2658
|
+
const source = form && typeof form.getSnapshot === "function" ? form : null;
|
|
2659
|
+
const read = () => transcriptViewOf(source);
|
|
2372
2660
|
const [mode, setMode] = (0, react.useState)(read);
|
|
2373
2661
|
(0, react.useEffect)(() => {
|
|
2374
2662
|
if (!source) {
|
|
@@ -2433,13 +2721,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2433
2721
|
return seat ? seat.querySelector("[contenteditable=\"true\"]") : null;
|
|
2434
2722
|
}
|
|
2435
2723
|
function FocusContent(props) {
|
|
2436
|
-
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, t } = props;
|
|
2724
|
+
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, configForms, sidebarRight, t } = props;
|
|
2437
2725
|
const prefs = usePrefs();
|
|
2438
2726
|
const listState = useSessions((s) => s);
|
|
2439
|
-
const currentId =
|
|
2727
|
+
const currentId = useCurrentSessionId(sessions, uiSession, listState);
|
|
2440
2728
|
const snap = useSessionSnapshot(sessions, currentId);
|
|
2441
2729
|
const chatView = useChatView(uiConversation, currentId);
|
|
2442
|
-
const transcriptView = useTranscriptViewMode(settingsScope);
|
|
2730
|
+
const transcriptView = useTranscriptViewMode(settingsScope, configForms);
|
|
2443
2731
|
const legacy = (0, react.useMemo)(() => legacySliceOf(chatView, snap), [chatView, snap]);
|
|
2444
2732
|
const stable = legacy.partial == null;
|
|
2445
2733
|
const mdLabels = (0, react.useMemo)(() => markdownLabels(t), [t]);
|
|
@@ -2765,23 +3053,49 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2765
3053
|
if (conversation && typeof conversation.resolveImage === "function") return conversation.resolveImage(currentId, attachment);
|
|
2766
3054
|
return Promise.reject(/* @__PURE__ */ new Error("dsh-focus-overlay: image resolver unavailable"));
|
|
2767
3055
|
}
|
|
3056
|
+
const openMentionedFile = (0, react.useCallback)((path, options) => {
|
|
3057
|
+
try {
|
|
3058
|
+
if (workspaces && typeof workspaces.openPath === "function") {
|
|
3059
|
+
workspaces.openPath(path).catch(() => {});
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
const cwd = currentId != null ? sessions?.list?.getSnapshot?.()?.byId?.[currentId]?.cwd : void 0;
|
|
3063
|
+
if (sidebarRight && currentId != null && typeof sidebarRight.openResource === "function") {
|
|
3064
|
+
const address = sessionFileAddressOf(String(currentId), cwd, path);
|
|
3065
|
+
sidebarRight.openResource(address, options && options.line !== void 0 ? { params: { line: options.line } } : void 0);
|
|
3066
|
+
}
|
|
3067
|
+
} catch {}
|
|
3068
|
+
}, [
|
|
3069
|
+
sidebarRight,
|
|
3070
|
+
workspaces,
|
|
3071
|
+
sessions,
|
|
3072
|
+
currentId
|
|
3073
|
+
]);
|
|
3074
|
+
/** Assistant-row inline-code file mentions. The official provider resolves
|
|
3075
|
+
* only a turn's produced / presented files, and dsh 0.2.x reads them from
|
|
3076
|
+
* the real Turn face (`owner.turn.data.get('deliverables')`) — the same
|
|
3077
|
+
* owner the official ui-chat turn tail passes. Handing it a plain
|
|
3078
|
+
* `{turn: number, step}` makes `forClosing` throw, and the plugin's catch
|
|
3079
|
+
* silently disabled the chips on every 0.2 build (pre-existing bug, not a
|
|
3080
|
+
* 0.2.2 regression). `fileMentionOwnerOf` (model.ts) resolves the Turn face
|
|
3081
|
+
* from the Chat snapshot's `timeline.turns`, and keeps the legacy owner
|
|
3082
|
+
* shape for generations without a timeline. */
|
|
2768
3083
|
const fileMentionsFor = (0, react.useCallback)((node) => {
|
|
2769
3084
|
if (!chatFileMentions) return void 0;
|
|
2770
3085
|
try {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
},
|
|
2776
|
-
seq: node.seq,
|
|
2777
|
-
openFile: (path) => {
|
|
2778
|
-
if (workspaces) workspaces.openPath(path).catch(() => {});
|
|
2779
|
-
}
|
|
2780
|
-
});
|
|
3086
|
+
const openFile = (path, options) => openMentionedFile(path, options);
|
|
3087
|
+
const resolved = fileMentionOwnerOf(chatView, node, openFile);
|
|
3088
|
+
if (resolved === null) return void 0;
|
|
3089
|
+
return chatFileMentions.forClosing(resolved.owner, currentId);
|
|
2781
3090
|
} catch {
|
|
2782
3091
|
return;
|
|
2783
3092
|
}
|
|
2784
|
-
}, [
|
|
3093
|
+
}, [
|
|
3094
|
+
chatFileMentions,
|
|
3095
|
+
chatView,
|
|
3096
|
+
currentId,
|
|
3097
|
+
openMentionedFile
|
|
3098
|
+
]);
|
|
2785
3099
|
const binding = (0, react.useMemo)(() => sessions && currentId != null ? sessions.binding(currentId) : void 0, [sessions, currentId]);
|
|
2786
3100
|
const inputFace = useInputFace(conversation, binding && binding.ctx);
|
|
2787
3101
|
const inputState = useInputState(inputFace);
|
|
@@ -2829,7 +3143,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2829
3143
|
setDockFocused(f);
|
|
2830
3144
|
if (!f) setEngaged(false);
|
|
2831
3145
|
};
|
|
2832
|
-
const queueCount = snap && snap.queue ? snap.queue.length : 0;
|
|
3146
|
+
const queueCount = inputState && Array.isArray(inputState.queue) ? inputState.queue.length : snap && snap.queue ? snap.queue.length : 0;
|
|
2833
3147
|
const occCount = inputState && inputState.occurrences ? inputState.occurrences.length : 0;
|
|
2834
3148
|
const promptErr = snap && snap.promptError && snap.promptError.op === "send" ? snap.promptError.error : null;
|
|
2835
3149
|
const errorLine = promptErr ? promptErr.message || promptErr.code || "send failed" : null;
|
|
@@ -3037,7 +3351,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3037
3351
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3038
3352
|
className: "fm-process-label",
|
|
3039
3353
|
children: processLabel(it.counts, t)
|
|
3040
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3354
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-process-chevron" })]
|
|
3041
3355
|
}) : null, open ? it.workItems.map((wi, wi2) => renderItem(wi, key + "-w" + wi2, false)) : null] }, key);
|
|
3042
3356
|
}
|
|
3043
3357
|
return null;
|
|
@@ -3147,6 +3461,11 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3147
3461
|
ref: overlayRef,
|
|
3148
3462
|
tabIndex: -1,
|
|
3149
3463
|
children: [
|
|
3464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3465
|
+
className: "fm-frame-strip",
|
|
3466
|
+
"data-window-drag": true,
|
|
3467
|
+
"aria-hidden": "true"
|
|
3468
|
+
}),
|
|
3150
3469
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3151
3470
|
className: "fm-topbar",
|
|
3152
3471
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -3371,6 +3690,15 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3371
3690
|
})
|
|
3372
3691
|
});
|
|
3373
3692
|
}
|
|
3693
|
+
/** The settings body both plugin-settings surfaces share: the preference
|
|
3694
|
+
* fields plus the focus-entry counter. Rendered by the legacy Settings card
|
|
3695
|
+
* and by the dsh 0.2 Plugins-page bundle page. */
|
|
3696
|
+
function FocusSettingsBody({ t }) {
|
|
3697
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusEntryStat, { t })] });
|
|
3698
|
+
}
|
|
3699
|
+
/** Legacy surface (dsh <= 0.1.x): a collapsible `<li>` card in the Settings
|
|
3700
|
+
* → Plugins tab, registered into the keyed `settings.plugin.item` slot. dsh
|
|
3701
|
+
* 0.2 removed that slot, so this only renders where the slot is declared. */
|
|
3374
3702
|
function FocusSettingsCard({ t }) {
|
|
3375
3703
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3376
3704
|
const title = t("settings.label");
|
|
@@ -3391,13 +3719,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3391
3719
|
className: "fm-plugin-card-desc",
|
|
3392
3720
|
children: t("settings.description")
|
|
3393
3721
|
})]
|
|
3394
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3395
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3722
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
|
|
3723
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3396
3724
|
className: "fm-plugin-card-body",
|
|
3397
|
-
children:
|
|
3725
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3398
3726
|
}) : null]
|
|
3399
3727
|
});
|
|
3400
3728
|
}
|
|
3729
|
+
/** dsh 0.2 surface: the bundle's own configuration on the sidebar Plugins
|
|
3730
|
+
* page (`plugins.bundle.config`, keyed by the bundle's package name). The page
|
|
3731
|
+
* draws the title, icon, and crumb itself and always asks for `view: 'page'`,
|
|
3732
|
+
* so this renders the body alone — no card chrome, no collapse. */
|
|
3733
|
+
function FocusBundleConfig({ t, view }) {
|
|
3734
|
+
if (view !== void 0 && view !== "page") return null;
|
|
3735
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3736
|
+
className: "fm-plugin-page",
|
|
3737
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3401
3740
|
function FocusOnboarding(props) {
|
|
3402
3741
|
const { complete, openSection, t } = props;
|
|
3403
3742
|
const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
|
|
@@ -3429,7 +3768,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3429
3768
|
onboardingStore.markDone();
|
|
3430
3769
|
setDone(true);
|
|
3431
3770
|
finish();
|
|
3432
|
-
|
|
3771
|
+
let opened = false;
|
|
3772
|
+
if (typeof props.openSettings === "function") try {
|
|
3773
|
+
opened = props.openSettings() !== false;
|
|
3774
|
+
} catch {
|
|
3775
|
+
opened = false;
|
|
3776
|
+
}
|
|
3777
|
+
if (!opened && typeof openSection === "function") openSection("plugins");
|
|
3433
3778
|
};
|
|
3434
3779
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3435
3780
|
open: true,
|
|
@@ -3494,13 +3839,20 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3494
3839
|
/**
|
|
3495
3840
|
* Focus Mode, browser half: a full-screen reading overlay in `shell.overlay`
|
|
3496
3841
|
* (additive, never replaces shipped UI), a "专注" button in the session header
|
|
3497
|
-
* action row, and
|
|
3498
|
-
*
|
|
3499
|
-
*
|
|
3500
|
-
*
|
|
3501
|
-
*
|
|
3842
|
+
* action row, and the plugin's settings card. The settings surface lives where
|
|
3843
|
+
* the running dsh keeps it: the Settings → Plugins tab's keyed
|
|
3844
|
+
* `settings.plugin.item` slot on dsh <= 0.1.x, and the sidebar Plugins page's
|
|
3845
|
+
* `plugins.bundle.config` slot (keyed by this bundle's package name) on dsh
|
|
3846
|
+
* 0.2, which retired both that slot and the `settingsScope` service. Rendering
|
|
3847
|
+
* reuses the official client UI primitives module
|
|
3848
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` runtime id) — Markdown /
|
|
3849
|
+
* projectUserText / Button — plus the conversation service's composer registry
|
|
3850
|
+
* and the uiSession pending-interaction / main-binding service.
|
|
3502
3851
|
*/
|
|
3503
3852
|
const NS = "focus";
|
|
3853
|
+
/** This bundle's package name — the key `plugins.bundle.config` and the
|
|
3854
|
+
* `pluginNavigation.openBundle` deep link address this plugin by (dsh 0.2). */
|
|
3855
|
+
const PACKAGE_NAME = "dsh-focus-overlay";
|
|
3504
3856
|
let warnedLegacy = false;
|
|
3505
3857
|
function warnLegacyOnce() {
|
|
3506
3858
|
if (warnedLegacy) return;
|
|
@@ -3516,8 +3868,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3516
3868
|
"locale",
|
|
3517
3869
|
"workspaces",
|
|
3518
3870
|
"uiSession",
|
|
3519
|
-
"uiConversation"
|
|
3520
|
-
"settingsScope"
|
|
3871
|
+
"uiConversation"
|
|
3521
3872
|
],
|
|
3522
3873
|
apply(ctx) {
|
|
3523
3874
|
const style = document.createElement("style");
|
|
@@ -3550,6 +3901,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3550
3901
|
const workspaces = ctx.workspaces;
|
|
3551
3902
|
const conversation = ctx.get("conversation");
|
|
3552
3903
|
const chatFileMentions = ctx.get("chatFileMentions");
|
|
3904
|
+
const sidebarRight = ctx.get("sidebarRight");
|
|
3553
3905
|
const uiSession = ctx.get("uiSession");
|
|
3554
3906
|
const uiConversation = ctx.get("uiConversation");
|
|
3555
3907
|
if (!uiSession || !uiConversation) warnLegacyOnce();
|
|
@@ -3560,6 +3912,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3560
3912
|
} catch {
|
|
3561
3913
|
settingsScope = null;
|
|
3562
3914
|
}
|
|
3915
|
+
const configForms = ctx.get("configForms");
|
|
3563
3916
|
ctx.effect(() => {
|
|
3564
3917
|
let currentId = void 0;
|
|
3565
3918
|
let unsubSession = null;
|
|
@@ -3622,18 +3975,27 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3622
3975
|
} catch {}
|
|
3623
3976
|
if (!unsubChat) chatSlice = legacySliceOf(null, face.getSnapshot());
|
|
3624
3977
|
};
|
|
3978
|
+
const mainBinding = () => {
|
|
3979
|
+
try {
|
|
3980
|
+
return uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
3981
|
+
} catch {
|
|
3982
|
+
return null;
|
|
3983
|
+
}
|
|
3984
|
+
};
|
|
3625
3985
|
const onList = () => {
|
|
3626
|
-
const
|
|
3627
|
-
const next = ls ? ls.current : void 0;
|
|
3986
|
+
const next = currentSessionIdOf(sessions.list.getSnapshot(), mainBinding());
|
|
3628
3987
|
if (next !== currentId) {
|
|
3629
3988
|
currentId = next;
|
|
3630
3989
|
watch(next);
|
|
3631
|
-
}
|
|
3990
|
+
} else if (next != null && !unsubSession) watch(next);
|
|
3632
3991
|
};
|
|
3633
3992
|
onList();
|
|
3634
3993
|
const unsubList = sessions.list.subscribe(onList);
|
|
3994
|
+
const mainSource = uiSession && uiSession.current;
|
|
3995
|
+
const unsubMain = mainSource && typeof mainSource.subscribe === "function" ? mainSource.subscribe(onList) : null;
|
|
3635
3996
|
return () => {
|
|
3636
3997
|
if (unsubList) unsubList();
|
|
3998
|
+
if (unsubMain) unsubMain();
|
|
3637
3999
|
if (unsubSession) unsubSession();
|
|
3638
4000
|
if (unsubChat) unsubChat();
|
|
3639
4001
|
};
|
|
@@ -3668,6 +4030,8 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3668
4030
|
uiSession,
|
|
3669
4031
|
uiConversation,
|
|
3670
4032
|
settingsScope,
|
|
4033
|
+
configForms,
|
|
4034
|
+
sidebarRight,
|
|
3671
4035
|
t
|
|
3672
4036
|
})));
|
|
3673
4037
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
@@ -3682,13 +4046,30 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3682
4046
|
id: "dsh-focus-overlay",
|
|
3683
4047
|
order: 1e3
|
|
3684
4048
|
}, () => (0, react.createElement)(FocusSettingsCard, { t })));
|
|
4049
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
4050
|
+
name: "plugins.bundle.config",
|
|
4051
|
+
key: PACKAGE_NAME
|
|
4052
|
+
}, (props) => (0, react.createElement)(FocusBundleConfig, {
|
|
4053
|
+
...props,
|
|
4054
|
+
t
|
|
4055
|
+
})));
|
|
4056
|
+
const pluginNavigation = ctx.get("pluginNavigation");
|
|
4057
|
+
const openSettings = pluginNavigation && typeof pluginNavigation.openBundle === "function" ? () => {
|
|
4058
|
+
try {
|
|
4059
|
+
pluginNavigation.openBundle(PACKAGE_NAME);
|
|
4060
|
+
return true;
|
|
4061
|
+
} catch {
|
|
4062
|
+
return false;
|
|
4063
|
+
}
|
|
4064
|
+
} : void 0;
|
|
3685
4065
|
ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
|
|
3686
4066
|
name: "settings.onboarding",
|
|
3687
4067
|
id: "dsh-focus-overlay",
|
|
3688
4068
|
order: 10
|
|
3689
4069
|
}, (props) => (0, react.createElement)(FocusOnboarding, {
|
|
3690
4070
|
...props,
|
|
3691
|
-
t
|
|
4071
|
+
t,
|
|
4072
|
+
openSettings
|
|
3692
4073
|
})));
|
|
3693
4074
|
}
|
|
3694
4075
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-focus-overlay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Focus Mode for the dsh web GUI: a full-screen reading overlay that hides chrome and folds the AI tool-call flow into summaries, reusing the official Markdown / image primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"0.1.2-alpha.5": "compatible",
|
|
32
32
|
"0.1.2-rc.1": "compatible",
|
|
33
33
|
"0.1.3-alpha.1": "compatible",
|
|
34
|
-
"0.1.5-rc.1": "compatible"
|
|
34
|
+
"0.1.5-rc.1": "compatible",
|
|
35
|
+
"0.2.0-rc.2": "compatible",
|
|
36
|
+
"0.2.2-rc.1": "compatible"
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
},
|