dsh-focus-overlay 0.10.0 → 0.10.1
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 +1 -1
- package/README.md +1 -1
- package/lib/client.js +48 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -58,7 +58,7 @@ Once in focus mode, an assistant turn no longer shows every step — it folds in
|
|
|
58
58
|
| Precise scroll preservation | Opens at the message you're reading in the chat (aligned by `seq`), not from the top |
|
|
59
59
|
| Right-side node navbar | One dot per user message; active pill follows scroll, hover preview, click to jump, auto-hidden under 2 messages |
|
|
60
60
|
| Back to latest | A centered "↓" floating button when you scroll away from the bottom with no draft |
|
|
61
|
-
| In-focus composer | The compact input bar appears at the live edge and shares the main composer's draft — `Enter` sends (queued while the AI runs); with a draft, clicking the conversation folds it into a blue-dot pill, one click to expand |
|
|
61
|
+
| In-focus composer | The compact input bar appears at the live edge and shares the main composer's draft — `Enter` sends (queued while the AI runs); with a draft, clicking the conversation folds it into a blue-dot pill, one click to expand; sending from the live edge auto-scrolls just enough to fully reveal the message you sent, never hidden behind the bar |
|
|
62
62
|
| File mentions | Wires `chatFileMentions`, turning inline-code tokens that name real files into clickable links |
|
|
63
63
|
| i18n | Chinese / English copy, follows the UI language |
|
|
64
64
|
| Plugin configuration card | A collapsible card under "Settings → Plugins → Plugin configuration": navbar toggle, open-position strategy, text-area width — persisted to `localStorage` |
|
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
| 精确保留位置 | 进入专注时定位到聊天视图中正在阅读的消息(按 `seq` 对齐),而非从头开始 |
|
|
56
56
|
| 右侧节点导航条 | 每个 user 消息一个圆点,激活药丸跟随滚动、悬停预览、点击平滑跳转,少于 2 条自动隐藏 |
|
|
57
57
|
| 回到最新 | 无草稿且离开底部时,底部居中显示「↓」悬浮按钮 |
|
|
58
|
-
| 专注内输入条 | 滚到最底部自动展开精简输入框,与主输入框共享同一份草稿;`Enter` 发送(AI
|
|
58
|
+
| 专注内输入条 | 滚到最底部自动展开精简输入框,与主输入框共享同一份草稿;`Enter` 发送(AI 忙时自动排队);有草稿时点击对话区自动收起为带蓝点的圆钮,一键展开续写;在底部发送多行长消息后自动上滚刚好一段,让刚发出的消息完整露出、不被输入条遮挡 |
|
|
59
59
|
| 文件提及 | 接 `chatFileMentions`,内联代码命中真实文件时变成可点链接 |
|
|
60
60
|
| i18n | 中 / 英文案,跟随界面语言 |
|
|
61
61
|
| 插件配置卡片 | 在「设置 → 插件 → 插件配置」中的可折叠卡片,含导航条开关、打开定位策略、文字区宽度,持久化到 `localStorage` |
|
package/lib/client.js
CHANGED
|
@@ -216,6 +216,18 @@ window.__ModuleLoader__.load({
|
|
|
216
216
|
for (let i = items.length - 1; i >= 0; i--) if (items[i].kind === "user") return i;
|
|
217
217
|
return -1;
|
|
218
218
|
}
|
|
219
|
+
/** Seq of the newest prompt row (user OR steering) in an already-built focus
|
|
220
|
+
* list — the row a just-sent message occupies once it renders. Unlike
|
|
221
|
+
* {@link lastUserSeq} (raw nodes), this reads the rendered list, so a blank
|
|
222
|
+
* prompt or an unsent queue entry is already filtered out. Null when no
|
|
223
|
+
* prompt row exists. */
|
|
224
|
+
function lastPromptSeq(items) {
|
|
225
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
226
|
+
const it = items[i];
|
|
227
|
+
if (it.kind === "user" || it.kind === "steering") return it.seq;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
219
231
|
/**
|
|
220
232
|
* Decide whether a settled conversation snapshot (`running === false`) ends in
|
|
221
233
|
* a *normal* completed reply, and which user message started it.
|
|
@@ -315,6 +327,18 @@ window.__ModuleLoader__.load({
|
|
|
315
327
|
return prev;
|
|
316
328
|
}
|
|
317
329
|
/**
|
|
330
|
+
* Whether the overlay should scroll to uncover the just-sent prompt row. The
|
|
331
|
+
* send path arms the reveal only while the reader sits in the bottom zone
|
|
332
|
+
* (sending from up in history must never yank the view), and the reveal fires
|
|
333
|
+
* only once the newest prompt seq has advanced — the sent row has actually
|
|
334
|
+
* rendered; delivery can lag the click by a snapshot or two through queue
|
|
335
|
+
* adjudication. An unchanged seq (ordinary streaming snapshots, mount) or an
|
|
336
|
+
* unarmed state never scrolls.
|
|
337
|
+
*/
|
|
338
|
+
function shouldRevealSentPrompt(o) {
|
|
339
|
+
return o.armed && o.nextSeq != null && o.nextSeq !== o.prevSeq;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
318
342
|
* Index of the conversation-turn dot the nav rail should highlight, from live
|
|
319
343
|
* scroll geometry. The rail is a position indicator, so the decision is pure
|
|
320
344
|
* math over measured anchor tops (document order) plus the distance to the
|
|
@@ -1033,6 +1057,8 @@ window.__ModuleLoader__.load({
|
|
|
1033
1057
|
const [localDraft, setLocalDraft] = (0, react.useState)("");
|
|
1034
1058
|
const taRef = (0, react.useRef)(null);
|
|
1035
1059
|
const focusBarOnce = (0, react.useRef)(false);
|
|
1060
|
+
const revealArmed = (0, react.useRef)(false);
|
|
1061
|
+
const seenPromptSeq = (0, react.useRef)(null);
|
|
1036
1062
|
const computeActive = () => {
|
|
1037
1063
|
if (!bodyEl) return null;
|
|
1038
1064
|
const bodyTop = bodyEl.getBoundingClientRect().top;
|
|
@@ -1129,6 +1155,27 @@ window.__ModuleLoader__.load({
|
|
|
1129
1155
|
(0, react.useEffect)(() => {
|
|
1130
1156
|
scheduleRef.current();
|
|
1131
1157
|
}, [snap, prefs.width]);
|
|
1158
|
+
(0, react.useEffect)(() => {
|
|
1159
|
+
const seq = lastPromptSeq(items);
|
|
1160
|
+
if (seq != null && shouldRevealSentPrompt({
|
|
1161
|
+
armed: revealArmed.current,
|
|
1162
|
+
prevSeq: seenPromptSeq.current,
|
|
1163
|
+
nextSeq: seq
|
|
1164
|
+
})) {
|
|
1165
|
+
revealArmed.current = false;
|
|
1166
|
+
const idx = findSeqIndex(items, seq);
|
|
1167
|
+
const el = idx >= 0 ? anchors["fm-" + idx] : void 0;
|
|
1168
|
+
if (el && bodyEl) {
|
|
1169
|
+
const limit = bodyEl.getBoundingClientRect().bottom - 110;
|
|
1170
|
+
const hidden = el.getBoundingClientRect().bottom - limit;
|
|
1171
|
+
if (hidden > 0) bodyEl.scrollBy({
|
|
1172
|
+
top: hidden,
|
|
1173
|
+
behavior: "smooth"
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
seenPromptSeq.current = seq;
|
|
1178
|
+
}, [items]);
|
|
1132
1179
|
(0, react.useEffect)(() => () => {
|
|
1133
1180
|
if (rafId.current !== null) {
|
|
1134
1181
|
cancelAnimationFrame(rafId.current);
|
|
@@ -1200,6 +1247,7 @@ window.__ModuleLoader__.load({
|
|
|
1200
1247
|
const send = () => {
|
|
1201
1248
|
const text = draftValue;
|
|
1202
1249
|
if (text.trim() === "") return;
|
|
1250
|
+
if (zone) revealArmed.current = true;
|
|
1203
1251
|
if (inputFace) try {
|
|
1204
1252
|
inputFace.submit("queue");
|
|
1205
1253
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-focus-overlay",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|