dsh-focus-overlay 0.6.0 → 0.7.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 +5 -3
- package/README.md +6 -4
- package/lib/client.js +294 -139
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -99,6 +99,8 @@ dsh web
|
|
|
99
99
|
|
|
100
100
|
Restart `dsh web` after install.
|
|
101
101
|
|
|
102
|
+
> **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 in "Settings → Plugins → Plugin configuration".
|
|
103
|
+
|
|
102
104
|
## Usage
|
|
103
105
|
|
|
104
106
|
1. Open a session and click the **"Focus"** button in the session header action row.
|
|
@@ -114,10 +116,10 @@ In the sidebar "Settings → Plugins → Plugin configuration", expand the "Focu
|
|
|
114
116
|
|
|
115
117
|
| Option | Description |
|
|
116
118
|
| --- | --- |
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
+
| Auto-enter focus mode when the AI finishes a reply | On a normal completion: auto-open at your question when closed, or a "New reply ready" toast when open (default off) |
|
|
120
|
+
| Sync the current reading position when entering focus mode | Open at the message you're reading; off opens at the latest (default on) |
|
|
121
|
+
| Show right-side turn navbar | One navigation dot per user message (turn) — hover to preview, click to jump (default on) |
|
|
119
122
|
| Text width | 480–1200px slider for the reading column (default 760px) |
|
|
120
|
-
| Auto-enter focus mode when a reply completes | On a normal completion: auto-open at your question when closed, or a "New reply ready" toast when open; an "AI is waiting" toast on a question/approval (default off) |
|
|
121
123
|
|
|
122
124
|
## License
|
|
123
125
|
|
package/README.md
CHANGED
|
@@ -97,6 +97,8 @@ dsh web
|
|
|
97
97
|
|
|
98
98
|
安装后重启 `dsh web` 生效。
|
|
99
99
|
|
|
100
|
+
> **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可在「设置 → 插件 → 插件配置」中更改。
|
|
101
|
+
|
|
100
102
|
## 使用
|
|
101
103
|
|
|
102
104
|
1. 打开任意会话,点击标题栏操作区的 **「专注」** 按钮。
|
|
@@ -112,10 +114,10 @@ dsh web
|
|
|
112
114
|
|
|
113
115
|
| 选项 | 说明 |
|
|
114
116
|
| --- | --- |
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
117
|
+
| AI完成回复后,自动进入专注模式 | AI 正常完成回复后自动进入并定位到你的提问;已打开时弹「新回复已生成」(默认关) |
|
|
118
|
+
| 进入专注模式时,同步当前阅读位置 | 进入时定位到你正在阅读的消息;关闭则定位到最新(默认开) |
|
|
119
|
+
| 显示右侧轮次导航条 | 在右侧显示导航圆点,每个用户消息(一轮)对应一个,悬停预览、点击跳转(默认开) |
|
|
120
|
+
| 文字区宽度 | 480–1200px 滑块,调整正文阅读列宽(默认 760px) |
|
|
119
121
|
|
|
120
122
|
## 许可
|
|
121
123
|
|
package/lib/client.js
CHANGED
|
@@ -6,53 +6,6 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
let react = require("react");
|
|
7
7
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
//#region src/client/settings.ts
|
|
10
|
-
const KEY = "dsh-focus-overlay:prefs";
|
|
11
|
-
const DEFAULTS = {
|
|
12
|
-
navbar: true,
|
|
13
|
-
scroll: "preserve",
|
|
14
|
-
width: 760,
|
|
15
|
-
autoFocus: false
|
|
16
|
-
};
|
|
17
|
-
function load() {
|
|
18
|
-
try {
|
|
19
|
-
const raw = localStorage.getItem(KEY);
|
|
20
|
-
if (raw) return {
|
|
21
|
-
...DEFAULTS,
|
|
22
|
-
...JSON.parse(raw)
|
|
23
|
-
};
|
|
24
|
-
} catch {}
|
|
25
|
-
return { ...DEFAULTS };
|
|
26
|
-
}
|
|
27
|
-
let prefs = load();
|
|
28
|
-
const listeners = [];
|
|
29
|
-
const prefsStore = {
|
|
30
|
-
get: () => prefs,
|
|
31
|
-
set: (next) => {
|
|
32
|
-
prefs = next;
|
|
33
|
-
try {
|
|
34
|
-
localStorage.setItem(KEY, JSON.stringify(next));
|
|
35
|
-
} catch {}
|
|
36
|
-
for (const l of listeners) l();
|
|
37
|
-
},
|
|
38
|
-
update: (patch) => prefsStore.set({
|
|
39
|
-
...prefs,
|
|
40
|
-
...patch
|
|
41
|
-
}),
|
|
42
|
-
subscribe: (l) => {
|
|
43
|
-
listeners.push(l);
|
|
44
|
-
return () => {
|
|
45
|
-
const i = listeners.indexOf(l);
|
|
46
|
-
if (i >= 0) listeners.splice(i, 1);
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
function usePrefs() {
|
|
51
|
-
const [p, setP] = (0, react.useState)(prefsStore.get);
|
|
52
|
-
(0, react.useEffect)(() => prefsStore.subscribe(() => setP(prefsStore.get)), []);
|
|
53
|
-
return p;
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
9
|
//#region src/client/model.ts
|
|
57
10
|
function flattenText(content) {
|
|
58
11
|
if (!content || !content.length) return "";
|
|
@@ -313,6 +266,74 @@ window.__ModuleLoader__.load({
|
|
|
313
266
|
const pending = snap && snap.pending;
|
|
314
267
|
return Array.isArray(pending) && pending.length > 0;
|
|
315
268
|
}
|
|
269
|
+
/** True once the persisted onboarding flag matches the current intro version.
|
|
270
|
+
* Kept as a pure comparison so the "already seen?" decision is testable and
|
|
271
|
+
* can be versioned: bump the version to re-show the intro after a major change. */
|
|
272
|
+
function onboardingSeen(stored, version) {
|
|
273
|
+
return stored === version;
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/client/settings.ts
|
|
277
|
+
const KEY = "dsh-focus-overlay:prefs";
|
|
278
|
+
const DEFAULTS = {
|
|
279
|
+
navbar: true,
|
|
280
|
+
scroll: "preserve",
|
|
281
|
+
width: 760,
|
|
282
|
+
autoFocus: false
|
|
283
|
+
};
|
|
284
|
+
function load() {
|
|
285
|
+
try {
|
|
286
|
+
const raw = localStorage.getItem(KEY);
|
|
287
|
+
if (raw) return {
|
|
288
|
+
...DEFAULTS,
|
|
289
|
+
...JSON.parse(raw)
|
|
290
|
+
};
|
|
291
|
+
} catch {}
|
|
292
|
+
return { ...DEFAULTS };
|
|
293
|
+
}
|
|
294
|
+
let prefs = load();
|
|
295
|
+
const listeners = [];
|
|
296
|
+
const prefsStore = {
|
|
297
|
+
get: () => prefs,
|
|
298
|
+
set: (next) => {
|
|
299
|
+
prefs = next;
|
|
300
|
+
try {
|
|
301
|
+
localStorage.setItem(KEY, JSON.stringify(next));
|
|
302
|
+
} catch {}
|
|
303
|
+
for (const l of listeners) l();
|
|
304
|
+
},
|
|
305
|
+
update: (patch) => prefsStore.set({
|
|
306
|
+
...prefs,
|
|
307
|
+
...patch
|
|
308
|
+
}),
|
|
309
|
+
subscribe: (l) => {
|
|
310
|
+
listeners.push(l);
|
|
311
|
+
return () => {
|
|
312
|
+
const i = listeners.indexOf(l);
|
|
313
|
+
if (i >= 0) listeners.splice(i, 1);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function usePrefs() {
|
|
318
|
+
const [p, setP] = (0, react.useState)(prefsStore.get);
|
|
319
|
+
(0, react.useEffect)(() => prefsStore.subscribe(() => setP(prefsStore.get)), []);
|
|
320
|
+
return p;
|
|
321
|
+
}
|
|
322
|
+
const ONBOARD_KEY = "dsh-focus-overlay:onboarded";
|
|
323
|
+
const onboardingStore = {
|
|
324
|
+
isDone: () => {
|
|
325
|
+
try {
|
|
326
|
+
return onboardingSeen(localStorage.getItem(ONBOARD_KEY), "1");
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
markDone: () => {
|
|
332
|
+
try {
|
|
333
|
+
localStorage.setItem(ONBOARD_KEY, "1");
|
|
334
|
+
} catch {}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
316
337
|
//#endregion
|
|
317
338
|
//#region src/client/FocusView.tsx
|
|
318
339
|
let focusOn = false;
|
|
@@ -752,8 +773,91 @@ window.__ModuleLoader__.load({
|
|
|
752
773
|
children: t("toggle")
|
|
753
774
|
});
|
|
754
775
|
}
|
|
755
|
-
function
|
|
776
|
+
function FocusPrefsFields({ t }) {
|
|
756
777
|
const prefs = usePrefs();
|
|
778
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
779
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
780
|
+
className: "fm-plugin-field",
|
|
781
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
782
|
+
className: "fm-plugin-check",
|
|
783
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
784
|
+
type: "checkbox",
|
|
785
|
+
checked: prefs.autoFocus,
|
|
786
|
+
onChange: (e) => prefsStore.update({ autoFocus: e.target.checked })
|
|
787
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
788
|
+
className: "fm-plugin-check-label",
|
|
789
|
+
children: t("settings.autoFocus")
|
|
790
|
+
})]
|
|
791
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
792
|
+
className: "fm-plugin-field-hint",
|
|
793
|
+
children: t("settings.autoFocus.hint")
|
|
794
|
+
})]
|
|
795
|
+
}),
|
|
796
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
797
|
+
className: "fm-plugin-field",
|
|
798
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
799
|
+
className: "fm-plugin-check",
|
|
800
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
801
|
+
type: "checkbox",
|
|
802
|
+
checked: prefs.scroll === "preserve",
|
|
803
|
+
onChange: (e) => prefsStore.update({ scroll: e.target.checked ? "preserve" : "bottom" })
|
|
804
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
805
|
+
className: "fm-plugin-check-label",
|
|
806
|
+
children: t("settings.scrollPreserve")
|
|
807
|
+
})]
|
|
808
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
809
|
+
className: "fm-plugin-field-hint",
|
|
810
|
+
children: t("settings.scrollPreserve.hint")
|
|
811
|
+
})]
|
|
812
|
+
}),
|
|
813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
814
|
+
className: "fm-plugin-field",
|
|
815
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
816
|
+
className: "fm-plugin-check",
|
|
817
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
818
|
+
type: "checkbox",
|
|
819
|
+
checked: prefs.navbar,
|
|
820
|
+
onChange: (e) => prefsStore.update({ navbar: e.target.checked })
|
|
821
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
822
|
+
className: "fm-plugin-check-label",
|
|
823
|
+
children: t("settings.navbar")
|
|
824
|
+
})]
|
|
825
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
826
|
+
className: "fm-plugin-field-hint",
|
|
827
|
+
children: t("settings.navbar.hint")
|
|
828
|
+
})]
|
|
829
|
+
}),
|
|
830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
831
|
+
className: "fm-plugin-field",
|
|
832
|
+
children: [
|
|
833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
834
|
+
className: "fm-plugin-field-head",
|
|
835
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
836
|
+
className: "fm-plugin-field-label",
|
|
837
|
+
children: t("settings.width")
|
|
838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
839
|
+
className: "fm-plugin-field-value",
|
|
840
|
+
children: [prefs.width, "px"]
|
|
841
|
+
})]
|
|
842
|
+
}),
|
|
843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
844
|
+
type: "range",
|
|
845
|
+
className: "fm-plugin-range",
|
|
846
|
+
min: 480,
|
|
847
|
+
max: 1200,
|
|
848
|
+
step: 40,
|
|
849
|
+
value: prefs.width,
|
|
850
|
+
onChange: (e) => prefsStore.update({ width: Number(e.target.value) })
|
|
851
|
+
}),
|
|
852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
853
|
+
className: "fm-plugin-field-hint",
|
|
854
|
+
children: t("settings.width.hint")
|
|
855
|
+
})
|
|
856
|
+
]
|
|
857
|
+
})
|
|
858
|
+
] });
|
|
859
|
+
}
|
|
860
|
+
function FocusSettingsCard({ t }) {
|
|
757
861
|
const [open, setOpen] = (0, react.useState)(false);
|
|
758
862
|
const title = t("settings.label");
|
|
759
863
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -774,90 +878,100 @@ window.__ModuleLoader__.load({
|
|
|
774
878
|
children: t("settings.description")
|
|
775
879
|
})]
|
|
776
880
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
|
|
777
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
881
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
778
882
|
className: "fm-plugin-card-body",
|
|
883
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t })
|
|
884
|
+
}) : null]
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function FocusOnboarding(props) {
|
|
888
|
+
const { complete, openSection, t } = props;
|
|
889
|
+
const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
|
|
890
|
+
const finished = (0, react.useRef)(false);
|
|
891
|
+
const finish = (0, react.useCallback)(() => {
|
|
892
|
+
if (finished.current) return;
|
|
893
|
+
finished.current = true;
|
|
894
|
+
complete();
|
|
895
|
+
}, [complete]);
|
|
896
|
+
(0, react.useEffect)(() => {
|
|
897
|
+
if (done) finish();
|
|
898
|
+
}, [done, finish]);
|
|
899
|
+
(0, react.useEffect)(() => {
|
|
900
|
+
const appRoot = document.getElementById("root");
|
|
901
|
+
if (!appRoot) return;
|
|
902
|
+
const previous = appRoot.inert;
|
|
903
|
+
appRoot.inert = true;
|
|
904
|
+
return () => {
|
|
905
|
+
appRoot.inert = previous;
|
|
906
|
+
};
|
|
907
|
+
}, []);
|
|
908
|
+
if (done) return null;
|
|
909
|
+
const dismiss = () => {
|
|
910
|
+
onboardingStore.markDone();
|
|
911
|
+
setDone(true);
|
|
912
|
+
finish();
|
|
913
|
+
};
|
|
914
|
+
const goSettings = () => {
|
|
915
|
+
onboardingStore.markDone();
|
|
916
|
+
setDone(true);
|
|
917
|
+
finish();
|
|
918
|
+
openSection("plugins");
|
|
919
|
+
};
|
|
920
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
921
|
+
open: true,
|
|
922
|
+
title: t("onboarding.title"),
|
|
923
|
+
onClose: dismiss,
|
|
924
|
+
headless: true,
|
|
925
|
+
className: "fm-onboard",
|
|
926
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
927
|
+
className: "fm-onboard-content",
|
|
779
928
|
children: [
|
|
780
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
781
|
-
className: "fm-
|
|
782
|
-
children:
|
|
783
|
-
className: "fm-plugin-check",
|
|
784
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
785
|
-
type: "checkbox",
|
|
786
|
-
checked: prefs.navbar,
|
|
787
|
-
onChange: (e) => prefsStore.update({ navbar: e.target.checked })
|
|
788
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
789
|
-
className: "fm-plugin-check-label",
|
|
790
|
-
children: t("settings.navbar")
|
|
791
|
-
})]
|
|
792
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
793
|
-
className: "fm-plugin-field-hint",
|
|
794
|
-
children: t("settings.navbar.hint")
|
|
795
|
-
})]
|
|
929
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
930
|
+
className: "fm-onboard-title",
|
|
931
|
+
children: t("onboarding.title")
|
|
796
932
|
}),
|
|
797
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
798
|
-
className: "fm-
|
|
799
|
-
children:
|
|
800
|
-
className: "fm-plugin-check",
|
|
801
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
802
|
-
type: "checkbox",
|
|
803
|
-
checked: prefs.scroll === "preserve",
|
|
804
|
-
onChange: (e) => prefsStore.update({ scroll: e.target.checked ? "preserve" : "bottom" })
|
|
805
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
806
|
-
className: "fm-plugin-check-label",
|
|
807
|
-
children: t("settings.scrollPreserve")
|
|
808
|
-
})]
|
|
809
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
810
|
-
className: "fm-plugin-field-hint",
|
|
811
|
-
children: t("settings.scrollPreserve.hint")
|
|
812
|
-
})]
|
|
933
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
934
|
+
className: "fm-onboard-intro",
|
|
935
|
+
children: t("onboarding.intro")
|
|
813
936
|
}),
|
|
814
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
815
|
-
className: "fm-
|
|
816
|
-
children:
|
|
817
|
-
className: "fm-plugin-check",
|
|
818
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
819
|
-
type: "checkbox",
|
|
820
|
-
checked: prefs.autoFocus,
|
|
821
|
-
onChange: (e) => prefsStore.update({ autoFocus: e.target.checked })
|
|
822
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
823
|
-
className: "fm-plugin-check-label",
|
|
824
|
-
children: t("settings.autoFocus")
|
|
825
|
-
})]
|
|
826
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
827
|
-
className: "fm-plugin-field-hint",
|
|
828
|
-
children: t("settings.autoFocus.hint")
|
|
829
|
-
})]
|
|
937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
938
|
+
className: "fm-onboard-subtitle",
|
|
939
|
+
children: t("onboarding.features.title")
|
|
830
940
|
}),
|
|
831
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
832
|
-
className: "fm-
|
|
941
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
942
|
+
className: "fm-onboard-features",
|
|
833
943
|
children: [
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
944
|
+
"fullscreen",
|
|
945
|
+
"fold",
|
|
946
|
+
"navbar",
|
|
947
|
+
"autoFocus"
|
|
948
|
+
].map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("onboarding.feature." + f) }, f))
|
|
949
|
+
}),
|
|
950
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
951
|
+
className: "fm-onboard-subtitle",
|
|
952
|
+
children: t("onboarding.configure.title")
|
|
953
|
+
}),
|
|
954
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }),
|
|
955
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
956
|
+
className: "fm-onboard-note",
|
|
957
|
+
children: t("onboarding.laterNote")
|
|
958
|
+
}),
|
|
959
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
960
|
+
className: "fm-onboard-actions",
|
|
961
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
962
|
+
variant: "outline",
|
|
963
|
+
size: "sm",
|
|
964
|
+
onClick: goSettings,
|
|
965
|
+
children: t("onboarding.openSettings")
|
|
966
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
967
|
+
variant: "primary",
|
|
968
|
+
size: "sm",
|
|
969
|
+
onClick: dismiss,
|
|
970
|
+
children: t("onboarding.done")
|
|
971
|
+
})]
|
|
858
972
|
})
|
|
859
973
|
]
|
|
860
|
-
})
|
|
974
|
+
})
|
|
861
975
|
});
|
|
862
976
|
}
|
|
863
977
|
//#endregion
|
|
@@ -924,6 +1038,17 @@ body[data-fm-focus] [data-dsh-panel-host]{display:none!important}
|
|
|
924
1038
|
body[data-fm-focus] #root{margin-right:0!important;width:100%!important}
|
|
925
1039
|
body[data-fm-focus] #root [data-dsh-frame] > [data-pane="conversation"],
|
|
926
1040
|
body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!important}
|
|
1041
|
+
/* First-run onboarding (settings.onboarding step) — content inside the official
|
|
1042
|
+
Modal primitive; the primitive owns the mask/dialog chrome, these style our body. */
|
|
1043
|
+
.fm-onboard{width:min(600px,100%);padding:0}
|
|
1044
|
+
.fm-onboard-content{box-sizing:border-box;flex-direction:column;max-height:calc(100vh - 48px);padding:28px;display:flex;overflow-y:auto}
|
|
1045
|
+
.fm-onboard-title{color:var(--dsw-alias-label-primary);outline:none;margin:0;font-size:20px;font-weight:500;line-height:28px}
|
|
1046
|
+
.fm-onboard-intro{color:var(--dsw-alias-label-secondary);margin:8px 0 0;font-size:14px;line-height:22px}
|
|
1047
|
+
.fm-onboard-subtitle{color:var(--dsw-alias-label-primary);margin:20px 0 8px;font-size:14px;font-weight:600;line-height:22px}
|
|
1048
|
+
.fm-onboard-features{margin:0;padding:0 0 0 18px;list-style:disc;display:flex;flex-direction:column;gap:6px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
1049
|
+
.fm-onboard-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:18px}
|
|
1050
|
+
.fm-onboard-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
|
|
1051
|
+
@media (width<=560px){.fm-onboard-content{padding:24px}}
|
|
927
1052
|
`;
|
|
928
1053
|
//#endregion
|
|
929
1054
|
//#region src/client/locales.ts
|
|
@@ -937,14 +1062,14 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
937
1062
|
"settings.description": "全屏阅读遮罩的偏好设置",
|
|
938
1063
|
"settings.expand": "展开",
|
|
939
1064
|
"settings.collapse": "收起",
|
|
940
|
-
"settings.navbar": "
|
|
941
|
-
"settings.navbar.hint": "
|
|
942
|
-
"settings.scrollPreserve": "
|
|
943
|
-
"settings.scrollPreserve.hint": "
|
|
944
|
-
"settings.autoFocus": "
|
|
945
|
-
"settings.autoFocus.hint": "
|
|
1065
|
+
"settings.navbar": "显示右侧轮次导航条",
|
|
1066
|
+
"settings.navbar.hint": "在右侧显示导航圆点,每个用户消息(一轮)对应一个,悬停预览、点击跳转",
|
|
1067
|
+
"settings.scrollPreserve": "进入专注模式时,同步当前阅读位置",
|
|
1068
|
+
"settings.scrollPreserve.hint": "进入时定位到你正在阅读的消息;关闭则定位到最新",
|
|
1069
|
+
"settings.autoFocus": "AI完成回复后,自动进入专注模式",
|
|
1070
|
+
"settings.autoFocus.hint": "AI 正常完成回复后自动进入并定位到你的提问",
|
|
946
1071
|
"settings.width": "文字区宽度",
|
|
947
|
-
"settings.width.hint": "
|
|
1072
|
+
"settings.width.hint": "专注模式正文的阅读列宽(480–1200px)",
|
|
948
1073
|
"empty": "暂无打开的会话",
|
|
949
1074
|
"loading": "正在读取会话…",
|
|
950
1075
|
"running": "· 正在工作 …",
|
|
@@ -966,7 +1091,18 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
966
1091
|
"sum.questions": "问了 {n} 个问题",
|
|
967
1092
|
"sum.plans": "计划了 {n} 次",
|
|
968
1093
|
"sum.jobs": "后台任务 {n} 个",
|
|
969
|
-
"sum.others": "调用了 {n} 个工具"
|
|
1094
|
+
"sum.others": "调用了 {n} 个工具",
|
|
1095
|
+
"onboarding.title": "欢迎使用专注模式",
|
|
1096
|
+
"onboarding.intro": "dsh-focus-overlay 为 DeepSeek Harness Web 提供全屏专注阅读。",
|
|
1097
|
+
"onboarding.features.title": "功能",
|
|
1098
|
+
"onboarding.feature.fullscreen": "全屏阅读 —— 隐藏标题栏、输入区与侧栏,纵向空间尽给对话",
|
|
1099
|
+
"onboarding.feature.fold": "折叠工具调用 —— 把 AI 的命令、编辑、搜索等步骤折叠成一行摘要",
|
|
1100
|
+
"onboarding.feature.navbar": "右侧导航条 —— 按用户消息快速跳转,悬停预览",
|
|
1101
|
+
"onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
|
|
1102
|
+
"onboarding.configure.title": "快速配置",
|
|
1103
|
+
"onboarding.laterNote": "这些设置随时可在「设置 → 插件 → 插件配置」中更改。",
|
|
1104
|
+
"onboarding.openSettings": "打开设置",
|
|
1105
|
+
"onboarding.done": "完成"
|
|
970
1106
|
};
|
|
971
1107
|
const en = {
|
|
972
1108
|
"toggle": "Focus",
|
|
@@ -977,12 +1113,12 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
977
1113
|
"settings.description": "Preferences for the full-screen reading overlay",
|
|
978
1114
|
"settings.expand": "Expand",
|
|
979
1115
|
"settings.collapse": "Collapse",
|
|
980
|
-
"settings.navbar": "Show right-side navbar",
|
|
981
|
-
"settings.navbar.hint": "Show navigation
|
|
982
|
-
"settings.scrollPreserve": "
|
|
983
|
-
"settings.scrollPreserve.hint": "Open at
|
|
984
|
-
"settings.autoFocus": "Auto-enter focus mode when a reply
|
|
985
|
-
"settings.autoFocus.hint": "Auto-open after a normal
|
|
1116
|
+
"settings.navbar": "Show right-side turn navbar",
|
|
1117
|
+
"settings.navbar.hint": "Show one navigation dot per user message (turn) — hover to preview, click to jump",
|
|
1118
|
+
"settings.scrollPreserve": "Sync the current reading position when entering focus mode",
|
|
1119
|
+
"settings.scrollPreserve.hint": "Open at the message you're reading; off opens at the latest",
|
|
1120
|
+
"settings.autoFocus": "Auto-enter focus mode when the AI finishes a reply",
|
|
1121
|
+
"settings.autoFocus.hint": "Auto-open at your question after a normal reply",
|
|
986
1122
|
"settings.width": "Text width",
|
|
987
1123
|
"settings.width.hint": "Reading column width (480–1200px)",
|
|
988
1124
|
"empty": "No open session",
|
|
@@ -1006,7 +1142,18 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
1006
1142
|
"sum.questions": "asked {n} questions",
|
|
1007
1143
|
"sum.plans": "planned {n} times",
|
|
1008
1144
|
"sum.jobs": "{n} background jobs",
|
|
1009
|
-
"sum.others": "called {n} tools"
|
|
1145
|
+
"sum.others": "called {n} tools",
|
|
1146
|
+
"onboarding.title": "Welcome to Focus Mode",
|
|
1147
|
+
"onboarding.intro": "dsh-focus-overlay adds full-screen reading to the DeepSeek Harness web GUI.",
|
|
1148
|
+
"onboarding.features.title": "Features",
|
|
1149
|
+
"onboarding.feature.fullscreen": "Full-screen reading — hides the header, composer and sidebars",
|
|
1150
|
+
"onboarding.feature.fold": "Folded tool calls — collapses commands, edits and searches into one summary line",
|
|
1151
|
+
"onboarding.feature.navbar": "Right-side navbar — jump between user messages with hover previews",
|
|
1152
|
+
"onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
|
|
1153
|
+
"onboarding.configure.title": "Quick setup",
|
|
1154
|
+
"onboarding.laterNote": "You can change these anytime in Settings → Plugins → Plugin configuration.",
|
|
1155
|
+
"onboarding.openSettings": "Open settings",
|
|
1156
|
+
"onboarding.done": "Done"
|
|
1010
1157
|
};
|
|
1011
1158
|
//#endregion
|
|
1012
1159
|
//#region src/client/index.ts
|
|
@@ -1136,6 +1283,14 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
1136
1283
|
id: "dsh-focus-overlay",
|
|
1137
1284
|
order: 1e3
|
|
1138
1285
|
}, () => (0, react.createElement)(FocusSettingsCard, { t })));
|
|
1286
|
+
ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
|
|
1287
|
+
name: "settings.onboarding",
|
|
1288
|
+
id: "dsh-focus-overlay",
|
|
1289
|
+
order: 10
|
|
1290
|
+
}, (props) => (0, react.createElement)(FocusOnboarding, {
|
|
1291
|
+
...props,
|
|
1292
|
+
t
|
|
1293
|
+
})));
|
|
1139
1294
|
}
|
|
1140
1295
|
};
|
|
1141
1296
|
return module.exports;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-focus-overlay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|