dsh-focus-overlay 0.12.2 → 0.12.4
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 +16 -10
- package/README.md +16 -10
- package/lib/client.js +328 -52
- package/package.json +4 -2
package/README.en.md
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
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
|
|
@@ -55,7 +56,7 @@ Click the line to expand that turn's full work detail (per-category one-line sum
|
|
|
55
56
|
| Feature | Description |
|
|
56
57
|
| --- | --- |
|
|
57
58
|
| 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 through `
|
|
59
|
+
| Official rendering primitives | Assistant text through official `MarkdownText` (GFM + code highlighting + TeX + code copy buttons + footnotes); user messages through the official `projectUserText` projection; buttons, modals and icons are all official primitives |
|
|
59
60
|
| 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
61
|
| 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
62
|
| 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,12 +76,13 @@ Click the line to expand that turn's full work detail (per-category one-line sum
|
|
|
75
76
|
| 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
77
|
| Esc peels layer by layer | Answer card → input bar → focus mode, one layer per press; Esc mid-IME-composition cancels the composition first |
|
|
77
78
|
| 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 |
|
|
79
|
+
| 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) |
|
|
80
|
+
| 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 |
|
|
81
|
+
| 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
82
|
| 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
83
|
| 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
84
|
| 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 |
|
|
85
|
+
| 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`), so an `undefined` component can never throw React #130 |
|
|
84
86
|
|
|
85
87
|
## Install
|
|
86
88
|
|
|
@@ -119,7 +121,7 @@ dsh web
|
|
|
119
121
|
|
|
120
122
|
Restart `dsh web` after install.
|
|
121
123
|
|
|
122
|
-
> **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
|
|
124
|
+
> **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
125
|
|
|
124
126
|
## Usage
|
|
125
127
|
|
|
@@ -133,7 +135,11 @@ Restart `dsh web` after install.
|
|
|
133
135
|
|
|
134
136
|
## Settings
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
**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.
|
|
139
|
+
|
|
140
|
+
**On dsh ≤ 0.1.x**: sidebar "Settings → Plugins → Plugin configuration" → expand the "Focus Mode" card.
|
|
141
|
+
|
|
142
|
+
Both locations present the same options:
|
|
137
143
|
|
|
138
144
|
| Option | Description |
|
|
139
145
|
| --- | --- |
|
package/README.md
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
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
|
## 功能
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
| 功能 | 说明 |
|
|
54
55
|
| --- | --- |
|
|
55
56
|
| 全屏专注遮罩 | 经 `shell.overlay` 注册(纯增量、`replaceRisk: none`),覆盖标题区 / 输入区 / 侧栏;顶部只留会话标题与「退出专注」按钮 |
|
|
56
|
-
| 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 +
|
|
57
|
+
| 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 + 脚注),用户消息走官方 `projectUserText` 投影;按钮、模态、图标均为官方原语 |
|
|
57
58
|
| 图片解析 | assistant 的 `image` 块经 `uiConversation.imageUrl`(dsh 0.1.2+ 的会话授权图片缓存)解析,旧版 dsh 自动回退到 legacy 解析器 |
|
|
58
59
|
| 工具调用折叠 | 遵循官方「对话显示」偏好(normal / compact);compact 模式把每轮的工作部分折叠成「N 次工具调用 · M 条消息 · K 个 subagent」披露行(无内容时显示「已思考」),点击可展开完整明细;steering 消息始终独立成行不被折叠;流式中的最后一轮保持展开直到结束 |
|
|
59
60
|
| 分类摘要明细 | 展开后按工具类别计数:命令 / 编辑 / 搜索 / 读取 / 列目录 / 子代理 / 待办 / 目标 / 工作流 / 技能 / 提问 / 计划 / 后台任务 / 上下文注入 |
|
|
@@ -73,12 +74,13 @@
|
|
|
73
74
|
| 专注计数 | 设置卡片最底部一行累计进入专注模式的次数(含自动进入),持久化到 `localStorage`;超过 100 次出现 Star 引导按钮,点击前往仓库页面并记住状态,之后改为「前往 GitHub 项目页面」直达按钮 |
|
|
74
75
|
| Esc 逐层退出 | 答题卡 → 输入条 → 专注模式,一层一层退;输入法组词中的 Esc 优先取消组词 |
|
|
75
76
|
| i18n | 中 / 英文案,注册到 `focus` 命名空间,跟随界面语言 |
|
|
76
|
-
| 插件配置卡片 |
|
|
77
|
-
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding`
|
|
78
|
-
| 官方偏好联动 |
|
|
77
|
+
| 插件配置卡片 | dsh 0.2 起在侧栏「Plugins」页面打开本插件详情页,配置直接渲染在包描述与组件列表之间(`plugins.bundle.config`);dsh ≤ 0.1.x 仍为「设置 → 插件 → 插件配置」中的可折叠卡片(`settings.plugin.item`)。偏好持久化到 `localStorage`;Node 半区以 schemastery schema 注册同名设置 namespace |
|
|
78
|
+
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding` 步骤自带已读标记与版本号;「打开设置」按当前 dsh 版本跳到对应配置页 |
|
|
79
|
+
| 官方偏好联动 | 读取官方「对话显示」偏好(dsh ≤ 0.1.x:normal / compact;dsh 0.2:compact / standard / detailed / verbose),切换即时生效 |
|
|
79
80
|
| 渲染错误兜底 | 遮罩内容包裹错误边界:一次渲染崩溃不会永久打死「专注」按钮,下次打开自动重试 |
|
|
80
81
|
| 兼容 DSH-better-sidebar | 进入专注时在 `<body>` 打标记,样式层自动隐藏其右上角的收放面板按钮与已开启的右侧 / 底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
|
|
81
82
|
| 旧版兼容模式 | dsh < 0.1.2 缺少新服务时降级运行(legacy 快照适配),控制台一次性提示,不会崩溃 |
|
|
83
|
+
| 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`),不会因为 `undefined` 组件触发 React #130 |
|
|
82
84
|
|
|
83
85
|
## 安装
|
|
84
86
|
|
|
@@ -117,7 +119,7 @@ dsh web
|
|
|
117
119
|
|
|
118
120
|
安装后重启 `dsh web` 生效。
|
|
119
121
|
|
|
120
|
-
>
|
|
122
|
+
> **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可以更改——新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。
|
|
121
123
|
|
|
122
124
|
## 使用
|
|
123
125
|
|
|
@@ -131,7 +133,11 @@ dsh web
|
|
|
131
133
|
|
|
132
134
|
## 设置
|
|
133
135
|
|
|
134
|
-
|
|
136
|
+
**dsh 0.2 起**:侧栏「Plugins」页面 → 找到 `dsh-focus-overlay` → 详情页中,配置直接显示在包描述与组件列表之间。
|
|
137
|
+
|
|
138
|
+
**dsh ≤ 0.1.x**:侧栏「设置 → 插件 → 插件配置」→ 展开「专注模式」卡片。
|
|
139
|
+
|
|
140
|
+
两种位置呈现同一组选项:
|
|
135
141
|
|
|
136
142
|
| 选项 | 说明 |
|
|
137
143
|
| --- | --- |
|
package/lib/client.js
CHANGED
|
@@ -3,9 +3,62 @@ 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/icons.ts
|
|
33
|
+
/** Cross-release product-icon resolution.
|
|
34
|
+
*
|
|
35
|
+
* The official primitives package renamed its icon set in dsh 0.2: the
|
|
36
|
+
* fixed-size artwork variants (`IconCheckOutline14`, `IconChevronDownOutline14`,
|
|
37
|
+
* `IconEditOutline16`, `IconSendOutline14`) were removed, leaving one
|
|
38
|
+
* `…OutlineRegular` / `…OutlineMedium` component per glyph that takes an
|
|
39
|
+
* explicit `size` prop. A stale named import therefore resolves to
|
|
40
|
+
* `undefined`, and React throws #130 ("element type is invalid") the moment
|
|
41
|
+
* the component renders — the same failure mode the 0.1.3 `MessageText`
|
|
42
|
+
* removal produced.
|
|
43
|
+
*
|
|
44
|
+
* Import the namespace once and pick the first name the running build exports,
|
|
45
|
+
* so one bundle serves both generations. Each fallback carries the size the
|
|
46
|
+
* removed variant was drawn at, keeping layout identical across releases. */
|
|
47
|
+
function pick(names, size) {
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
const component = _deepseek_ai_dsh_client_ui_primitives[name];
|
|
50
|
+
if (typeof component === "function") return (props) => component({
|
|
51
|
+
size,
|
|
52
|
+
...props
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return () => null;
|
|
56
|
+
}
|
|
57
|
+
const IconCheck = pick(["IconCheckOutline14", "IconCheckOutlineRegular"], 14);
|
|
58
|
+
const IconChevronDown = pick(["IconChevronDownOutline14", "IconChevronDownOutlineRegular"], 14);
|
|
59
|
+
const IconEdit = pick(["IconEditOutline16", "IconEditOutlineRegular"], 16);
|
|
60
|
+
const IconSend = pick(["IconSendOutline14", "IconSendOutlineRegular"], 14);
|
|
61
|
+
//#endregion
|
|
9
62
|
//#region src/client/model.ts
|
|
10
63
|
function legacySliceOf(chatView, snap) {
|
|
11
64
|
const legacy = chatView && chatView.legacy ? chatView.legacy : null;
|
|
@@ -25,6 +78,61 @@ window.__ModuleLoader__.load({
|
|
|
25
78
|
} catch {}
|
|
26
79
|
return null;
|
|
27
80
|
}
|
|
81
|
+
/** dsh 0.2 moved the pending interaction off the uiSession
|
|
82
|
+
* `pendingInteractions` map and onto the unified `sessionStatus` snapshot
|
|
83
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
84
|
+
* Both carriers answer with the same interaction face, so the overlay's answer
|
|
85
|
+
* card resolves through either. Null when nothing is pending. */
|
|
86
|
+
function pendingInteractionFromStatus(status, sessionId) {
|
|
87
|
+
try {
|
|
88
|
+
const entry = status && typeof status.get === "function" ? status.get(sessionId) : null;
|
|
89
|
+
return entry && entry.pendingInteraction || null;
|
|
90
|
+
} catch {}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/** The current session id, resolved across dsh generations.
|
|
94
|
+
*
|
|
95
|
+
* dsh <= 0.1.x carried the viewer's selection on the session-list snapshot
|
|
96
|
+
* (`sessions.list.getSnapshot().current`). dsh 0.2 dropped that field: the
|
|
97
|
+
* selection is the session the main view retains, published on the list rows
|
|
98
|
+
* themselves (`byId[id].retainedBy.mainView > 0`) — the same public read the
|
|
99
|
+
* official cordis inspector uses. `mainBinding` is the uiSession main-binding
|
|
100
|
+
* snapshot, kept as a guarded last resort: it carries the same id, but that
|
|
101
|
+
* member is typed private, so the public list data stays primary. */
|
|
102
|
+
function currentSessionIdOf(listState, mainBinding) {
|
|
103
|
+
const fromList = listState ? listState.current : void 0;
|
|
104
|
+
if (fromList !== void 0 && fromList !== null) return fromList;
|
|
105
|
+
const byId = listState && listState.byId;
|
|
106
|
+
if (byId && typeof byId === "object") {
|
|
107
|
+
for (const row of Object.values(byId)) if (row && (row.retainedBy && row.retainedBy.mainView || 0) > 0) return row.id;
|
|
108
|
+
}
|
|
109
|
+
if (mainBinding) {
|
|
110
|
+
const id = mainBinding.key ?? (mainBinding.props ? mainBinding.props.sessionId : void 0);
|
|
111
|
+
if (id !== void 0 && id !== null) return id;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** The overlay fold mode derived from the official "对话显示" preference.
|
|
115
|
+
*
|
|
116
|
+
* dsh <= 0.1.x served it through the `settingsScope` service bound to the
|
|
117
|
+
* `ui-chat` namespace (`{ transcriptView: 'normal' | 'compact' }`). dsh 0.2
|
|
118
|
+
* retired `settingsScope`; the same preference is a Host settings field read
|
|
119
|
+
* through `configForms.get('ui-chat')`, whose snapshot nests it under `value`
|
|
120
|
+
* (and widened the accepted set to compact | standard | detailed | verbose,
|
|
121
|
+
* with `normal` / `expanded` kept as legacy saved values). Only `compact`
|
|
122
|
+
* folds the tool flow; every other explicit mode reads as the expanded
|
|
123
|
+
* presentation. Absence keeps the overlay's own compact default. */
|
|
124
|
+
function transcriptViewOf(scopeOrForm) {
|
|
125
|
+
try {
|
|
126
|
+
if (!scopeOrForm || typeof scopeOrForm.getSnapshot !== "function") return "compact";
|
|
127
|
+
const snapshot = scopeOrForm.getSnapshot();
|
|
128
|
+
const section = snapshot && snapshot.value && typeof snapshot.value === "object" ? snapshot.value : snapshot;
|
|
129
|
+
const mode = section ? section.transcriptView : null;
|
|
130
|
+
if (mode === void 0 || mode === null) return "compact";
|
|
131
|
+
return mode === "compact" ? "compact" : "normal";
|
|
132
|
+
} catch {
|
|
133
|
+
return "compact";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
28
136
|
/** Legacy pending interaction — pre-0.1.2 dsh carried a flat `pending` array
|
|
29
137
|
* on the session snapshot, with a different carrier shape (`payload`
|
|
30
138
|
* envelope, answers via `wait.respond({ok, value})` returning a `receipt`).
|
|
@@ -70,6 +178,28 @@ window.__ModuleLoader__.load({
|
|
|
70
178
|
});
|
|
71
179
|
return shaped;
|
|
72
180
|
}
|
|
181
|
+
/** Build the `dsh-resource://file/session/<id>/<path>` address that dsh 0.2's
|
|
182
|
+
* Sidebar resource controller opens a file through.
|
|
183
|
+
*
|
|
184
|
+
* The official `fileAddressFor` lives in `@deepseek-ai/dsh-util-workspace-path`,
|
|
185
|
+
* but the client bundle purity gate keeps feature bundles from importing other
|
|
186
|
+
* plugins' values across the boundary — the official Chat package inlines its
|
|
187
|
+
* own copy of the same grammar, and so does this one. Absolute paths inside the
|
|
188
|
+
* session workspace are reduced to workspace-relative; every id and path
|
|
189
|
+
* segment is component-encoded, with `:` kept literal for Windows drives. */
|
|
190
|
+
function sessionFileAddressOf(sessionId, cwd, path) {
|
|
191
|
+
const encode = (segment) => encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
192
|
+
const normalized = String(path).replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
193
|
+
const absolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
|
|
194
|
+
let relative = normalized;
|
|
195
|
+
if (absolute) {
|
|
196
|
+
const root = cwd === void 0 || cwd === null ? "" : String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
197
|
+
if (root !== "" && normalized === root) relative = "";
|
|
198
|
+
else if (root !== "" && normalized.startsWith(root + "/")) relative = normalized.slice(root.length + 1);
|
|
199
|
+
}
|
|
200
|
+
const body = relative.split("/").map(encode).join("/");
|
|
201
|
+
return `dsh-resource://file/session/${encode(String(sessionId))}/${body}`;
|
|
202
|
+
}
|
|
73
203
|
function flattenText(content) {
|
|
74
204
|
if (!content || !content.length) return "";
|
|
75
205
|
let out = "";
|
|
@@ -933,7 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
933
1063
|
"onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
|
|
934
1064
|
"onboarding.feature.hotkey": "快捷键 F —— 任意界面按 F 一键进入专注,可在设置中关闭",
|
|
935
1065
|
"onboarding.configure.title": "快速配置",
|
|
936
|
-
"onboarding.laterNote": "
|
|
1066
|
+
"onboarding.laterNote": "这些设置随时可以更改:新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。",
|
|
937
1067
|
"onboarding.openSettings": "打开设置",
|
|
938
1068
|
"onboarding.done": "完成"
|
|
939
1069
|
};
|
|
@@ -1019,7 +1149,7 @@ window.__ModuleLoader__.load({
|
|
|
1019
1149
|
"onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
|
|
1020
1150
|
"onboarding.feature.hotkey": "Hotkey F — press F anywhere to enter focus mode (toggleable in settings)",
|
|
1021
1151
|
"onboarding.configure.title": "Quick setup",
|
|
1022
|
-
"onboarding.laterNote": "
|
|
1152
|
+
"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
1153
|
"onboarding.openSettings": "Open settings",
|
|
1024
1154
|
"onboarding.done": "Done"
|
|
1025
1155
|
};
|
|
@@ -1109,7 +1239,7 @@ window.__ModuleLoader__.load({
|
|
|
1109
1239
|
"aria-pressed": on,
|
|
1110
1240
|
children: [multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1111
1241
|
className: "fm-opt-check" + (on ? " fm-opt-check-on" : ""),
|
|
1112
|
-
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1242
|
+
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, {}) : null
|
|
1113
1243
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1114
1244
|
className: "fm-opt-num",
|
|
1115
1245
|
children: index + 1
|
|
@@ -1220,7 +1350,7 @@ window.__ModuleLoader__.load({
|
|
|
1220
1350
|
title: t("answer.close"),
|
|
1221
1351
|
"aria-label": t("answer.close"),
|
|
1222
1352
|
onClick: onClose,
|
|
1223
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1353
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1224
1354
|
})]
|
|
1225
1355
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1226
1356
|
className: "fm-card-head",
|
|
@@ -1236,7 +1366,7 @@ window.__ModuleLoader__.load({
|
|
|
1236
1366
|
title: t("answer.close"),
|
|
1237
1367
|
"aria-label": t("answer.close"),
|
|
1238
1368
|
onClick: onClose,
|
|
1239
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1369
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1240
1370
|
})
|
|
1241
1371
|
]
|
|
1242
1372
|
}),
|
|
@@ -1434,7 +1564,7 @@ window.__ModuleLoader__.load({
|
|
|
1434
1564
|
disabled: empty,
|
|
1435
1565
|
onMouseDown: (e) => e.preventDefault(),
|
|
1436
1566
|
onClick: onSend,
|
|
1437
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1567
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSend, {})
|
|
1438
1568
|
})
|
|
1439
1569
|
]
|
|
1440
1570
|
})
|
|
@@ -1471,7 +1601,7 @@ window.__ModuleLoader__.load({
|
|
|
1471
1601
|
title: t("composer.expand"),
|
|
1472
1602
|
"aria-label": t("composer.expand"),
|
|
1473
1603
|
onClick: onExpand,
|
|
1474
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1604
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEdit, {}), hasDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "fm-pill-dot" }) : null]
|
|
1475
1605
|
});
|
|
1476
1606
|
}
|
|
1477
1607
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1480,7 +1610,7 @@ window.__ModuleLoader__.load({
|
|
|
1480
1610
|
title: t("backToLatest"),
|
|
1481
1611
|
"aria-label": t("backToLatest"),
|
|
1482
1612
|
onClick: onJumpBottom,
|
|
1483
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1613
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 16 })
|
|
1484
1614
|
});
|
|
1485
1615
|
}
|
|
1486
1616
|
//#endregion
|
|
@@ -1813,6 +1943,9 @@ window.__ModuleLoader__.load({
|
|
|
1813
1943
|
.fm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
|
|
1814
1944
|
.fm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
1815
1945
|
.fm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:16px}
|
|
1946
|
+
/* dsh 0.2 Plugins-page bundle configuration: the page draws its own title,
|
|
1947
|
+
icon and crumb, so this surface is body-only — no card border or collapse. */
|
|
1948
|
+
.fm-plugin-page{flex-direction:column;display:flex}
|
|
1816
1949
|
.fm-plugin-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
1817
1950
|
.fm-plugin-field+.fm-plugin-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
1818
1951
|
/* The stats row ends the card body, so it carries NO bottom padding of its
|
|
@@ -2264,6 +2397,19 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2264
2397
|
(0, react.useEffect)(() => focusStore.subscribe(() => setV(focusStore.getDonePing())), []);
|
|
2265
2398
|
return v;
|
|
2266
2399
|
}
|
|
2400
|
+
/** Body of a sent user message / steering row.
|
|
2401
|
+
*
|
|
2402
|
+
* dsh 0.1.3-alpha.2 removed the `MessageText` component from the primitives
|
|
2403
|
+
* module in favour of the `projectUserText` display projection (which has
|
|
2404
|
+
* existed since 0.1.2 and is still exported by 0.1.5). Importing the removed
|
|
2405
|
+
* name made it `undefined`, so the overlay threw React #130 the moment it
|
|
2406
|
+
* rendered a session containing a user message — the "toggle does nothing"
|
|
2407
|
+
* symptom. `projectUserText` returns inline plain runs plus `@session` /
|
|
2408
|
+
* `/skill` chips; `.fm-user` still supplies the bubble chrome and
|
|
2409
|
+
* `white-space: pre-wrap`, so the text keeps its shape. */
|
|
2410
|
+
function UserText({ text }) {
|
|
2411
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: (0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(text, []) });
|
|
2412
|
+
}
|
|
2267
2413
|
let bodyEl = null;
|
|
2268
2414
|
const anchors = {};
|
|
2269
2415
|
function useSessionSnapshot(sessions, sessionId) {
|
|
@@ -2288,13 +2434,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2288
2434
|
return snap;
|
|
2289
2435
|
}
|
|
2290
2436
|
/** The session's pending user interaction (question / plan review / approval),
|
|
2291
|
-
* read from the uiSession
|
|
2292
|
-
*
|
|
2293
|
-
*
|
|
2294
|
-
*
|
|
2295
|
-
*
|
|
2437
|
+
* read from the uiSession services. dsh 0.1.2 exposed the root-level
|
|
2438
|
+
* `pendingInteractions` map; dsh 0.2 unified it into `sessionStatus`
|
|
2439
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
2440
|
+
* When neither service is present (older dsh), falls back to the session
|
|
2441
|
+
* snapshot's flat `pending` array through the `legacyPendingOf` adapter.
|
|
2442
|
+
* Returns null when this session has nothing pending. */
|
|
2296
2443
|
function usePendingInteraction(uiSession, sessionId, snap) {
|
|
2297
|
-
const
|
|
2444
|
+
const legacyMap = uiSession && uiSession.pendingInteractions;
|
|
2445
|
+
const statusMap = uiSession && uiSession.sessionStatus;
|
|
2446
|
+
const source = legacyMap || statusMap;
|
|
2298
2447
|
const read = () => {
|
|
2299
2448
|
try {
|
|
2300
2449
|
return source ? source.getSnapshot() : null;
|
|
@@ -2315,7 +2464,47 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2315
2464
|
return;
|
|
2316
2465
|
}
|
|
2317
2466
|
}, [source]);
|
|
2318
|
-
return pendingInteractionOf(map, sessionId) || legacyPendingOf(snap);
|
|
2467
|
+
return (legacyMap ? pendingInteractionOf(map, sessionId) : pendingInteractionFromStatus(map, sessionId)) || legacyPendingOf(snap);
|
|
2468
|
+
}
|
|
2469
|
+
/** The viewed session id, resolved across dsh generations. dsh <= 0.1.x carried
|
|
2470
|
+
* the selection on the session-list snapshot (`listState.current`); dsh 0.2
|
|
2471
|
+
* dropped it and publishes the main view's retained session as the uiSession
|
|
2472
|
+
* main binding. Subscribes to whichever source exists (both, when both do), so
|
|
2473
|
+
* switching sessions re-resolves either way. */
|
|
2474
|
+
function useCurrentSessionId(sessions, uiSession, listState) {
|
|
2475
|
+
const read = () => {
|
|
2476
|
+
if (!sessions || typeof sessions.list?.getSnapshot !== "function") return void 0;
|
|
2477
|
+
let mainBinding = null;
|
|
2478
|
+
try {
|
|
2479
|
+
mainBinding = uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
2480
|
+
} catch {
|
|
2481
|
+
mainBinding = null;
|
|
2482
|
+
}
|
|
2483
|
+
return currentSessionIdOf(sessions.list.getSnapshot(), mainBinding);
|
|
2484
|
+
};
|
|
2485
|
+
const [id, setId] = (0, react.useState)(read);
|
|
2486
|
+
(0, react.useEffect)(() => {
|
|
2487
|
+
if (!sessions || typeof sessions.list?.subscribe !== "function") {
|
|
2488
|
+
setId(void 0);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
const sync = () => setId(read());
|
|
2492
|
+
sync();
|
|
2493
|
+
const unsubs = [];
|
|
2494
|
+
try {
|
|
2495
|
+
unsubs.push(sessions.list.subscribe(sync));
|
|
2496
|
+
} catch {}
|
|
2497
|
+
const main = uiSession && uiSession.current;
|
|
2498
|
+
if (main && typeof main.subscribe === "function") try {
|
|
2499
|
+
unsubs.push(main.subscribe(sync));
|
|
2500
|
+
} catch {}
|
|
2501
|
+
return () => {
|
|
2502
|
+
for (const u of unsubs) try {
|
|
2503
|
+
u();
|
|
2504
|
+
} catch {}
|
|
2505
|
+
};
|
|
2506
|
+
}, [sessions, uiSession]);
|
|
2507
|
+
return id;
|
|
2319
2508
|
}
|
|
2320
2509
|
/** The session's Chat view snapshot (dsh 0.1.2 `ChatSnapshot`) from the
|
|
2321
2510
|
* uiConversation assembly — the first subscriber activates the chat target,
|
|
@@ -2342,20 +2531,25 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2342
2531
|
}, [uiConversation, sessionId]);
|
|
2343
2532
|
return chat;
|
|
2344
2533
|
}
|
|
2345
|
-
/** The official "对话显示" preference (`ui-chat.transcriptView
|
|
2346
|
-
*
|
|
2347
|
-
* TranscriptViewPolicy binds
|
|
2348
|
-
*
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2534
|
+
/** The official "对话显示" preference (`ui-chat.transcriptView`), read
|
|
2535
|
+
* reactively. dsh <= 0.1.x serves it through the settings scope the official
|
|
2536
|
+
* TranscriptViewPolicy binds (values normal | compact); dsh 0.2 retired that
|
|
2537
|
+
* service and serves the same field through the `configForms` controller for
|
|
2538
|
+
* the `ui-chat` namespace (compact | standard | detailed | verbose, with the
|
|
2539
|
+
* legacy normal | expanded values still accepted). Both are read through the
|
|
2540
|
+
* shared `transcriptViewOf` projection; only `compact` folds the tool flow.
|
|
2541
|
+
* Defaults to compact when no preference has arrived yet. */
|
|
2542
|
+
function useTranscriptViewMode(settingsScope, configForms) {
|
|
2543
|
+
const form = (0, react.useMemo)(() => {
|
|
2544
|
+
if (settingsScope) return settingsScope;
|
|
2352
2545
|
try {
|
|
2353
|
-
|
|
2354
|
-
return (section ? section.transcriptView : null) === "normal" ? "normal" : "compact";
|
|
2546
|
+
return configForms && typeof configForms.get === "function" ? configForms.get("ui-chat") : null;
|
|
2355
2547
|
} catch {
|
|
2356
|
-
return
|
|
2548
|
+
return null;
|
|
2357
2549
|
}
|
|
2358
|
-
};
|
|
2550
|
+
}, [settingsScope, configForms]);
|
|
2551
|
+
const source = form && typeof form.getSnapshot === "function" ? form : null;
|
|
2552
|
+
const read = () => transcriptViewOf(source);
|
|
2359
2553
|
const [mode, setMode] = (0, react.useState)(read);
|
|
2360
2554
|
(0, react.useEffect)(() => {
|
|
2361
2555
|
if (!source) {
|
|
@@ -2420,13 +2614,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2420
2614
|
return seat ? seat.querySelector("[contenteditable=\"true\"]") : null;
|
|
2421
2615
|
}
|
|
2422
2616
|
function FocusContent(props) {
|
|
2423
|
-
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, t } = props;
|
|
2617
|
+
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, configForms, sidebarRight, t } = props;
|
|
2424
2618
|
const prefs = usePrefs();
|
|
2425
2619
|
const listState = useSessions((s) => s);
|
|
2426
|
-
const currentId =
|
|
2620
|
+
const currentId = useCurrentSessionId(sessions, uiSession, listState);
|
|
2427
2621
|
const snap = useSessionSnapshot(sessions, currentId);
|
|
2428
2622
|
const chatView = useChatView(uiConversation, currentId);
|
|
2429
|
-
const transcriptView = useTranscriptViewMode(settingsScope);
|
|
2623
|
+
const transcriptView = useTranscriptViewMode(settingsScope, configForms);
|
|
2430
2624
|
const legacy = (0, react.useMemo)(() => legacySliceOf(chatView, snap), [chatView, snap]);
|
|
2431
2625
|
const stable = legacy.partial == null;
|
|
2432
2626
|
const mdLabels = (0, react.useMemo)(() => markdownLabels(t), [t]);
|
|
@@ -2752,6 +2946,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2752
2946
|
if (conversation && typeof conversation.resolveImage === "function") return conversation.resolveImage(currentId, attachment);
|
|
2753
2947
|
return Promise.reject(/* @__PURE__ */ new Error("dsh-focus-overlay: image resolver unavailable"));
|
|
2754
2948
|
}
|
|
2949
|
+
const openMentionedFile = (0, react.useCallback)((path, options) => {
|
|
2950
|
+
try {
|
|
2951
|
+
if (workspaces && typeof workspaces.openPath === "function") {
|
|
2952
|
+
workspaces.openPath(path).catch(() => {});
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
const cwd = currentId != null ? sessions?.list?.getSnapshot?.()?.byId?.[currentId]?.cwd : void 0;
|
|
2956
|
+
if (sidebarRight && currentId != null && typeof sidebarRight.openResource === "function") {
|
|
2957
|
+
const address = sessionFileAddressOf(String(currentId), cwd, path);
|
|
2958
|
+
sidebarRight.openResource(address, options && options.line !== void 0 ? { params: { line: options.line } } : void 0);
|
|
2959
|
+
}
|
|
2960
|
+
} catch {}
|
|
2961
|
+
}, [
|
|
2962
|
+
sidebarRight,
|
|
2963
|
+
workspaces,
|
|
2964
|
+
sessions,
|
|
2965
|
+
currentId
|
|
2966
|
+
]);
|
|
2755
2967
|
const fileMentionsFor = (0, react.useCallback)((node) => {
|
|
2756
2968
|
if (!chatFileMentions) return void 0;
|
|
2757
2969
|
try {
|
|
@@ -2761,14 +2973,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2761
2973
|
step: node.step
|
|
2762
2974
|
},
|
|
2763
2975
|
seq: node.seq,
|
|
2764
|
-
openFile: (path) =>
|
|
2765
|
-
|
|
2766
|
-
}
|
|
2767
|
-
});
|
|
2976
|
+
openFile: (path, options) => openMentionedFile(path, options)
|
|
2977
|
+
}, currentId);
|
|
2768
2978
|
} catch {
|
|
2769
2979
|
return;
|
|
2770
2980
|
}
|
|
2771
|
-
}, [
|
|
2981
|
+
}, [
|
|
2982
|
+
chatFileMentions,
|
|
2983
|
+
currentId,
|
|
2984
|
+
openMentionedFile
|
|
2985
|
+
]);
|
|
2772
2986
|
const binding = (0, react.useMemo)(() => sessions && currentId != null ? sessions.binding(currentId) : void 0, [sessions, currentId]);
|
|
2773
2987
|
const inputFace = useInputFace(conversation, binding && binding.ctx);
|
|
2774
2988
|
const inputState = useInputState(inputFace);
|
|
@@ -2975,7 +3189,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2975
3189
|
},
|
|
2976
3190
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2977
3191
|
className: "fm-user",
|
|
2978
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3192
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserText, { text: it.text })
|
|
2979
3193
|
})
|
|
2980
3194
|
}, key);
|
|
2981
3195
|
if (it.kind === "steering") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -2986,7 +3200,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2986
3200
|
},
|
|
2987
3201
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2988
3202
|
className: "fm-user fm-steering",
|
|
2989
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3203
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserText, { text: it.text })
|
|
2990
3204
|
})
|
|
2991
3205
|
}, key);
|
|
2992
3206
|
if (it.kind === "assistant") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssistantItem, {
|
|
@@ -3024,7 +3238,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3024
3238
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3025
3239
|
className: "fm-process-label",
|
|
3026
3240
|
children: processLabel(it.counts, t)
|
|
3027
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3241
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-process-chevron" })]
|
|
3028
3242
|
}) : null, open ? it.workItems.map((wi, wi2) => renderItem(wi, key + "-w" + wi2, false)) : null] }, key);
|
|
3029
3243
|
}
|
|
3030
3244
|
return null;
|
|
@@ -3358,6 +3572,15 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3358
3572
|
})
|
|
3359
3573
|
});
|
|
3360
3574
|
}
|
|
3575
|
+
/** The settings body both plugin-settings surfaces share: the preference
|
|
3576
|
+
* fields plus the focus-entry counter. Rendered by the legacy Settings card
|
|
3577
|
+
* and by the dsh 0.2 Plugins-page bundle page. */
|
|
3578
|
+
function FocusSettingsBody({ t }) {
|
|
3579
|
+
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 })] });
|
|
3580
|
+
}
|
|
3581
|
+
/** Legacy surface (dsh <= 0.1.x): a collapsible `<li>` card in the Settings
|
|
3582
|
+
* → Plugins tab, registered into the keyed `settings.plugin.item` slot. dsh
|
|
3583
|
+
* 0.2 removed that slot, so this only renders where the slot is declared. */
|
|
3361
3584
|
function FocusSettingsCard({ t }) {
|
|
3362
3585
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3363
3586
|
const title = t("settings.label");
|
|
@@ -3378,13 +3601,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3378
3601
|
className: "fm-plugin-card-desc",
|
|
3379
3602
|
children: t("settings.description")
|
|
3380
3603
|
})]
|
|
3381
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3382
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3604
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
|
|
3605
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3383
3606
|
className: "fm-plugin-card-body",
|
|
3384
|
-
children:
|
|
3607
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3385
3608
|
}) : null]
|
|
3386
3609
|
});
|
|
3387
3610
|
}
|
|
3611
|
+
/** dsh 0.2 surface: the bundle's own configuration on the sidebar Plugins
|
|
3612
|
+
* page (`plugins.bundle.config`, keyed by the bundle's package name). The page
|
|
3613
|
+
* draws the title, icon, and crumb itself and always asks for `view: 'page'`,
|
|
3614
|
+
* so this renders the body alone — no card chrome, no collapse. */
|
|
3615
|
+
function FocusBundleConfig({ t, view }) {
|
|
3616
|
+
if (view !== void 0 && view !== "page") return null;
|
|
3617
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3618
|
+
className: "fm-plugin-page",
|
|
3619
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3388
3622
|
function FocusOnboarding(props) {
|
|
3389
3623
|
const { complete, openSection, t } = props;
|
|
3390
3624
|
const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
|
|
@@ -3416,7 +3650,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3416
3650
|
onboardingStore.markDone();
|
|
3417
3651
|
setDone(true);
|
|
3418
3652
|
finish();
|
|
3419
|
-
|
|
3653
|
+
let opened = false;
|
|
3654
|
+
if (typeof props.openSettings === "function") try {
|
|
3655
|
+
opened = props.openSettings() !== false;
|
|
3656
|
+
} catch {
|
|
3657
|
+
opened = false;
|
|
3658
|
+
}
|
|
3659
|
+
if (!opened && typeof openSection === "function") openSection("plugins");
|
|
3420
3660
|
};
|
|
3421
3661
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3422
3662
|
open: true,
|
|
@@ -3481,13 +3721,20 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3481
3721
|
/**
|
|
3482
3722
|
* Focus Mode, browser half: a full-screen reading overlay in `shell.overlay`
|
|
3483
3723
|
* (additive, never replaces shipped UI), a "专注" button in the session header
|
|
3484
|
-
* action row, and
|
|
3485
|
-
*
|
|
3486
|
-
*
|
|
3487
|
-
*
|
|
3488
|
-
*
|
|
3724
|
+
* action row, and the plugin's settings card. The settings surface lives where
|
|
3725
|
+
* the running dsh keeps it: the Settings → Plugins tab's keyed
|
|
3726
|
+
* `settings.plugin.item` slot on dsh <= 0.1.x, and the sidebar Plugins page's
|
|
3727
|
+
* `plugins.bundle.config` slot (keyed by this bundle's package name) on dsh
|
|
3728
|
+
* 0.2, which retired both that slot and the `settingsScope` service. Rendering
|
|
3729
|
+
* reuses the official client UI primitives module
|
|
3730
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` runtime id) — Markdown /
|
|
3731
|
+
* projectUserText / Button — plus the conversation service's composer registry
|
|
3732
|
+
* and the uiSession pending-interaction / main-binding service.
|
|
3489
3733
|
*/
|
|
3490
3734
|
const NS = "focus";
|
|
3735
|
+
/** This bundle's package name — the key `plugins.bundle.config` and the
|
|
3736
|
+
* `pluginNavigation.openBundle` deep link address this plugin by (dsh 0.2). */
|
|
3737
|
+
const PACKAGE_NAME = "dsh-focus-overlay";
|
|
3491
3738
|
let warnedLegacy = false;
|
|
3492
3739
|
function warnLegacyOnce() {
|
|
3493
3740
|
if (warnedLegacy) return;
|
|
@@ -3503,8 +3750,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3503
3750
|
"locale",
|
|
3504
3751
|
"workspaces",
|
|
3505
3752
|
"uiSession",
|
|
3506
|
-
"uiConversation"
|
|
3507
|
-
"settingsScope"
|
|
3753
|
+
"uiConversation"
|
|
3508
3754
|
],
|
|
3509
3755
|
apply(ctx) {
|
|
3510
3756
|
const style = document.createElement("style");
|
|
@@ -3537,6 +3783,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3537
3783
|
const workspaces = ctx.workspaces;
|
|
3538
3784
|
const conversation = ctx.get("conversation");
|
|
3539
3785
|
const chatFileMentions = ctx.get("chatFileMentions");
|
|
3786
|
+
const sidebarRight = ctx.get("sidebarRight");
|
|
3540
3787
|
const uiSession = ctx.get("uiSession");
|
|
3541
3788
|
const uiConversation = ctx.get("uiConversation");
|
|
3542
3789
|
if (!uiSession || !uiConversation) warnLegacyOnce();
|
|
@@ -3547,6 +3794,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3547
3794
|
} catch {
|
|
3548
3795
|
settingsScope = null;
|
|
3549
3796
|
}
|
|
3797
|
+
const configForms = ctx.get("configForms");
|
|
3550
3798
|
ctx.effect(() => {
|
|
3551
3799
|
let currentId = void 0;
|
|
3552
3800
|
let unsubSession = null;
|
|
@@ -3609,18 +3857,27 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3609
3857
|
} catch {}
|
|
3610
3858
|
if (!unsubChat) chatSlice = legacySliceOf(null, face.getSnapshot());
|
|
3611
3859
|
};
|
|
3860
|
+
const mainBinding = () => {
|
|
3861
|
+
try {
|
|
3862
|
+
return uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
3863
|
+
} catch {
|
|
3864
|
+
return null;
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3612
3867
|
const onList = () => {
|
|
3613
|
-
const
|
|
3614
|
-
const next = ls ? ls.current : void 0;
|
|
3868
|
+
const next = currentSessionIdOf(sessions.list.getSnapshot(), mainBinding());
|
|
3615
3869
|
if (next !== currentId) {
|
|
3616
3870
|
currentId = next;
|
|
3617
3871
|
watch(next);
|
|
3618
|
-
}
|
|
3872
|
+
} else if (next != null && !unsubSession) watch(next);
|
|
3619
3873
|
};
|
|
3620
3874
|
onList();
|
|
3621
3875
|
const unsubList = sessions.list.subscribe(onList);
|
|
3876
|
+
const mainSource = uiSession && uiSession.current;
|
|
3877
|
+
const unsubMain = mainSource && typeof mainSource.subscribe === "function" ? mainSource.subscribe(onList) : null;
|
|
3622
3878
|
return () => {
|
|
3623
3879
|
if (unsubList) unsubList();
|
|
3880
|
+
if (unsubMain) unsubMain();
|
|
3624
3881
|
if (unsubSession) unsubSession();
|
|
3625
3882
|
if (unsubChat) unsubChat();
|
|
3626
3883
|
};
|
|
@@ -3655,6 +3912,8 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3655
3912
|
uiSession,
|
|
3656
3913
|
uiConversation,
|
|
3657
3914
|
settingsScope,
|
|
3915
|
+
configForms,
|
|
3916
|
+
sidebarRight,
|
|
3658
3917
|
t
|
|
3659
3918
|
})));
|
|
3660
3919
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
@@ -3669,13 +3928,30 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3669
3928
|
id: "dsh-focus-overlay",
|
|
3670
3929
|
order: 1e3
|
|
3671
3930
|
}, () => (0, react.createElement)(FocusSettingsCard, { t })));
|
|
3931
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
3932
|
+
name: "plugins.bundle.config",
|
|
3933
|
+
key: PACKAGE_NAME
|
|
3934
|
+
}, (props) => (0, react.createElement)(FocusBundleConfig, {
|
|
3935
|
+
...props,
|
|
3936
|
+
t
|
|
3937
|
+
})));
|
|
3938
|
+
const pluginNavigation = ctx.get("pluginNavigation");
|
|
3939
|
+
const openSettings = pluginNavigation && typeof pluginNavigation.openBundle === "function" ? () => {
|
|
3940
|
+
try {
|
|
3941
|
+
pluginNavigation.openBundle(PACKAGE_NAME);
|
|
3942
|
+
return true;
|
|
3943
|
+
} catch {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
} : void 0;
|
|
3672
3947
|
ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
|
|
3673
3948
|
name: "settings.onboarding",
|
|
3674
3949
|
id: "dsh-focus-overlay",
|
|
3675
3950
|
order: 10
|
|
3676
3951
|
}, (props) => (0, react.createElement)(FocusOnboarding, {
|
|
3677
3952
|
...props,
|
|
3678
|
-
t
|
|
3953
|
+
t,
|
|
3954
|
+
openSettings
|
|
3679
3955
|
})));
|
|
3680
3956
|
}
|
|
3681
3957
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-focus-overlay",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
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",
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"dshReleases": {
|
|
31
31
|
"0.1.2-alpha.5": "compatible",
|
|
32
32
|
"0.1.2-rc.1": "compatible",
|
|
33
|
-
"0.1.3-alpha.1": "compatible"
|
|
33
|
+
"0.1.3-alpha.1": "compatible",
|
|
34
|
+
"0.1.5-rc.1": "compatible",
|
|
35
|
+
"0.2.0-rc.2": "compatible"
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
},
|