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 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
- | Show right-side navbar | Toggle the nav dots (default on) |
118
- | Preserve chat position on open | Open at your current reading position; off opens at the latest (default on) |
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
- | 文字区宽度 | 480–1200px 滑块,调整阅读列宽(默认 760px) |
118
- | 回复结束后自动进入专注模式 | 回复正常完成后:关闭时自动进入并定位到提问,已打开时弹「新回复已生成」;AI 提问 / 审批等待时弹「AI 正在等待你的回复」(默认关) |
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 FocusSettingsCard({ t }) {
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.jsxs)("div", {
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.jsxs)("div", {
781
- className: "fm-plugin-field",
782
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
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.jsxs)("div", {
798
- className: "fm-plugin-field",
799
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
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.jsxs)("div", {
815
- className: "fm-plugin-field",
816
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
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.jsxs)("div", {
832
- className: "fm-plugin-field",
941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
942
+ className: "fm-onboard-features",
833
943
  children: [
834
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
835
- className: "fm-plugin-field-head",
836
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
837
- className: "fm-plugin-field-label",
838
- children: t("settings.width")
839
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
840
- className: "fm-plugin-field-value",
841
- children: [prefs.width, "px"]
842
- })]
843
- }),
844
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
845
- type: "range",
846
- className: "fm-plugin-range",
847
- min: 480,
848
- max: 1200,
849
- step: 40,
850
- value: prefs.width,
851
- onChange: (e) => prefsStore.update({ width: Number(e.target.value) })
852
- }),
853
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
854
- className: "fm-plugin-field-hint",
855
- children: t("settings.width.hint")
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
- }) : null]
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": "回复正常完成后自动打开;AI 提问 / 审批等待时弹出提醒",
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": "专注模式阅读列宽度(480–1200px)",
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 dots for user messages on the right",
982
- "settings.scrollPreserve": "Preserve chat position on open",
983
- "settings.scrollPreserve.hint": "Open at your current reading position; off opens at the latest",
984
- "settings.autoFocus": "Auto-enter focus mode when a reply completes",
985
- "settings.autoFocus.hint": "Auto-open after a normal completion; toast while AI waits for you",
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.6.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",