deepseek-harness-background 0.2.1 → 0.3.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.md +8 -2
- package/README.zh.md +8 -2
- package/lib/client.js +1213 -75
- package/lib/client.js.map +1 -1
- package/lib/index.js +8 -3
- package/package.json +8 -8
package/lib/client.js
CHANGED
|
@@ -4,28 +4,84 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
//#endregion
|
|
7
29
|
let react = require("react");
|
|
30
|
+
react = __toESM(react, 1);
|
|
8
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
let react_dom = require("react-dom");
|
|
33
|
+
react_dom = __toESM(react_dom, 1);
|
|
9
34
|
//#region src/client/background-css.ts
|
|
10
35
|
/**
|
|
11
36
|
* Background CSS — the stylesheet injected once into the document when the
|
|
12
37
|
* plugin first runs. It defines the behind-body wallpaper layer, the scrim,
|
|
13
|
-
* and the frosted-glass
|
|
14
|
-
* onto `document.body`. Kept in a single idempotent
|
|
15
|
-
* tag (mirrors the reference `dsh-wallpaper-engine`
|
|
38
|
+
* and the frosted-glass surfaces, all driven by CSS variables the painter
|
|
39
|
+
* writes onto `document.body`. Kept in a single idempotent
|
|
40
|
+
* `<style data-plugin-css>` tag (mirrors the reference `dsh-wallpaper-engine`
|
|
41
|
+
* bundle).
|
|
16
42
|
*/
|
|
17
43
|
/** Unique id stamped on the injected style element (dedup key). */
|
|
18
44
|
const BACKGROUND_CSS_TAG = "deepseek-harness-background/styles";
|
|
19
45
|
/** Body attribute the painter sets while a wallpaper is active. */
|
|
20
46
|
const ACTIVE_ATTR = "data-dsh-bg";
|
|
21
47
|
/**
|
|
48
|
+
* Body attribute the painter sets only while the glass is actually on (a
|
|
49
|
+
* wallpaper is active AND panelOpacity < 1). The explicit-fill rules below —
|
|
50
|
+
* whitelisted buttons, popovers and badges whose official fills are not
|
|
51
|
+
* token-mediated — key on this gate so they return to the exact official
|
|
52
|
+
* paints when the user maxes the panel-opacity slider.
|
|
53
|
+
*/
|
|
54
|
+
const GLASS_ATTR = "data-dsh-bg-glass";
|
|
55
|
+
/**
|
|
22
56
|
* The backdrop CSS. The wallpaper is a fixed `z-index:-2` element (below the
|
|
23
|
-
* scrim at `-1`, below the UI). While active, the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
57
|
+
* scrim at `-1`, below the UI). While active, the app frame + sidebar fills
|
|
58
|
+
* turn transparent so all columns share one backdrop.
|
|
59
|
+
*
|
|
60
|
+
* Frosted glass is a WHITELIST, not a blanket: only the surfaces that float
|
|
61
|
+
* as small cards/buttons over the wallpaper are glassed — composer card,
|
|
62
|
+
* message bubbles, code surfaces (blocks, inline code, tool IO cards,
|
|
63
|
+
* skill/MCP call cards), the three chrome buttons (new session, composer +
|
|
64
|
+
* button, scroll-to-bottom), the agent task strip family (`
|
|
65
|
+
* --dsw-specific-tip`), the subagent lineage popover, the home hero
|
|
66
|
+
* "preview" badge, and the timeline rail (timeline-css.ts). Every glassed
|
|
67
|
+
* surface shares ONE recipe: fill from the painter's --dsw-specific-input-
|
|
68
|
+
* major token (same alpha curve + theme dimming), the full shared
|
|
69
|
+
* blur/saturate/brightness chain driven by the glass-blur slider, and the
|
|
70
|
+
* shared sheen vars — so no surface reads heavier than the composer card.
|
|
71
|
+
* Dialogs, the settings UI, menus, tooltips, toasts and every hover/accent
|
|
72
|
+
* fill keep the OFFICIAL opaque paints — they are reading surfaces and must
|
|
73
|
+
* stay legible.
|
|
74
|
+
*
|
|
75
|
+
* Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,
|
|
76
|
+
* `data-terminal`…), the `:global` `.md-code-block` anchor, and CSS-module
|
|
77
|
+
* SUFFIX conventions (`_bubble`, `_newSession`…) audited for collisions:
|
|
78
|
+
* `_bubble` excludes `role="tooltip"` (Tooltip.module.css shares the suffix),
|
|
79
|
+
* `_add` is scoped under `[data-composer-card]` (DiffBlock line markers share
|
|
80
|
+
* it), `_newSession` excludes `Label` (the button's inner label span) and
|
|
81
|
+
* `_toBottom` excludes `Slot` (the zero-height sticky wrapper) — both share
|
|
82
|
+
* the substring with the chrome control but must keep their own paint, and
|
|
83
|
+
* the subagent popover is `role="tree"` + `_menu` so the generic menu
|
|
84
|
+
* surface stays untouched.
|
|
29
85
|
*/
|
|
30
86
|
const BACKGROUND_CSS = `
|
|
31
87
|
.dsh-bg-layer {
|
|
@@ -58,22 +114,118 @@ window.__ModuleLoader__.load({
|
|
|
58
114
|
--dsw-specific-sidebar-fill: transparent;
|
|
59
115
|
}
|
|
60
116
|
|
|
61
|
-
/*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
/* ---- Whitelisted frosted-glass sheet ---------------------------------
|
|
118
|
+
Fills come from the --dsw-* tokens the painter overrides on body ONLY for
|
|
119
|
+
this list's own surfaces (input/bubble/code/tip); this block adds the
|
|
120
|
+
shared wet-glass sheen + blur from --bg-glass-blur. Exposure is calibrated
|
|
121
|
+
per scheme (the painter writes --bg-glass-brightness / --bg-glass-sheen):
|
|
122
|
+
light glass slightly DIMS, dark glass keeps a slight lift. Gated on the
|
|
123
|
+
GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel
|
|
124
|
+
slider returns these surfaces to the exact official paints. */
|
|
125
|
+
body[data-dsh-bg-glass] [data-composer-card],
|
|
126
|
+
body[data-dsh-bg-glass] [class*="_bubble"]:not([role="tooltip"]),
|
|
127
|
+
body[data-dsh-bg-glass] .md-code-block,
|
|
128
|
+
body[data-dsh-bg-glass] [data-terminal],
|
|
129
|
+
body[data-dsh-bg-glass] [data-diff],
|
|
130
|
+
body[data-dsh-bg-glass] [data-read],
|
|
131
|
+
body[data-dsh-bg-glass] [data-search],
|
|
132
|
+
body[data-dsh-bg-glass] [data-web],
|
|
133
|
+
body[data-dsh-bg-glass] [class*="_ioCard"],
|
|
134
|
+
body[data-dsh-bg-glass] [class*="_instructionsCard"],
|
|
135
|
+
body[data-dsh-bg-glass] [class*="_markdown"] :not(pre) > code {
|
|
136
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
137
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
138
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
72
139
|
box-shadow:
|
|
73
|
-
inset 0 1px 0 rgba(255, 255, 255,
|
|
140
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32),
|
|
74
141
|
inset 0 -1px 0 rgba(255, 255, 255, 0.08),
|
|
75
142
|
inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),
|
|
76
|
-
0 12px 40px rgba(0, 0, 0,
|
|
143
|
+
0 12px 40px rgba(0, 0, 0, 0.12);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* The sticky code-block header officially occludes scrolled code with
|
|
147
|
+
bg-base (transparent under a wallpaper): refill it with the banner token
|
|
148
|
+
(itself translucent glass) so code stays hidden beneath the sticky bar. */
|
|
149
|
+
body[data-dsh-bg] .md-code-block [class*="_bannerWrap"] {
|
|
150
|
+
background-color: var(--dsw-alias-markdown-code-block-banner);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* The sidebar build badge paints with the label-ink token — a TEXT token we
|
|
154
|
+
must not override globally — so it gets a theme-aware translucent ink. */
|
|
155
|
+
body[data-dsh-bg] [class*="_buildRevision"] {
|
|
156
|
+
background-color: rgba(15, 17, 21, 0.62);
|
|
157
|
+
}
|
|
158
|
+
body[data-ds-dark-theme][data-dsh-bg] [class*="_buildRevision"] {
|
|
159
|
+
background-color: rgba(249, 250, 251, 0.62);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The empty-state hero glow asset would wash out the wallpaper behind the
|
|
163
|
+
welcome message — dim it so the art stays visible. */
|
|
164
|
+
body[data-dsh-bg] [class*="_heroGlow"] {
|
|
165
|
+
opacity: 0.2;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ---- Whitelisted chrome buttons --------------------------------------
|
|
169
|
+
These three buttons' official fills ride button-* tokens shared with
|
|
170
|
+
unrelated surfaces, so instead of overriding the tokens they get explicit
|
|
171
|
+
paints — but the SAME glass recipe as the composer card and bubbles:
|
|
172
|
+
fill = the painter's --dsw-specific-input-major token (the identical
|
|
173
|
+
translucent white + theme curve), filter = the sheet's exact
|
|
174
|
+
blur/saturate/brightness chain (blur follows the glass-blur slider, no
|
|
175
|
+
caps), sheen = the shared sheen vars. Hover = the same white glass at the
|
|
176
|
+
boosted alpha var. Gated on data-dsh-bg-glass (off when the panel is
|
|
177
|
+
fully opaque). */
|
|
178
|
+
body[data-dsh-bg-glass] [class*="_newSession"]:not([class*="Label"]),
|
|
179
|
+
body[data-dsh-bg-glass] [data-composer-card] [class*="_add"],
|
|
180
|
+
body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]) {
|
|
181
|
+
background-color: var(--dsw-specific-input-major);
|
|
182
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
183
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
184
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
185
|
+
box-shadow:
|
|
186
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32),
|
|
187
|
+
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),
|
|
188
|
+
0 4px 14px rgba(0, 0, 0, 0.08);
|
|
189
|
+
}
|
|
190
|
+
body[data-dsh-bg-glass] [class*="_newSession"]:not([class*="Label"]):hover,
|
|
191
|
+
body[data-dsh-bg-glass] [data-composer-card] [class*="_add"]:hover:not(:disabled),
|
|
192
|
+
body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]):hover {
|
|
193
|
+
background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
|
|
194
|
+
}
|
|
195
|
+
/* Collapsed rail renders the new-session control as a bare icon — keep the
|
|
196
|
+
official transparent paint (and its official translucent hover). */
|
|
197
|
+
body[data-dsh-bg-glass] [class*="_collapsed"] [class*="_newSession"],
|
|
198
|
+
body[data-dsh-bg-glass] [class*="_collapsed"] [class*="_newSession"]:hover {
|
|
199
|
+
background-color: transparent;
|
|
200
|
+
background-image: none;
|
|
201
|
+
box-shadow: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ---- Subagent lineage popover (title-bar expanded list) --------------
|
|
205
|
+
Its official fill rides the menu token shared with every dropdown; glass
|
|
206
|
+
THIS popover explicitly via its role="tree" + _menu combination so real
|
|
207
|
+
menus stay officially opaque and legible. Same unified recipe: the
|
|
208
|
+
composer fill token + the full shared filter chain (the official
|
|
209
|
+
elevation shadow stays). */
|
|
210
|
+
body[data-dsh-bg-glass] [role="tree"][class*="_menu"] {
|
|
211
|
+
background-color: var(--dsw-specific-input-major);
|
|
212
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
213
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
214
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ---- Home hero "preview" badge (top-right superscript pill) ----------
|
|
218
|
+
Officially the state-business-tertiary pastel; re-emit the same hue as
|
|
219
|
+
glass but with the UNIFIED alpha curve (color-mix against the painter's
|
|
220
|
+
--bg-glass-alpha) and the shared blur chain, so it sits on the wallpaper
|
|
221
|
+
with exactly the glass strength of the composer card. */
|
|
222
|
+
body[data-dsh-bg-glass] [class*="_previewBadge"] {
|
|
223
|
+
background-color: color-mix(in srgb, rgb(228 237 253) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);
|
|
224
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
225
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
226
|
+
}
|
|
227
|
+
body[data-ds-dark-theme][data-dsh-bg-glass] [class*="_previewBadge"] {
|
|
228
|
+
background-color: color-mix(in srgb, rgb(52 65 91) calc(var(--bg-glass-alpha, 0.72) * 100%), transparent);
|
|
77
229
|
}
|
|
78
230
|
`;
|
|
79
231
|
/**
|
|
@@ -117,22 +269,88 @@ window.__ModuleLoader__.load({
|
|
|
117
269
|
const LAYER_CLASS = "dsh-bg-layer";
|
|
118
270
|
const IMAGE_CLASS = "dsh-bg-image";
|
|
119
271
|
const SCRIM_CLASS = "dsh-bg-scrim";
|
|
120
|
-
/**
|
|
121
|
-
|
|
272
|
+
/**
|
|
273
|
+
* White-glass tokens — the WHITELISTED surfaces only. Everything that floats
|
|
274
|
+
* as a small card over the wallpaper: the composer input stack, message
|
|
275
|
+
* bubbles, the markdown code surfaces (blocks + banner + inline code), and
|
|
276
|
+
* the agent task strip family (`--dsw-specific-tip`: TodoPanel + GoalBar +
|
|
277
|
+
* QueueDock share one dock column and one fill token). Menus, dialogs, the
|
|
278
|
+
* settings UI, tooltips, toasts, hover fills and every accent keep the
|
|
279
|
+
* OFFICIAL token values — they are reading surfaces and must stay legible.
|
|
280
|
+
* `factor` scales the alpha relative to the input surface.
|
|
281
|
+
*/
|
|
282
|
+
const GLASS_SURFACE_TOKENS = [
|
|
283
|
+
{
|
|
284
|
+
token: "--dsw-specific-input-major",
|
|
285
|
+
factor: 1
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
token: "--dsw-specific-bubble",
|
|
289
|
+
factor: .8
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
token: "--dsw-alias-markdown-code-block",
|
|
293
|
+
factor: 1
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
token: "--dsw-alias-markdown-code-block-banner",
|
|
297
|
+
factor: 1
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
token: "--dsw-alias-markdown-inline-code",
|
|
301
|
+
factor: .9
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
token: "--dsw-specific-tip",
|
|
305
|
+
factor: 1
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
/** Every token the glass owns (save/restore key set). */
|
|
309
|
+
const GLASS_TOKENS = GLASS_SURFACE_TOKENS.map(({ token }) => token);
|
|
122
310
|
/** Fraction of the official white surface alpha kept at the most transparent panel setting. */
|
|
123
311
|
const GLASS_MIN_ALPHA = .05;
|
|
312
|
+
/** Base white-glass alpha for one panelOpacity (the curve every surface shares). */
|
|
313
|
+
function glassAlpha(panelOpacity) {
|
|
314
|
+
const alpha = GLASS_MIN_ALPHA + panelOpacity * .85;
|
|
315
|
+
return Math.max(0, Math.min(.9, alpha));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Saturation boost that rides the blur radius. Gentler than the reference
|
|
319
|
+
* engine (1.1 + blur * 0.02, capped at 1.6): stacking a hot saturate on top of
|
|
320
|
+
* the brightness/sheen layers pushed bright wallpapers into clipping.
|
|
321
|
+
*/
|
|
322
|
+
function glassSaturate(blur) {
|
|
323
|
+
if (blur <= 0) return 1;
|
|
324
|
+
return Math.round(Math.min(1.6, 1.1 + blur * .02) * 1e3) / 1e3;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Exposure calibration per scheme. Light glass neither boosts brightness nor
|
|
328
|
+
* stacks a heavy sheen (mainstream frosted recipes — macOS vibrancy, Windows
|
|
329
|
+
* acrylic, common web glass — blur + saturate + tint without a positive
|
|
330
|
+
* brightness gain); dark glass keeps the reference engine's slight lift, which
|
|
331
|
+
* stops the white-glass fills from turning muddy.
|
|
332
|
+
*/
|
|
333
|
+
function glassExposure(inDark) {
|
|
334
|
+
return inDark ? {
|
|
335
|
+
brightness: "1.04",
|
|
336
|
+
sheen: "0.16",
|
|
337
|
+
sheenMid: "0.05"
|
|
338
|
+
} : {
|
|
339
|
+
brightness: "0.98",
|
|
340
|
+
sheen: "0.07",
|
|
341
|
+
sheenMid: "0.02"
|
|
342
|
+
};
|
|
343
|
+
}
|
|
124
344
|
/**
|
|
125
345
|
* The theme-aware glass surface alphas for one panelOpacity: the input-surface
|
|
126
346
|
* token and the bubble token share one curve, dimmed per scheme. Shared by the
|
|
127
347
|
* live painter and the settings-row preview card so the two cannot drift.
|
|
128
348
|
*/
|
|
129
349
|
function glassSurfaceAlphas(panelOpacity, inDark) {
|
|
130
|
-
const alpha =
|
|
131
|
-
const clamped = Math.max(0, Math.min(.9, alpha));
|
|
132
|
-
const factor = inDark ? .4 : .8;
|
|
350
|
+
const alpha = glassAlpha(panelOpacity) * (inDark ? .4 : .8);
|
|
133
351
|
return {
|
|
134
|
-
major: `rgba(255, 255, 255, ${
|
|
135
|
-
bubble: `rgba(255, 255, 255, ${(
|
|
352
|
+
major: `rgba(255, 255, 255, ${alpha.toFixed(3)})`,
|
|
353
|
+
bubble: `rgba(255, 255, 255, ${(alpha * .8).toFixed(3)})`
|
|
136
354
|
};
|
|
137
355
|
}
|
|
138
356
|
/**
|
|
@@ -160,7 +378,7 @@ window.__ModuleLoader__.load({
|
|
|
160
378
|
rememberOnce(prop) {
|
|
161
379
|
if (this.savedTokens.has(prop) || this.savedVars.has(prop)) return;
|
|
162
380
|
const value = document.body.style.getPropertyValue(prop);
|
|
163
|
-
if (
|
|
381
|
+
if (GLASS_TOKENS.includes(prop)) this.savedTokens.set(prop, value);
|
|
164
382
|
else this.savedVars.set(prop, value);
|
|
165
383
|
}
|
|
166
384
|
/**
|
|
@@ -214,7 +432,7 @@ window.__ModuleLoader__.load({
|
|
|
214
432
|
this.setVar("--bg-wallpaper-scale", (1 + settings.wallpaperBlur * .006).toFixed(4));
|
|
215
433
|
this.setVar("--bg-scrim", String(settings.scrim));
|
|
216
434
|
this.setVar("--bg-glass-blur", `${settings.blur}px`);
|
|
217
|
-
this.setVar("--bg-glass-saturate", String(
|
|
435
|
+
this.setVar("--bg-glass-saturate", String(glassSaturate(settings.blur)));
|
|
218
436
|
this.applySurfaceGlass(settings, false);
|
|
219
437
|
if (!this.observer) {
|
|
220
438
|
this.observer = new MutationObserver(() => this.applySurfaceGlass(this.settings, false));
|
|
@@ -247,7 +465,7 @@ window.__ModuleLoader__.load({
|
|
|
247
465
|
break;
|
|
248
466
|
case "blur":
|
|
249
467
|
this.setVar("--bg-glass-blur", `${value}px`);
|
|
250
|
-
this.setVar("--bg-glass-saturate", String(
|
|
468
|
+
this.setVar("--bg-glass-saturate", String(glassSaturate(Number(value))));
|
|
251
469
|
break;
|
|
252
470
|
case "wallpaperBlur":
|
|
253
471
|
this.setVar("--bg-wallpaper-blur", `${value}px`);
|
|
@@ -266,9 +484,9 @@ window.__ModuleLoader__.load({
|
|
|
266
484
|
applySurfaceGlass(settings, forceRestore) {
|
|
267
485
|
const s = document.body.style;
|
|
268
486
|
const inDark = document.body.dataset.dsDarkTheme !== void 0;
|
|
269
|
-
for (const token of
|
|
487
|
+
for (const token of GLASS_TOKENS) this.rememberOnce(token);
|
|
270
488
|
const restore = () => {
|
|
271
|
-
for (const token of
|
|
489
|
+
for (const token of GLASS_TOKENS) {
|
|
272
490
|
const original = this.savedTokens.get(token);
|
|
273
491
|
if (original !== void 0 && original !== "") s.setProperty(token, original);
|
|
274
492
|
else s.removeProperty(token);
|
|
@@ -276,12 +494,22 @@ window.__ModuleLoader__.load({
|
|
|
276
494
|
};
|
|
277
495
|
if (forceRestore || settings === void 0 || settings.panelOpacity >= 1) {
|
|
278
496
|
restore();
|
|
497
|
+
document.body.removeAttribute(GLASS_ATTR);
|
|
279
498
|
this.setVar("--bg-glass-blur", "0px");
|
|
499
|
+
this.setVar("--bg-glass-brightness", "1");
|
|
500
|
+
s.removeProperty("--bg-glass-alpha");
|
|
501
|
+
s.removeProperty("--bg-glass-alpha-strong");
|
|
280
502
|
return;
|
|
281
503
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
504
|
+
document.body.setAttribute(GLASS_ATTR, "on");
|
|
505
|
+
const exposure = glassExposure(inDark);
|
|
506
|
+
this.setVar("--bg-glass-brightness", exposure.brightness);
|
|
507
|
+
this.setVar("--bg-glass-sheen", exposure.sheen);
|
|
508
|
+
this.setVar("--bg-glass-sheen-mid", exposure.sheenMid);
|
|
509
|
+
const base = glassAlpha(settings.panelOpacity) * (inDark ? .4 : .8);
|
|
510
|
+
for (const { token, factor } of GLASS_SURFACE_TOKENS) s.setProperty(token, `rgba(255, 255, 255, ${(base * factor).toFixed(3)})`);
|
|
511
|
+
this.setVar("--bg-glass-alpha", base.toFixed(3));
|
|
512
|
+
this.setVar("--bg-glass-alpha-strong", Math.min(.9, base * 1.5).toFixed(3));
|
|
285
513
|
}
|
|
286
514
|
/** Remove the wallpaper + scrim layers and clear the active attribute. */
|
|
287
515
|
removeLayers() {
|
|
@@ -302,6 +530,7 @@ window.__ModuleLoader__.load({
|
|
|
302
530
|
this.removeLayers();
|
|
303
531
|
const s = document.body.style;
|
|
304
532
|
document.body.removeAttribute(ACTIVE_ATTR);
|
|
533
|
+
document.body.removeAttribute(GLASS_ATTR);
|
|
305
534
|
for (const [prop, value] of this.savedTokens) s.setProperty(prop, value);
|
|
306
535
|
for (const [prop, value] of this.savedVars) s.setProperty(prop, value);
|
|
307
536
|
this.savedTokens.clear();
|
|
@@ -343,6 +572,7 @@ window.__ModuleLoader__.load({
|
|
|
343
572
|
"--bg-wallpaper-scale",
|
|
344
573
|
"--bg-glass-blur",
|
|
345
574
|
"--bg-glass-saturate",
|
|
575
|
+
"--bg-glass-brightness",
|
|
346
576
|
"--bg-preview-glass"
|
|
347
577
|
];
|
|
348
578
|
/**
|
|
@@ -362,11 +592,13 @@ window.__ModuleLoader__.load({
|
|
|
362
592
|
const inDark = document.body.dataset.dsDarkTheme !== void 0;
|
|
363
593
|
if (settings.panelOpacity >= 1) {
|
|
364
594
|
s.setProperty("--bg-glass-blur", "0px");
|
|
595
|
+
s.setProperty("--bg-glass-brightness", "1");
|
|
365
596
|
s.setProperty("--bg-preview-glass", "var(--dsw-alias-bg-layer-1)");
|
|
366
597
|
return;
|
|
367
598
|
}
|
|
368
599
|
s.setProperty("--bg-glass-blur", `${settings.blur}px`);
|
|
369
|
-
s.setProperty("--bg-glass-saturate", String(
|
|
600
|
+
s.setProperty("--bg-glass-saturate", String(glassSaturate(settings.blur)));
|
|
601
|
+
s.setProperty("--bg-glass-brightness", glassExposure(inDark).brightness);
|
|
370
602
|
s.setProperty("--bg-preview-glass", glassSurfaceAlphas(settings.panelOpacity, inDark).bubble);
|
|
371
603
|
}
|
|
372
604
|
/** Remove every preview variable from an element (restores CSS defaults). */
|
|
@@ -400,7 +632,16 @@ window.__ModuleLoader__.load({
|
|
|
400
632
|
"background.contain": "完整",
|
|
401
633
|
"background.clear": "清除背景",
|
|
402
634
|
"background.uploadFailed": "上传失败,请重试",
|
|
403
|
-
"background.saveFailed": "保存失败,请重试"
|
|
635
|
+
"background.saveFailed": "保存失败,请重试",
|
|
636
|
+
"background.timeline": "会话时间线",
|
|
637
|
+
"background.timelineHint": "右侧消息导航轨,悬停展开问题列表(DeepSeek 官网风格)",
|
|
638
|
+
"timeline.railLabel": "对话时间线",
|
|
639
|
+
"timeline.roleUser": "用户",
|
|
640
|
+
"timeline.noText": "(无文本内容)",
|
|
641
|
+
"timeline.marksOnly": "★ 只看标记",
|
|
642
|
+
"timeline.mark": "标记为重点",
|
|
643
|
+
"timeline.unmark": "取消重点标记",
|
|
644
|
+
"timeline.noMarks": "还没有标记的重点"
|
|
404
645
|
};
|
|
405
646
|
/** English copy. */
|
|
406
647
|
const en = {
|
|
@@ -427,7 +668,16 @@ window.__ModuleLoader__.load({
|
|
|
427
668
|
"background.contain": "Contain",
|
|
428
669
|
"background.clear": "Clear background",
|
|
429
670
|
"background.uploadFailed": "Upload failed, try again",
|
|
430
|
-
"background.saveFailed": "Save failed, try again"
|
|
671
|
+
"background.saveFailed": "Save failed, try again",
|
|
672
|
+
"background.timeline": "Conversation timeline",
|
|
673
|
+
"background.timelineHint": "Message rail at the right edge; hover to expand the question list (DeepSeek web style)",
|
|
674
|
+
"timeline.railLabel": "Chat timeline",
|
|
675
|
+
"timeline.roleUser": "User",
|
|
676
|
+
"timeline.noText": "(no text)",
|
|
677
|
+
"timeline.marksOnly": "★ Marked only",
|
|
678
|
+
"timeline.mark": "Mark as key point",
|
|
679
|
+
"timeline.unmark": "Unmark key point",
|
|
680
|
+
"timeline.noMarks": "No marked key points"
|
|
431
681
|
};
|
|
432
682
|
//#endregion
|
|
433
683
|
//#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
@@ -549,7 +799,7 @@ window.__ModuleLoader__.load({
|
|
|
549
799
|
const settingsClient = new SettingsClient();
|
|
550
800
|
//#endregion
|
|
551
801
|
//#region \0dsh-css:D:\Project\deepseek-harness-background\src\client\SettingsRow.module.css.mjs
|
|
552
|
-
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;height:132px;position:relative;overflow:hidden}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.
|
|
802
|
+
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;height:132px;position:relative;overflow:hidden}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.42)) brightness(var(--bg-glass-brightness,1));border:1px solid #ffffff38;border-radius:10px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:16px;left:16px;box-shadow:inset 0 1px #ffffff4d}.-ozqdq_previewGlassText{color:#fff;opacity:.8;text-shadow:0 1px 2px #00000059;white-space:nowrap;font-size:11px;line-height:1}.-ozqdq_previewEmpty{color:var(--dsw-alias-label-tertiary);justify-content:center;align-items:center;font-size:12px;line-height:1.5;display:flex;position:absolute;inset:0}.-ozqdq_section{flex-direction:column;gap:8px;display:flex}.-ozqdq_sectionLabel{color:var(--dsw-alias-label-tertiary);letter-spacing:.01em;font-size:12px;font-weight:500;line-height:18px}.-ozqdq_sourceRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.-ozqdq_urlRow{align-items:center;gap:8px;display:flex}.-ozqdq_urlInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 10px;font-size:13px;line-height:1.5}.-ozqdq_urlInput::placeholder{color:var(--dsw-alias-label-dimmed)}.-ozqdq_urlInput:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.-ozqdq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.-ozqdq_enabledBadge{white-space:nowrap;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}.-ozqdq_btn{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;transition:background-color .15s,border-color .15s,color .15s;display:inline-flex}.-ozqdq_btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.-ozqdq_btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.-ozqdq_btn:disabled{opacity:.45;cursor:default}.-ozqdq_btnPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.-ozqdq_btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.-ozqdq_btnDanger{color:var(--dsw-alias-label-error)}.-ozqdq_btnDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.-ozqdq_sliderRow{align-items:center;gap:10px;display:flex}.-ozqdq_sliderLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_slider{-webkit-appearance:none;appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.-ozqdq_slider::-webkit-slider-runnable-track{background:linear-gradient(90deg, var(--dsw-alias-brand-primary) var(--bg-fill,0%), var(--dsw-alias-border-l3) var(--bg-fill,0%));border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-track{background:var(--dsw-alias-border-l3);border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-progress{background:var(--dsw-alias-brand-primary);border-radius:2px;height:4px}.-ozqdq_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%;margin-top:-5px}.-ozqdq_slider::-moz-range-thumb{border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%}.-ozqdq_slider:hover::-webkit-slider-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:hover::-moz-range-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:active::-webkit-slider-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:active::-moz-range-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:focus-visible{outline:none}.-ozqdq_slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_slider:focus-visible::-moz-range-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_sliderValue{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);flex:0 0 52px;font-size:13px;line-height:1.5}.-ozqdq_segRow{align-items:center;gap:10px;display:flex}.-ozqdq_segLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_segGroup{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex:1;gap:2px;min-width:0;padding:2px;display:inline-flex}.-ozqdq_segBtn{height:24px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:7px;flex:1;padding:0 16px;font-size:12px;line-height:24px}.-ozqdq_segBtn:hover:not(.-ozqdq_segActive){color:var(--dsw-alias-label-primary)}.-ozqdq_segActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-weight:500}.-ozqdq_error{color:var(--dsw-alias-label-error);font-size:12px;line-height:1.5}.-ozqdq_clearRow{align-items:center;gap:10px;display:flex}.-ozqdq_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:11px;flex:none;width:40px;height:22px;padding:0;transition:background-color .15s;position:relative}.-ozqdq_switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}.-ozqdq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_switchKnob{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;position:absolute;top:3px;left:3px;box-shadow:0 1px 3px #00000047}.-ozqdq_switch[aria-checked=true] .-ozqdq_switchKnob{transform:translate(18px)}@media (prefers-reduced-motion:reduce){.-ozqdq_btn,.-ozqdq_switch,.-ozqdq_switchKnob{transition:none}}";
|
|
553
803
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
554
804
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
555
805
|
const tag = document.createElement("style");
|
|
@@ -559,39 +809,41 @@ window.__ModuleLoader__.load({
|
|
|
559
809
|
document.head.appendChild(tag);
|
|
560
810
|
}
|
|
561
811
|
var SettingsRow_module_css_default = {
|
|
812
|
+
"sourceRow": "-ozqdq_sourceRow",
|
|
813
|
+
"previewScrim": "-ozqdq_previewScrim",
|
|
814
|
+
"segBtn": "-ozqdq_segBtn",
|
|
815
|
+
"header": "-ozqdq_header",
|
|
816
|
+
"clearRow": "-ozqdq_clearRow",
|
|
817
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
818
|
+
"switch": "-ozqdq_switch",
|
|
819
|
+
"segGroup": "-ozqdq_segGroup",
|
|
562
820
|
"segActive": "-ozqdq_segActive",
|
|
563
|
-
"
|
|
564
|
-
"
|
|
821
|
+
"segRow": "-ozqdq_segRow",
|
|
822
|
+
"btn": "-ozqdq_btn",
|
|
565
823
|
"previewEmpty": "-ozqdq_previewEmpty",
|
|
824
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
566
825
|
"row": "-ozqdq_row",
|
|
567
|
-
"urlRow": "-ozqdq_urlRow",
|
|
568
|
-
"slider": "-ozqdq_slider",
|
|
569
|
-
"sliderValue": "-ozqdq_sliderValue",
|
|
570
|
-
"clearRow": "-ozqdq_clearRow",
|
|
571
|
-
"preview": "-ozqdq_preview",
|
|
572
|
-
"clearBtn": "-ozqdq_clearBtn",
|
|
573
|
-
"btn": "-ozqdq_btn",
|
|
574
|
-
"segLabel": "-ozqdq_segLabel",
|
|
575
|
-
"previewGlassText": "-ozqdq_previewGlassText",
|
|
576
|
-
"btnPrimary": "-ozqdq_btnPrimary",
|
|
577
|
-
"header": "-ozqdq_header",
|
|
578
|
-
"title": "-ozqdq_title",
|
|
579
|
-
"previewScrim": "-ozqdq_previewScrim",
|
|
580
|
-
"sectionLabel": "-ozqdq_sectionLabel",
|
|
581
|
-
"enabledBadge": "-ozqdq_enabledBadge",
|
|
582
826
|
"titleRow": "-ozqdq_titleRow",
|
|
583
|
-
"
|
|
827
|
+
"btnDanger": "-ozqdq_btnDanger",
|
|
584
828
|
"desc": "-ozqdq_desc",
|
|
585
829
|
"sliderRow": "-ozqdq_sliderRow",
|
|
586
|
-
"
|
|
830
|
+
"segLabel": "-ozqdq_segLabel",
|
|
831
|
+
"title": "-ozqdq_title",
|
|
832
|
+
"urlRow": "-ozqdq_urlRow",
|
|
833
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
834
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
587
835
|
"hint": "-ozqdq_hint",
|
|
588
|
-
"
|
|
589
|
-
"segGroup": "-ozqdq_segGroup",
|
|
836
|
+
"slider": "-ozqdq_slider",
|
|
590
837
|
"urlInput": "-ozqdq_urlInput",
|
|
591
|
-
"
|
|
592
|
-
"
|
|
593
|
-
"
|
|
594
|
-
"
|
|
838
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
839
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
840
|
+
"error": "-ozqdq_error",
|
|
841
|
+
"previewImg": "-ozqdq_previewImg",
|
|
842
|
+
"sectionLabel": "-ozqdq_sectionLabel",
|
|
843
|
+
"section": "-ozqdq_section",
|
|
844
|
+
"preview": "-ozqdq_preview",
|
|
845
|
+
"sliderValue": "-ozqdq_sliderValue",
|
|
846
|
+
"previewGlassText": "-ozqdq_previewGlassText"
|
|
595
847
|
};
|
|
596
848
|
//#endregion
|
|
597
849
|
//#region src/client/SettingsRow.tsx
|
|
@@ -623,7 +875,8 @@ window.__ModuleLoader__.load({
|
|
|
623
875
|
panelOpacity: DEFAULT_PANEL_OPACITY,
|
|
624
876
|
blur: 16,
|
|
625
877
|
wallpaperBlur: 0,
|
|
626
|
-
fit: DEFAULT_FIT
|
|
878
|
+
fit: DEFAULT_FIT,
|
|
879
|
+
timeline: true
|
|
627
880
|
};
|
|
628
881
|
/** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so
|
|
629
882
|
* the two blur sliders feel smooth near small values. Bounds come from the
|
|
@@ -671,7 +924,7 @@ window.__ModuleLoader__.load({
|
|
|
671
924
|
}
|
|
672
925
|
/** Field-wise equality for distinguishing our own saves from external changes. */
|
|
673
926
|
function sameSettings(a, b) {
|
|
674
|
-
return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit;
|
|
927
|
+
return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit && a.timeline === b.timeline;
|
|
675
928
|
}
|
|
676
929
|
/**
|
|
677
930
|
* A stepped range control. While dragging it re-renders only itself: the value
|
|
@@ -909,6 +1162,16 @@ window.__ModuleLoader__.load({
|
|
|
909
1162
|
paintLive(next);
|
|
910
1163
|
saveNow(next);
|
|
911
1164
|
};
|
|
1165
|
+
const handleTimelineToggle = () => {
|
|
1166
|
+
markEditing();
|
|
1167
|
+
const next = {
|
|
1168
|
+
...draftRef.current,
|
|
1169
|
+
timeline: !draftRef.current.timeline
|
|
1170
|
+
};
|
|
1171
|
+
draftRef.current = next;
|
|
1172
|
+
setDraft(next);
|
|
1173
|
+
saveNow(next);
|
|
1174
|
+
};
|
|
912
1175
|
const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url;
|
|
913
1176
|
const hasImage = sourceUrl !== "";
|
|
914
1177
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1071,6 +1334,29 @@ window.__ModuleLoader__.load({
|
|
|
1071
1334
|
children: t(mode === "cover" ? "background.cover" : "background.contain")
|
|
1072
1335
|
}, mode))
|
|
1073
1336
|
})]
|
|
1337
|
+
}),
|
|
1338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1339
|
+
className: SettingsRow_module_css_default.segRow,
|
|
1340
|
+
children: [
|
|
1341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1342
|
+
className: SettingsRow_module_css_default.segLabel,
|
|
1343
|
+
children: t("background.timeline")
|
|
1344
|
+
}),
|
|
1345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1346
|
+
type: "button",
|
|
1347
|
+
role: "switch",
|
|
1348
|
+
"aria-checked": draft.timeline,
|
|
1349
|
+
"aria-label": t("background.timeline"),
|
|
1350
|
+
title: t("background.timelineHint"),
|
|
1351
|
+
className: SettingsRow_module_css_default.switch,
|
|
1352
|
+
onClick: handleTimelineToggle,
|
|
1353
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsRow_module_css_default.switchKnob })
|
|
1354
|
+
}),
|
|
1355
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1356
|
+
className: SettingsRow_module_css_default.hint,
|
|
1357
|
+
children: t("background.timelineHint")
|
|
1358
|
+
})
|
|
1359
|
+
]
|
|
1074
1360
|
})
|
|
1075
1361
|
]
|
|
1076
1362
|
}),
|
|
@@ -1092,17 +1378,862 @@ window.__ModuleLoader__.load({
|
|
|
1092
1378
|
});
|
|
1093
1379
|
}
|
|
1094
1380
|
//#endregion
|
|
1381
|
+
//#region src/client/timeline-css.ts
|
|
1382
|
+
/**
|
|
1383
|
+
* Timeline CSS — the conversation scroll-navigation rail (DeepSeek web
|
|
1384
|
+
* ScrollNav structure, rebuilt on this plugin's frosted-glass system).
|
|
1385
|
+
*
|
|
1386
|
+
* Structure and metrics mirror the official chat.deepseek.com stylesheet
|
|
1387
|
+
* (module map extracted from the shipped bundle; v0.1.4 of the reference port
|
|
1388
|
+
* dsh-chat-timeline widens the panel to 260px and adds bookmarks):
|
|
1389
|
+
* nav 34px-wide rail, fixed right edge, vertically centered; collapsed
|
|
1390
|
+
* blurred capsule (height − 8px); expanding panel (max 260px, radius 16)
|
|
1391
|
+
* whose scroll area holds 30px rows (tick indicator + fading-in title);
|
|
1392
|
+
* 32px gradient fade veils top/bottom shown while that side clips.
|
|
1393
|
+
*
|
|
1394
|
+
* Glass integration: the official chat.deepseek.com paints (light
|
|
1395
|
+
* rgba(255,255,255,.8)/.94, dark rgba(21,21,23,.6)/rgba(28,28,32,.95),
|
|
1396
|
+
* 5px collapsed / 16px expanded blur) remain as the NO-GLASS fallback.
|
|
1397
|
+
* While the plugin's glass system is on (`body[data-dsh-bg-glass]`), the
|
|
1398
|
+
* rail joins the SAME unified recipe as the composer card and bubbles:
|
|
1399
|
+
* fill = the painter's `--dsw-specific-input-major` token, blur/saturate/
|
|
1400
|
+
* brightness = the shared `--bg-glass-*` variables, i.e. the glass-blur
|
|
1401
|
+
* slider. Class prefix `dsbt-` deliberately avoids every
|
|
1402
|
+
* `[class*="_xxx"]` anchor of the plugin's glass sheet (underscores only).
|
|
1403
|
+
*/
|
|
1404
|
+
/** Unique id stamped on the injected style element (dedup key). */
|
|
1405
|
+
const TIMELINE_CSS_TAG = "deepseek-harness-background/timeline";
|
|
1406
|
+
/** Inject the timeline stylesheet once. Idempotent (dedup key on the tag). */
|
|
1407
|
+
function injectTimelineCss() {
|
|
1408
|
+
if (typeof document === "undefined") return;
|
|
1409
|
+
if (document.querySelector(`style[data-plugin-css="deepseek-harness-background/timeline"]`)) return;
|
|
1410
|
+
const tag = document.createElement("style");
|
|
1411
|
+
tag.dataset.plugin = "deepseek-harness-background";
|
|
1412
|
+
tag.dataset.pluginCss = TIMELINE_CSS_TAG;
|
|
1413
|
+
tag.textContent = TIMELINE_CSS;
|
|
1414
|
+
document.head.appendChild(tag);
|
|
1415
|
+
}
|
|
1416
|
+
const TIMELINE_CSS = `
|
|
1417
|
+
.dsbt-nav {
|
|
1418
|
+
user-select: none; -webkit-user-select: none;
|
|
1419
|
+
z-index: 90; align-items: center; width: 34px;
|
|
1420
|
+
height: var(--dsbt-h, 300px);
|
|
1421
|
+
transition: right .2s ease, opacity .2s ease;
|
|
1422
|
+
display: flex; position: fixed; top: 50%; right: 16px;
|
|
1423
|
+
transform: translateY(-50%); pointer-events: auto;
|
|
1424
|
+
}
|
|
1425
|
+
@media (max-width: 767px) { .dsbt-nav { display: none; } }
|
|
1426
|
+
|
|
1427
|
+
/* Collapsed capsule — frosted strip behind the tick marks. Official paint:
|
|
1428
|
+
light rgba(255,255,255,.8), dark rgba(21,21,23,.6), 5px blur. */
|
|
1429
|
+
.dsbt-bg {
|
|
1430
|
+
background-color: rgba(255, 255, 255, .8);
|
|
1431
|
+
-webkit-backdrop-filter: blur(5px);
|
|
1432
|
+
backdrop-filter: blur(5px);
|
|
1433
|
+
box-shadow:
|
|
1434
|
+
inset 0 1px 0 rgba(255, 255, 255, .28),
|
|
1435
|
+
inset 0 0 0 .5px rgba(255, 255, 255, .1),
|
|
1436
|
+
0 6px 18px rgba(0, 0, 0, .07);
|
|
1437
|
+
border-radius: 16px; width: 34px;
|
|
1438
|
+
height: calc(var(--dsbt-h, 300px) - 8px); max-height: calc(var(--dsbt-h, 300px) - 8px);
|
|
1439
|
+
position: absolute; top: 50%; right: 0; transform: translateY(-50%);
|
|
1440
|
+
transition: opacity .2s ease; pointer-events: none;
|
|
1441
|
+
}
|
|
1442
|
+
body[data-ds-dark-theme] .dsbt-bg {
|
|
1443
|
+
background-color: rgba(21, 21, 23, .6);
|
|
1444
|
+
box-shadow:
|
|
1445
|
+
inset 0 1px 0 rgba(255, 255, 255, .09),
|
|
1446
|
+
inset 0 0 0 .5px rgba(255, 255, 255, .05),
|
|
1447
|
+
0 6px 18px rgba(0, 0, 0, .3);
|
|
1448
|
+
}
|
|
1449
|
+
.dsbt-bg.dsbt-bg-hide { opacity: 0; }
|
|
1450
|
+
|
|
1451
|
+
/* Expanding panel — exactly the capsule's box (identical height in both
|
|
1452
|
+
states), width animates 34px -> measured fit (max 260px). Official
|
|
1453
|
+
hover-state paint: white/black frosted glass at ~94% alpha, 16px blur. */
|
|
1454
|
+
.dsbt-wrap {
|
|
1455
|
+
width: 34px;
|
|
1456
|
+
height: calc(var(--dsbt-h, 300px) - 8px); max-height: calc(var(--dsbt-h, 300px) - 8px);
|
|
1457
|
+
border: 1px solid transparent; border-radius: 16px;
|
|
1458
|
+
flex-direction: column; align-items: stretch;
|
|
1459
|
+
transition: width .22s cubic-bezier(.4, 0, .2, 1), background-color .2s ease,
|
|
1460
|
+
box-shadow .2s ease, border-color .2s ease;
|
|
1461
|
+
display: flex; position: absolute; top: 50%; right: 0;
|
|
1462
|
+
transform: translateY(-50%);
|
|
1463
|
+
overflow: hidden; box-sizing: border-box; background: transparent;
|
|
1464
|
+
pointer-events: none;
|
|
1465
|
+
}
|
|
1466
|
+
.dsbt-wrap.dsbt-show {
|
|
1467
|
+
pointer-events: auto;
|
|
1468
|
+
width: min(var(--dsbt-w, 260px), 260px);
|
|
1469
|
+
background-color: rgba(255, 255, 255, .94);
|
|
1470
|
+
-webkit-backdrop-filter: blur(16px);
|
|
1471
|
+
backdrop-filter: blur(16px);
|
|
1472
|
+
border-color: rgba(0, 0, 0, .06);
|
|
1473
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, .08), 0 2px 8px rgba(0, 0, 0, .04);
|
|
1474
|
+
}
|
|
1475
|
+
body[data-ds-dark-theme] .dsbt-wrap.dsbt-show {
|
|
1476
|
+
background-color: rgba(28, 28, 32, .95);
|
|
1477
|
+
border-color: rgba(255, 255, 255, .08);
|
|
1478
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .25);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/* Content fade veils — the official ::before/::after pair rebuilt as
|
|
1482
|
+
elements: a bar of the panel's OWN fill color masked to nothing over
|
|
1483
|
+
32px (solid -> mask == gradient-to-transparent, but works with the
|
|
1484
|
+
translucent fill). Shown only while that side actually clips. */
|
|
1485
|
+
.dsbt-fade {
|
|
1486
|
+
z-index: 2; pointer-events: none; opacity: 0;
|
|
1487
|
+
background-color: rgba(255, 255, 255, .94);
|
|
1488
|
+
-webkit-mask-image: linear-gradient(#000 20.19%, transparent 100%);
|
|
1489
|
+
mask-image: linear-gradient(#000 20.19%, transparent 100%);
|
|
1490
|
+
width: 100%; height: 32px; transition: opacity .2s ease;
|
|
1491
|
+
position: absolute; left: 0; top: 0;
|
|
1492
|
+
}
|
|
1493
|
+
body[data-ds-dark-theme] .dsbt-fade { background-color: rgba(28, 28, 32, .95); }
|
|
1494
|
+
.dsbt-fade.dsbt-fade-bot { top: auto; bottom: 0; transform: rotate(180deg); }
|
|
1495
|
+
.dsbt-fade.dsbt-fade-on { opacity: 1; }
|
|
1496
|
+
|
|
1497
|
+
/* Filter bar — "marked only" toggle pinned above the scroll area. */
|
|
1498
|
+
.dsbt-filterbar {
|
|
1499
|
+
padding: 8px 12px 4px; display: flex; justify-content: flex-end;
|
|
1500
|
+
align-items: center; border-bottom: 1px solid rgba(0, 0, 0, .05);
|
|
1501
|
+
box-sizing: border-box; flex: none; transition: opacity .2s ease;
|
|
1502
|
+
}
|
|
1503
|
+
.dsbt-wrap:not(.dsbt-show) .dsbt-filterbar { opacity: 0; }
|
|
1504
|
+
body[data-ds-dark-theme] .dsbt-filterbar { border-bottom-color: rgba(255, 255, 255, .06); }
|
|
1505
|
+
.dsbt-filterbtn {
|
|
1506
|
+
font-size: 11px; line-height: 16px; padding: 2px 8px; border-radius: 10px;
|
|
1507
|
+
border: 1px solid rgba(0, 0, 0, .1); background: rgba(0, 0, 0, .03);
|
|
1508
|
+
color: rgba(0, 0, 0, .65); cursor: pointer; transition: all .15s ease;
|
|
1509
|
+
display: inline-flex; align-items: center; gap: 3px; font-family: inherit;
|
|
1510
|
+
}
|
|
1511
|
+
.dsbt-filterbtn:hover { background: rgba(0, 0, 0, .07); color: rgba(0, 0, 0, .9); }
|
|
1512
|
+
.dsbt-filterbtn.dsbt-filteron {
|
|
1513
|
+
background: rgba(245, 158, 11, .12); border-color: #f59e0b;
|
|
1514
|
+
color: #b45309; font-weight: 600;
|
|
1515
|
+
}
|
|
1516
|
+
body[data-ds-dark-theme] .dsbt-filterbtn {
|
|
1517
|
+
border-color: rgba(255, 255, 255, .12); background: rgba(255, 255, 255, .05);
|
|
1518
|
+
color: rgba(255, 255, 255, .7);
|
|
1519
|
+
}
|
|
1520
|
+
body[data-ds-dark-theme] .dsbt-filterbtn:hover { background: rgba(255, 255, 255, .1); color: rgba(255, 255, 255, .95); }
|
|
1521
|
+
body[data-ds-dark-theme] .dsbt-filterbtn.dsbt-filteron {
|
|
1522
|
+
background: rgba(251, 191, 36, .18); border-color: #fbbf24; color: #fbbf24;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/* Scroll area — rows hug the right edge; scrolls only when overfull. */
|
|
1526
|
+
.dsbt-page {
|
|
1527
|
+
padding: 15px 0 15px 24px; box-sizing: border-box;
|
|
1528
|
+
overscroll-behavior: contain; flex: 1 1 auto; min-height: 0;
|
|
1529
|
+
flex-direction: column; align-items: flex-end; display: flex;
|
|
1530
|
+
position: relative; width: 100%; overflow: hidden;
|
|
1531
|
+
}
|
|
1532
|
+
.dsbt-wrap.dsbt-show .dsbt-page {
|
|
1533
|
+
overflow-y: auto; overflow-x: hidden;
|
|
1534
|
+
scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, .15) transparent;
|
|
1535
|
+
}
|
|
1536
|
+
body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-page { scrollbar-color: rgba(255, 255, 255, .25) transparent; }
|
|
1537
|
+
.dsbt-page::-webkit-scrollbar { width: 4px; }
|
|
1538
|
+
.dsbt-page::-webkit-scrollbar-track { background: transparent; }
|
|
1539
|
+
.dsbt-page::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, .15); border-radius: 4px; }
|
|
1540
|
+
body[data-ds-dark-theme] .dsbt-page::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .25); }
|
|
1541
|
+
|
|
1542
|
+
/* Empty state (filtered list with no marks). */
|
|
1543
|
+
.dsbt-empty {
|
|
1544
|
+
font-size: 12px; line-height: 18px; padding: 16px 12px;
|
|
1545
|
+
color: rgba(0, 0, 0, .45); text-align: center; width: 100%; box-sizing: border-box;
|
|
1546
|
+
}
|
|
1547
|
+
body[data-ds-dark-theme] .dsbt-empty { color: rgba(255, 255, 255, .45); }
|
|
1548
|
+
|
|
1549
|
+
/* One message row: 30px, star bookmark + tick indicator at the right,
|
|
1550
|
+
title fading in. */
|
|
1551
|
+
.dsbt-item {
|
|
1552
|
+
cursor: pointer; height: 30px; min-height: 30px; flex-shrink: 0;
|
|
1553
|
+
justify-content: flex-end; align-items: center;
|
|
1554
|
+
width: calc(100% - 4px); margin-right: 4px; line-height: 20px;
|
|
1555
|
+
display: flex; background: none; border: none; font: inherit;
|
|
1556
|
+
text-align: right; box-sizing: border-box; padding: 0;
|
|
1557
|
+
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .65));
|
|
1558
|
+
transition: color .15s ease;
|
|
1559
|
+
}
|
|
1560
|
+
.dsbt-item:hover { color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .95)); }
|
|
1561
|
+
body[data-ds-dark-theme] .dsbt-item { color: rgba(255, 255, 255, .65); }
|
|
1562
|
+
body[data-ds-dark-theme] .dsbt-item:hover { color: rgba(255, 255, 255, .95); }
|
|
1563
|
+
.dsbt-title {
|
|
1564
|
+
font-size: 13px; line-height: 20px; flex: 1 1 auto; min-width: 0;
|
|
1565
|
+
text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
|
|
1566
|
+
margin-right: 6px; text-align: right; color: inherit;
|
|
1567
|
+
opacity: 0; transition: opacity .12s ease, color .15s ease;
|
|
1568
|
+
}
|
|
1569
|
+
.dsbt-show .dsbt-title { opacity: 1; }
|
|
1570
|
+
|
|
1571
|
+
/* Star bookmark — appears on row hover while the panel is expanded; gold
|
|
1572
|
+
when marked. Keyboard-operable (span[role=button]). */
|
|
1573
|
+
.dsbt-star {
|
|
1574
|
+
font-size: 13px; line-height: 16px; width: 18px; height: 18px;
|
|
1575
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1576
|
+
opacity: 0; cursor: pointer; transition: all .15s ease; margin-right: 6px;
|
|
1577
|
+
flex-shrink: 0; color: rgba(0, 0, 0, .25); border-radius: 4px; user-select: none;
|
|
1578
|
+
-webkit-user-select: none;
|
|
1579
|
+
}
|
|
1580
|
+
.dsbt-wrap.dsbt-show .dsbt-item:hover .dsbt-star { opacity: .75; }
|
|
1581
|
+
.dsbt-wrap.dsbt-show .dsbt-star:hover { opacity: 1; transform: scale(1.2); color: #d97706; }
|
|
1582
|
+
.dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #f59e0b; }
|
|
1583
|
+
body[data-ds-dark-theme] .dsbt-star { color: rgba(255, 255, 255, .25); }
|
|
1584
|
+
body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-star:hover { color: #fde047; }
|
|
1585
|
+
body[data-ds-dark-theme] .dsbt-wrap.dsbt-show .dsbt-star.dsbt-staron { opacity: 1; color: #fbbf24; }
|
|
1586
|
+
|
|
1587
|
+
.dsbt-ind { flex-shrink: 0; justify-content: center; align-items: center; width: 16px; height: 20px; display: flex; }
|
|
1588
|
+
.dsbt-line {
|
|
1589
|
+
background-color: var(--dsw-alias-border-l4, rgba(0, 0, 0, .16));
|
|
1590
|
+
border-radius: 4px; flex-shrink: 0; width: 8px; height: 2px;
|
|
1591
|
+
transition: background-color .2s ease, transform .2s ease, width .2s ease, height .2s ease;
|
|
1592
|
+
}
|
|
1593
|
+
.dsbt-item:hover .dsbt-line { background-color: var(--dsw-alias-label-primary, rgba(0, 0, 0, .9)); }
|
|
1594
|
+
.dsbt-item.dsbt-active { color: var(--dsw-alias-state-business-primary, #4d6bfe); }
|
|
1595
|
+
.dsbt-item.dsbt-active .dsbt-title { color: var(--dsw-alias-state-business-primary, #4d6bfe); font-weight: 500; }
|
|
1596
|
+
.dsbt-item.dsbt-active .dsbt-line {
|
|
1597
|
+
background-color: var(--dsw-alias-state-business-primary, #4d6bfe);
|
|
1598
|
+
transform-origin: 50%; transform: scale(1.5);
|
|
1599
|
+
}
|
|
1600
|
+
body[data-ds-dark-theme] .dsbt-item.dsbt-active { color: var(--dsw-alias-state-business-primary, #4d6bfe); }
|
|
1601
|
+
body[data-ds-dark-theme] .dsbt-line { background-color: rgba(255, 255, 255, .2); }
|
|
1602
|
+
body[data-ds-dark-theme] .dsbt-item:hover .dsbt-line { background-color: rgba(255, 255, 255, .9); }
|
|
1603
|
+
|
|
1604
|
+
/* Marked (key-point) rows: a wider golden tick in both themes; the active
|
|
1605
|
+
state keeps its brand-blue scale so reading position always wins. */
|
|
1606
|
+
.dsbt-item.dsbt-marked .dsbt-line { background-color: #d97706; width: 10px; height: 2.5px; }
|
|
1607
|
+
.dsbt-item.dsbt-marked:hover .dsbt-line { background-color: #b45309; }
|
|
1608
|
+
.dsbt-item.dsbt-marked.dsbt-active .dsbt-line {
|
|
1609
|
+
background-color: var(--dsw-alias-state-business-primary, #4d6bfe);
|
|
1610
|
+
transform-origin: 50%; transform: scale(1.5);
|
|
1611
|
+
}
|
|
1612
|
+
body[data-ds-dark-theme] .dsbt-item.dsbt-marked .dsbt-line { background-color: #fbbf24; width: 10px; height: 2.5px; }
|
|
1613
|
+
body[data-ds-dark-theme] .dsbt-item.dsbt-marked:hover .dsbt-line { background-color: #fde047; }
|
|
1614
|
+
|
|
1615
|
+
/* ---- Unified glass integration -----------------------------------------
|
|
1616
|
+
Glass system on (wallpaper active + panelOpacity < 1): the rail joins
|
|
1617
|
+
the composer/bubble recipe — the same translucent fill token and the
|
|
1618
|
+
same blur/saturate/brightness chain (the glass-blur slider drives it,
|
|
1619
|
+
no fixed 5/16px). Placed after the dark-theme rules so equal-specificity
|
|
1620
|
+
overrides win in both schemes. */
|
|
1621
|
+
body[data-dsh-bg-glass] .dsbt-bg,
|
|
1622
|
+
body[data-dsh-bg-glass] .dsbt-wrap.dsbt-show {
|
|
1623
|
+
background-color: var(--dsw-specific-input-major);
|
|
1624
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
1625
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
1626
|
+
}
|
|
1627
|
+
body[data-dsh-bg-glass] .dsbt-fade {
|
|
1628
|
+
background-color: var(--dsw-specific-input-major);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1632
|
+
.dsbt-nav, .dsbt-bg, .dsbt-wrap, .dsbt-fade, .dsbt-title, .dsbt-line, .dsbt-star, .dsbt-filterbtn, .dsbt-filterbar { transition: none; }
|
|
1633
|
+
}
|
|
1634
|
+
`;
|
|
1635
|
+
//#endregion
|
|
1636
|
+
//#region src/client/timeline.tsx
|
|
1637
|
+
/**
|
|
1638
|
+
* Conversation timeline rail — DeepSeek web ScrollNav rebuilt on this
|
|
1639
|
+
* plugin's frosted-glass system (styles: ./timeline-css.ts).
|
|
1640
|
+
*
|
|
1641
|
+
* Collapsed: a fixed 34px-wide frosted capsule at the right edge, vertically
|
|
1642
|
+
* centered, one tick per user message (active tick scaled + brand blue,
|
|
1643
|
+
* key-point bookmarks golden). Hovering expands the SAME box into a frosted
|
|
1644
|
+
* panel listing every user question (title fades in; >8 messages widen to
|
|
1645
|
+
* 260px, otherwise the width fits the longest title). Both states share one
|
|
1646
|
+
* computed outer height, so expanding never jumps, and 32px fade veils mask
|
|
1647
|
+
* the clipped edge on each side while that side actually scrolls.
|
|
1648
|
+
*
|
|
1649
|
+
* Enhancements ported from dsh-chat-timeline v0.1.4 (MIT):
|
|
1650
|
+
* - Key-point bookmarks: star a question in the expanded panel, persisted per
|
|
1651
|
+
* session in localStorage; marked rows show a golden tick in the capsule
|
|
1652
|
+
* and a "marked only" filter narrows the list.
|
|
1653
|
+
* - Jump stabilization: the reading-position tracker freezes while a smooth
|
|
1654
|
+
* jump animates (cleared 150ms after the last scroll event, plus an 800ms
|
|
1655
|
+
* fallback), so the panel cannot jitter mid-jump.
|
|
1656
|
+
* - Rewind integration: messages withdrawn by a rewind command node are
|
|
1657
|
+
* filtered out of the rail.
|
|
1658
|
+
*
|
|
1659
|
+
* Data source (client-side only): loaded chat nodes -> background loadOlder
|
|
1660
|
+
* loop until complete. Clicking a row loads older history on demand, scrolls
|
|
1661
|
+
* the conversation to the target row, and keeps the rail's own active row
|
|
1662
|
+
* visible.
|
|
1663
|
+
*
|
|
1664
|
+
* The rail reads the shared settings transport: it renders unless
|
|
1665
|
+
* `timeline: false`. Its glass paints are self-contained official values
|
|
1666
|
+
* (see timeline-css.ts), independent of the plugin's token overrides.
|
|
1667
|
+
*/
|
|
1668
|
+
if (typeof document !== "undefined") injectTimelineCss();
|
|
1669
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1670
|
+
/** Extract preview text from a user message's ContentBlock list. */
|
|
1671
|
+
function userTextOf(content) {
|
|
1672
|
+
if (!Array.isArray(content)) return "";
|
|
1673
|
+
let out = "";
|
|
1674
|
+
for (const block of content) if (block !== null && typeof block === "object" && block.type === "text") {
|
|
1675
|
+
const text = block.text;
|
|
1676
|
+
if (typeof text === "string") out += text;
|
|
1677
|
+
}
|
|
1678
|
+
return out.trim().slice(0, 80);
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Stable bookmark key for one entry: the durable message key, falling back to
|
|
1682
|
+
* the anchor seq — so marks survive history reloads. Entries with neither are
|
|
1683
|
+
* not markable (empty key).
|
|
1684
|
+
* @param m - the entry (or any shaped object).
|
|
1685
|
+
*/
|
|
1686
|
+
function markKeyOf(m) {
|
|
1687
|
+
if (m === null || typeof m !== "object") return "";
|
|
1688
|
+
const rec = m;
|
|
1689
|
+
if (typeof rec.key === "string" && rec.key !== "") return `key:${rec.key}`;
|
|
1690
|
+
return typeof rec.seq === "number" ? `seq:${rec.seq}` : "";
|
|
1691
|
+
}
|
|
1692
|
+
/** localStorage key prefix for the per-session marked-key lists. */
|
|
1693
|
+
const MARKS_STORAGE_PREFIX = "dsbt_marks_";
|
|
1694
|
+
/**
|
|
1695
|
+
* Read the marked keys for one session from localStorage.
|
|
1696
|
+
* @param sessionId - the session the marks belong to.
|
|
1697
|
+
*/
|
|
1698
|
+
function readMarks(sessionId) {
|
|
1699
|
+
if (typeof window === "undefined" || !sessionId) return [];
|
|
1700
|
+
try {
|
|
1701
|
+
const raw = window.localStorage.getItem(MARKS_STORAGE_PREFIX + sessionId);
|
|
1702
|
+
if (!raw) return [];
|
|
1703
|
+
const parsed = JSON.parse(raw);
|
|
1704
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
1705
|
+
} catch {
|
|
1706
|
+
return [];
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Persist the marked keys for one session (removes the key when empty).
|
|
1711
|
+
* @param sessionId - the session the marks belong to.
|
|
1712
|
+
* @param marks - the marked-key list.
|
|
1713
|
+
*/
|
|
1714
|
+
function writeMarks(sessionId, marks) {
|
|
1715
|
+
if (typeof window === "undefined" || !sessionId) return;
|
|
1716
|
+
try {
|
|
1717
|
+
if (!Array.isArray(marks) || marks.length === 0) window.localStorage.removeItem(MARKS_STORAGE_PREFIX + sessionId);
|
|
1718
|
+
else window.localStorage.setItem(MARKS_STORAGE_PREFIX + sessionId, JSON.stringify(marks));
|
|
1719
|
+
} catch {}
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Extract the rewind target seq from a rewind outcome text.
|
|
1723
|
+
* @param text - the command outcome text.
|
|
1724
|
+
*/
|
|
1725
|
+
function rewindTargetOfOutcome(text) {
|
|
1726
|
+
if (typeof text !== "string") return void 0;
|
|
1727
|
+
const digits = (/rewound.*?(?:target\s+)?(\d+)/i.exec(text) ?? /#(\d+)/.exec(text))?.[1];
|
|
1728
|
+
if (digits === void 0) return void 0;
|
|
1729
|
+
const n = Number.parseInt(digits, 10);
|
|
1730
|
+
return Number.isFinite(n) ? n : void 0;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Is this rewind command a preview/dry-run (marks the target hidden without
|
|
1734
|
+
* an outcome)?
|
|
1735
|
+
* @param command - the command payload.
|
|
1736
|
+
*/
|
|
1737
|
+
function isRewindPreviewCommand(command) {
|
|
1738
|
+
if (command.args === null || typeof command.args !== "object") return false;
|
|
1739
|
+
const args = command.args;
|
|
1740
|
+
return args.preview === true || args.dryRun === true;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Collect the seq numbers a rewind hid: preview commands hide their target
|
|
1744
|
+
* directly; successful commands hide their own row plus every node anchored
|
|
1745
|
+
* inside the rewound span [target, sourceEventSeq].
|
|
1746
|
+
* @param chat - a chat snapshot (loosely typed on purpose).
|
|
1747
|
+
*/
|
|
1748
|
+
function hiddenSeqsOfChat(chat) {
|
|
1749
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
1750
|
+
if (chat === null || typeof chat !== "object") return hidden;
|
|
1751
|
+
const nodes = chat.nodes;
|
|
1752
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return hidden;
|
|
1753
|
+
const all = [...nodes.values()];
|
|
1754
|
+
const spans = [];
|
|
1755
|
+
for (const node of all) {
|
|
1756
|
+
if (node === null || typeof node !== "object") continue;
|
|
1757
|
+
if (node.kind !== "command") continue;
|
|
1758
|
+
const command = node.data;
|
|
1759
|
+
if (command === null || typeof command !== "object") continue;
|
|
1760
|
+
if (command.name !== "rewind") continue;
|
|
1761
|
+
if (isRewindPreviewCommand(command)) {
|
|
1762
|
+
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
const outcome = command.outcome;
|
|
1766
|
+
if (outcome === null || typeof outcome !== "object" || outcome.kind !== "success") continue;
|
|
1767
|
+
const marker = outcome.sourceEventSeq;
|
|
1768
|
+
if (typeof marker !== "number") continue;
|
|
1769
|
+
if (typeof command.seq === "number") hidden.add(command.seq);
|
|
1770
|
+
const target = rewindTargetOfOutcome(outcome.text);
|
|
1771
|
+
if (target !== void 0) spans.push({
|
|
1772
|
+
start: target,
|
|
1773
|
+
end: marker
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
if (spans.length === 0) return hidden;
|
|
1777
|
+
for (const node of all) {
|
|
1778
|
+
if (node === null || typeof node !== "object") continue;
|
|
1779
|
+
const anchor = typeof node.anchorSeq === "number" ? node.anchorSeq : void 0;
|
|
1780
|
+
if (anchor === void 0) continue;
|
|
1781
|
+
if (spans.some((span) => anchor >= span.start && anchor <= span.end)) hidden.add(anchor);
|
|
1782
|
+
}
|
|
1783
|
+
return hidden;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Enumerate user messages from the loaded chat-node snapshot (the client-side
|
|
1787
|
+
* collector; sorted by anchor sequence, rewind-hidden entries dropped).
|
|
1788
|
+
* @param snapshot - a session snapshot (loosely typed on purpose).
|
|
1789
|
+
*/
|
|
1790
|
+
function collectMessages(snapshot) {
|
|
1791
|
+
const chat = snapshot?.chat;
|
|
1792
|
+
const nodes = chat?.nodes;
|
|
1793
|
+
if (nodes === void 0 || typeof nodes.values !== "function") return [];
|
|
1794
|
+
const hidden = hiddenSeqsOfChat(chat);
|
|
1795
|
+
const out = [];
|
|
1796
|
+
for (const node of nodes.values()) {
|
|
1797
|
+
if (node === null || typeof node !== "object") continue;
|
|
1798
|
+
if (node.kind !== "user") continue;
|
|
1799
|
+
const data = node.data;
|
|
1800
|
+
if (data === null || typeof data !== "object" || typeof data.time !== "number" || !Array.isArray(data.content)) continue;
|
|
1801
|
+
const key = typeof node.key === "string" ? node.key : void 0;
|
|
1802
|
+
if (key === void 0) continue;
|
|
1803
|
+
const seq = typeof node.anchorSeq === "number" ? node.anchorSeq : 0;
|
|
1804
|
+
if (hidden.has(seq)) continue;
|
|
1805
|
+
out.push({
|
|
1806
|
+
seq,
|
|
1807
|
+
time: data.time,
|
|
1808
|
+
text: userTextOf(data.content),
|
|
1809
|
+
key
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
out.sort((a, b) => a.seq - b.seq);
|
|
1813
|
+
return out;
|
|
1814
|
+
}
|
|
1815
|
+
/** Resolve the DOM anchor key of one entry (direct key form only). */
|
|
1816
|
+
function resolveAnchorKey(m) {
|
|
1817
|
+
return typeof m.key === "string" && m.key !== "" ? m.key : void 0;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Outer rail height for a message count: rows + breathing room, clamped so
|
|
1821
|
+
* both states always share the exact same height (never jumps on expand).
|
|
1822
|
+
* @param count - number of user messages.
|
|
1823
|
+
*/
|
|
1824
|
+
function railHeightFor(count) {
|
|
1825
|
+
return Math.max(140, Math.min(300, count * 30 + 38));
|
|
1826
|
+
}
|
|
1827
|
+
/** Longest-title width estimate bounds. */
|
|
1828
|
+
const RAIL_MIN_WIDTH = 96;
|
|
1829
|
+
const RAIL_MAX_WIDTH = 260;
|
|
1830
|
+
/**
|
|
1831
|
+
* Measure the expanded-panel width for the given titles (canvas text metrics;
|
|
1832
|
+
* falls back to the max width when measurement is unavailable). Accounts for
|
|
1833
|
+
* the star column so a marked row never clips its tick.
|
|
1834
|
+
* @param texts - the message previews.
|
|
1835
|
+
*/
|
|
1836
|
+
function railWidthFor(texts) {
|
|
1837
|
+
if (texts.length > 8) return RAIL_MAX_WIDTH;
|
|
1838
|
+
let widest = 0;
|
|
1839
|
+
if (typeof document !== "undefined") {
|
|
1840
|
+
const ctx = document.createElement("canvas").getContext("2d");
|
|
1841
|
+
if (ctx) {
|
|
1842
|
+
ctx.font = "13px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif";
|
|
1843
|
+
for (const t of texts) {
|
|
1844
|
+
const w = ctx.measureText(t === "" ? "…" : t).width;
|
|
1845
|
+
if (w > widest) widest = w;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return Math.round(Math.max(RAIL_MIN_WIDTH, Math.min(RAIL_MAX_WIDTH, widest + 6 + 24 + 16 + 12)));
|
|
1850
|
+
}
|
|
1851
|
+
/** Ensure the target node is loaded, then scroll its row into view. */
|
|
1852
|
+
async function jumpToMessage(sessionsService, sessionId, key) {
|
|
1853
|
+
const session = sessionsService.binding(sessionId)?.session;
|
|
1854
|
+
if (session === void 0) return false;
|
|
1855
|
+
let guard = 0;
|
|
1856
|
+
while (guard++ < 120) {
|
|
1857
|
+
const snapshot = session.getSnapshot();
|
|
1858
|
+
if (snapshot?.chat?.nodes?.get(key) !== void 0) break;
|
|
1859
|
+
if (snapshot?.hasMore !== true) return false;
|
|
1860
|
+
if (snapshot.loadingOlder === true) {
|
|
1861
|
+
await delay(50);
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
await session.loadOlder();
|
|
1865
|
+
}
|
|
1866
|
+
const row = document.querySelector("[data-conversation-scroll]")?.querySelector(`[data-chat-anchor-key="${CSS.escape(key)}"]`) ?? null;
|
|
1867
|
+
if (row === null) return false;
|
|
1868
|
+
const reducedMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1869
|
+
row.scrollIntoView({
|
|
1870
|
+
behavior: reducedMotion ? "auto" : "smooth",
|
|
1871
|
+
block: "center"
|
|
1872
|
+
});
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Scroll only the rail itself so the active row stays visible (min distance).
|
|
1877
|
+
* offsetTop math instead of getBoundingClientRect: stable while the panel's
|
|
1878
|
+
* own smooth scroll animates (no feedback loop with live rects).
|
|
1879
|
+
*/
|
|
1880
|
+
function scrollTimelineItemIntoView(page, item) {
|
|
1881
|
+
const pageTop = page.scrollTop;
|
|
1882
|
+
const pageBottom = pageTop + page.clientHeight;
|
|
1883
|
+
const itemTop = item.offsetTop;
|
|
1884
|
+
const itemBottom = itemTop + item.offsetHeight;
|
|
1885
|
+
if (itemTop < pageTop) page.scrollTop = Math.max(0, itemTop - 10);
|
|
1886
|
+
else if (itemBottom > pageBottom) page.scrollTop = itemBottom - page.clientHeight + 10;
|
|
1887
|
+
}
|
|
1888
|
+
/** Is another timeline rail already mounted (e.g. dsh-chat-timeline)? */
|
|
1889
|
+
function otherRailPresent() {
|
|
1890
|
+
return document.querySelector(".dsct_nav") !== null;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* The timeline rail component. Portaled to body; registered into the
|
|
1894
|
+
* conversation input dock purely to bind per-session lifecycle.
|
|
1895
|
+
*/
|
|
1896
|
+
function TimelineRail(props) {
|
|
1897
|
+
const { sessionId, sessionsService, t } = props;
|
|
1898
|
+
const settings = react.default.useSyncExternalStore(settingsClient.subscribe, settingsClient.getSnapshot);
|
|
1899
|
+
const timelineUnknown = settings.status === "loading";
|
|
1900
|
+
const timelineDisabled = settings.value?.timeline === false;
|
|
1901
|
+
const session = sessionId !== void 0 && sessionsService !== void 0 ? sessionsService.binding(sessionId)?.session : void 0;
|
|
1902
|
+
const subscribeSession = react.default.useCallback((listener) => session === void 0 ? () => {} : session.subscribe(listener), [session]);
|
|
1903
|
+
const snapshotSession = react.default.useCallback(() => session === void 0 ? void 0 : session.getSnapshot(), [session]);
|
|
1904
|
+
const nodeSnapshot = react.default.useSyncExternalStore(subscribeSession, snapshotSession);
|
|
1905
|
+
const messages = react.default.useMemo(() => collectMessages(nodeSnapshot), [nodeSnapshot]);
|
|
1906
|
+
const [loadedAll, setLoadedAll] = react.default.useState(false);
|
|
1907
|
+
const [activeIndex, setActiveIndex] = react.default.useState(-1);
|
|
1908
|
+
const [show, setShow] = react.default.useState(false);
|
|
1909
|
+
const [rightOffset, setRightOffset] = react.default.useState(16);
|
|
1910
|
+
const [railWidth, setRailWidth] = react.default.useState(RAIL_MAX_WIDTH);
|
|
1911
|
+
const [fades, setFades] = react.default.useState({
|
|
1912
|
+
top: false,
|
|
1913
|
+
bottom: false
|
|
1914
|
+
});
|
|
1915
|
+
const [hidden, setHidden] = react.default.useState(() => otherRailPresent());
|
|
1916
|
+
const [marks, setMarks] = react.default.useState(() => readMarks(sessionId));
|
|
1917
|
+
const [marksOnly, setMarksOnly] = react.default.useState(false);
|
|
1918
|
+
const [isNarrow, setIsNarrow] = react.default.useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(max-width: 767px)").matches);
|
|
1919
|
+
const pageRef = react.default.useRef(null);
|
|
1920
|
+
const activeItemRef = react.default.useRef(null);
|
|
1921
|
+
const jumpPendingRef = react.default.useRef(false);
|
|
1922
|
+
const jumpFallbackRef = react.default.useRef(void 0);
|
|
1923
|
+
react.default.useEffect(() => {
|
|
1924
|
+
setMarks(readMarks(sessionId));
|
|
1925
|
+
setMarksOnly(false);
|
|
1926
|
+
}, [sessionId]);
|
|
1927
|
+
const markedSet = react.default.useMemo(() => new Set(marks), [marks]);
|
|
1928
|
+
const marksRef = react.default.useRef(marks);
|
|
1929
|
+
marksRef.current = marks;
|
|
1930
|
+
const toggleMark = react.default.useCallback((m) => {
|
|
1931
|
+
const k = markKeyOf(m);
|
|
1932
|
+
if (k === "") return;
|
|
1933
|
+
const prev = marksRef.current;
|
|
1934
|
+
const next = prev.includes(k) ? prev.filter((x) => x !== k) : [...prev, k];
|
|
1935
|
+
marksRef.current = next;
|
|
1936
|
+
setMarks(next);
|
|
1937
|
+
writeMarks(sessionId, next);
|
|
1938
|
+
}, [sessionId]);
|
|
1939
|
+
react.default.useEffect(() => {
|
|
1940
|
+
if (typeof window.matchMedia !== "function") return;
|
|
1941
|
+
const mql = window.matchMedia("(max-width: 767px)");
|
|
1942
|
+
const onChange = (e) => setIsNarrow(e.matches);
|
|
1943
|
+
mql.addEventListener("change", onChange);
|
|
1944
|
+
return () => mql.removeEventListener("change", onChange);
|
|
1945
|
+
}, []);
|
|
1946
|
+
react.default.useEffect(() => {
|
|
1947
|
+
if (session === void 0 || isNarrow || sessionId === void 0 || timelineUnknown || timelineDisabled || hidden) return;
|
|
1948
|
+
setLoadedAll(false);
|
|
1949
|
+
let cancelled = false;
|
|
1950
|
+
const run = async () => {
|
|
1951
|
+
let guard = 0;
|
|
1952
|
+
while (!cancelled && guard++ < 120) {
|
|
1953
|
+
const snap = session.getSnapshot();
|
|
1954
|
+
if (snap?.hasMore !== true) break;
|
|
1955
|
+
if (snap.loadingOlder === true) {
|
|
1956
|
+
await delay(50);
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
await session.loadOlder();
|
|
1960
|
+
}
|
|
1961
|
+
if (!cancelled) setLoadedAll(true);
|
|
1962
|
+
};
|
|
1963
|
+
run().catch(() => {
|
|
1964
|
+
if (!cancelled) setLoadedAll(true);
|
|
1965
|
+
});
|
|
1966
|
+
return () => {
|
|
1967
|
+
cancelled = true;
|
|
1968
|
+
};
|
|
1969
|
+
}, [
|
|
1970
|
+
sessionId,
|
|
1971
|
+
isNarrow,
|
|
1972
|
+
session,
|
|
1973
|
+
timelineUnknown,
|
|
1974
|
+
timelineDisabled,
|
|
1975
|
+
hidden
|
|
1976
|
+
]);
|
|
1977
|
+
react.default.useEffect(() => {
|
|
1978
|
+
if (!loadedAll && messages.length > 0) return;
|
|
1979
|
+
setRailWidth(railWidthFor(messages.map((m) => m.text)));
|
|
1980
|
+
}, [loadedAll, messages]);
|
|
1981
|
+
react.default.useEffect(() => {
|
|
1982
|
+
if (messages.length === 0) return;
|
|
1983
|
+
const indexByKey = /* @__PURE__ */ new Map();
|
|
1984
|
+
messages.forEach((m, i) => {
|
|
1985
|
+
const key = resolveAnchorKey(m);
|
|
1986
|
+
if (key !== void 0) indexByKey.set(key, i);
|
|
1987
|
+
});
|
|
1988
|
+
const updateActive = () => {
|
|
1989
|
+
if (jumpPendingRef.current) return;
|
|
1990
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
1991
|
+
if (sp === null) return;
|
|
1992
|
+
const rect = sp.getBoundingClientRect();
|
|
1993
|
+
if (rect.height === 0) return;
|
|
1994
|
+
const line = rect.top + rect.height * .4;
|
|
1995
|
+
const rows = sp.querySelectorAll("[data-chat-anchor-key]");
|
|
1996
|
+
let best = -1;
|
|
1997
|
+
let bestDist = Infinity;
|
|
1998
|
+
for (const row of rows) {
|
|
1999
|
+
const key = row.getAttribute("data-chat-anchor-key");
|
|
2000
|
+
if (key === null) continue;
|
|
2001
|
+
const idx = indexByKey.get(key) ?? -1;
|
|
2002
|
+
if (idx === -1) continue;
|
|
2003
|
+
const r = row.getBoundingClientRect();
|
|
2004
|
+
const dist = Math.abs(r.top + r.height / 2 - line);
|
|
2005
|
+
if (dist < bestDist) {
|
|
2006
|
+
bestDist = dist;
|
|
2007
|
+
best = idx;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
setActiveIndex(best);
|
|
2011
|
+
};
|
|
2012
|
+
updateActive();
|
|
2013
|
+
const el = document.querySelector("[data-conversation-scroll]");
|
|
2014
|
+
let timer;
|
|
2015
|
+
let settleTimer;
|
|
2016
|
+
const onScroll = () => {
|
|
2017
|
+
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2018
|
+
settleTimer = window.setTimeout(() => {
|
|
2019
|
+
settleTimer = void 0;
|
|
2020
|
+
if (jumpPendingRef.current) {
|
|
2021
|
+
jumpPendingRef.current = false;
|
|
2022
|
+
if (jumpFallbackRef.current !== void 0) {
|
|
2023
|
+
clearTimeout(jumpFallbackRef.current);
|
|
2024
|
+
jumpFallbackRef.current = void 0;
|
|
2025
|
+
}
|
|
2026
|
+
updateActive();
|
|
2027
|
+
}
|
|
2028
|
+
}, 150);
|
|
2029
|
+
if (timer !== void 0) return;
|
|
2030
|
+
timer = window.setTimeout(() => {
|
|
2031
|
+
timer = void 0;
|
|
2032
|
+
updateActive();
|
|
2033
|
+
}, 60);
|
|
2034
|
+
};
|
|
2035
|
+
el?.addEventListener("scroll", onScroll, { passive: true });
|
|
2036
|
+
const interval = window.setInterval(updateActive, 2e3);
|
|
2037
|
+
return () => {
|
|
2038
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2039
|
+
if (settleTimer !== void 0) clearTimeout(settleTimer);
|
|
2040
|
+
if (jumpFallbackRef.current !== void 0) {
|
|
2041
|
+
clearTimeout(jumpFallbackRef.current);
|
|
2042
|
+
jumpFallbackRef.current = void 0;
|
|
2043
|
+
}
|
|
2044
|
+
el?.removeEventListener("scroll", onScroll);
|
|
2045
|
+
clearInterval(interval);
|
|
2046
|
+
};
|
|
2047
|
+
}, [messages]);
|
|
2048
|
+
const syncFades = react.default.useCallback(() => {
|
|
2049
|
+
const page = pageRef.current;
|
|
2050
|
+
if (page === null) {
|
|
2051
|
+
setFades({
|
|
2052
|
+
top: false,
|
|
2053
|
+
bottom: false
|
|
2054
|
+
});
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
const canTop = page.scrollTop > 2;
|
|
2058
|
+
const canBot = page.scrollTop + page.clientHeight < page.scrollHeight - 2;
|
|
2059
|
+
setFades((prev) => prev.top === canTop && prev.bottom === canBot ? prev : {
|
|
2060
|
+
top: canTop,
|
|
2061
|
+
bottom: canBot
|
|
2062
|
+
});
|
|
2063
|
+
}, []);
|
|
2064
|
+
react.default.useEffect(syncFades, [
|
|
2065
|
+
syncFades,
|
|
2066
|
+
show,
|
|
2067
|
+
messages.length,
|
|
2068
|
+
loadedAll,
|
|
2069
|
+
marksOnly
|
|
2070
|
+
]);
|
|
2071
|
+
react.default.useLayoutEffect(() => {
|
|
2072
|
+
if (!show) return;
|
|
2073
|
+
const page = pageRef.current;
|
|
2074
|
+
const item = activeItemRef.current;
|
|
2075
|
+
if (page === null || item === null) return;
|
|
2076
|
+
scrollTimelineItemIntoView(page, item);
|
|
2077
|
+
syncFades();
|
|
2078
|
+
}, [
|
|
2079
|
+
activeIndex,
|
|
2080
|
+
messages.length,
|
|
2081
|
+
marksOnly,
|
|
2082
|
+
show,
|
|
2083
|
+
syncFades
|
|
2084
|
+
]);
|
|
2085
|
+
react.default.useEffect(() => {
|
|
2086
|
+
let raf = 0;
|
|
2087
|
+
const measure = () => {
|
|
2088
|
+
raf = 0;
|
|
2089
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
2090
|
+
if (sp === null) return;
|
|
2091
|
+
const rect = sp.getBoundingClientRect();
|
|
2092
|
+
if (rect.width === 0 && rect.height === 0) return;
|
|
2093
|
+
const next = Math.max(8, Math.round(window.innerWidth - rect.right + 12));
|
|
2094
|
+
setRightOffset((prev) => Math.abs(prev - next) > .5 ? next : prev);
|
|
2095
|
+
};
|
|
2096
|
+
const schedule = () => {
|
|
2097
|
+
if (raf === 0) raf = window.requestAnimationFrame(measure);
|
|
2098
|
+
};
|
|
2099
|
+
const sp = document.querySelector("[data-conversation-scroll]");
|
|
2100
|
+
const ro = typeof ResizeObserver === "function" && sp !== null ? new ResizeObserver(schedule) : null;
|
|
2101
|
+
if (ro !== null && sp !== null) ro.observe(sp);
|
|
2102
|
+
window.addEventListener("resize", schedule);
|
|
2103
|
+
const guard = window.setInterval(() => setHidden(otherRailPresent()), 2e3);
|
|
2104
|
+
measure();
|
|
2105
|
+
return () => {
|
|
2106
|
+
if (raf !== 0) cancelAnimationFrame(raf);
|
|
2107
|
+
ro?.disconnect();
|
|
2108
|
+
window.removeEventListener("resize", schedule);
|
|
2109
|
+
clearInterval(guard);
|
|
2110
|
+
};
|
|
2111
|
+
}, []);
|
|
2112
|
+
if (timelineUnknown || timelineDisabled || sessionId === void 0 || sessionsService === void 0 || isNarrow || hidden || messages.length < 2) return null;
|
|
2113
|
+
const height = railHeightFor(messages.length);
|
|
2114
|
+
const activeKey = activeIndex >= 0 && activeIndex < messages.length ? markKeyOf(messages[activeIndex]) : "";
|
|
2115
|
+
const displayMessages = marksOnly ? messages.filter((m) => markedSet.has(markKeyOf(m))) : messages;
|
|
2116
|
+
return react_dom.default.createPortal(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2117
|
+
className: "dsbt-nav",
|
|
2118
|
+
style: {
|
|
2119
|
+
right: rightOffset,
|
|
2120
|
+
["--dsbt-h"]: `${height}px`,
|
|
2121
|
+
["--dsbt-w"]: `${railWidth}px`
|
|
2122
|
+
},
|
|
2123
|
+
role: "navigation",
|
|
2124
|
+
"aria-label": t("timeline.railLabel"),
|
|
2125
|
+
onMouseEnter: () => setShow(true),
|
|
2126
|
+
onMouseLeave: () => setShow(false),
|
|
2127
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: show ? "dsbt-bg dsbt-bg-hide" : "dsbt-bg" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2128
|
+
className: show ? "dsbt-wrap dsbt-show" : "dsbt-wrap",
|
|
2129
|
+
children: [
|
|
2130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2131
|
+
className: "dsbt-filterbar",
|
|
2132
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2133
|
+
type: "button",
|
|
2134
|
+
className: marksOnly ? "dsbt-filterbtn dsbt-filteron" : "dsbt-filterbtn",
|
|
2135
|
+
"aria-pressed": marksOnly,
|
|
2136
|
+
onClick: () => setMarksOnly((v) => !v),
|
|
2137
|
+
children: [t("timeline.marksOnly"), marks.length > 0 ? ` (${marks.length})` : ""]
|
|
2138
|
+
})
|
|
2139
|
+
}),
|
|
2140
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2141
|
+
className: fades.top ? "dsbt-fade dsbt-fade-on" : "dsbt-fade",
|
|
2142
|
+
"aria-hidden": true
|
|
2143
|
+
}),
|
|
2144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2145
|
+
className: fades.bottom ? "dsbt-fade dsbt-fade-bot dsbt-fade-on" : "dsbt-fade dsbt-fade-bot",
|
|
2146
|
+
"aria-hidden": true
|
|
2147
|
+
}),
|
|
2148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2149
|
+
className: "dsbt-page",
|
|
2150
|
+
ref: pageRef,
|
|
2151
|
+
onScroll: syncFades,
|
|
2152
|
+
children: displayMessages.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2153
|
+
className: "dsbt-empty",
|
|
2154
|
+
children: t("timeline.noMarks")
|
|
2155
|
+
}) : displayMessages.map((m) => {
|
|
2156
|
+
const key = resolveAnchorKey(m);
|
|
2157
|
+
const mk = markKeyOf(m);
|
|
2158
|
+
const marked = markedSet.has(mk);
|
|
2159
|
+
const isActive = activeKey !== "" && mk === activeKey;
|
|
2160
|
+
const cls = `dsbt-item${isActive ? " dsbt-active" : ""}${marked ? " dsbt-marked" : ""}`;
|
|
2161
|
+
const label = `${marked ? "★ " : ""}${t("timeline.roleUser")}: ${m.text.slice(0, 60) || t("timeline.noText")}`;
|
|
2162
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2163
|
+
type: "button",
|
|
2164
|
+
ref: isActive ? activeItemRef : void 0,
|
|
2165
|
+
className: cls,
|
|
2166
|
+
title: `${marked ? "★ " : ""}${m.text === "" ? t("timeline.noText") : m.text}`,
|
|
2167
|
+
"aria-label": label,
|
|
2168
|
+
"aria-current": isActive ? "location" : void 0,
|
|
2169
|
+
onClick: () => {
|
|
2170
|
+
if (key === void 0) return;
|
|
2171
|
+
jumpPendingRef.current = true;
|
|
2172
|
+
if (jumpFallbackRef.current !== void 0) clearTimeout(jumpFallbackRef.current);
|
|
2173
|
+
jumpFallbackRef.current = window.setTimeout(() => {
|
|
2174
|
+
jumpFallbackRef.current = void 0;
|
|
2175
|
+
jumpPendingRef.current = false;
|
|
2176
|
+
}, 800);
|
|
2177
|
+
jumpToMessage(sessionsService, sessionId, key).catch(() => {});
|
|
2178
|
+
},
|
|
2179
|
+
children: [
|
|
2180
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2181
|
+
className: "dsbt-title",
|
|
2182
|
+
children: m.text === "" ? t("timeline.noText") : m.text
|
|
2183
|
+
}),
|
|
2184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2185
|
+
className: marked ? "dsbt-star dsbt-staron" : "dsbt-star",
|
|
2186
|
+
role: "button",
|
|
2187
|
+
tabIndex: 0,
|
|
2188
|
+
"aria-pressed": marked,
|
|
2189
|
+
"aria-label": marked ? t("timeline.unmark") : t("timeline.mark"),
|
|
2190
|
+
onMouseDown: (e) => {
|
|
2191
|
+
e.stopPropagation();
|
|
2192
|
+
e.preventDefault();
|
|
2193
|
+
},
|
|
2194
|
+
onClick: (e) => {
|
|
2195
|
+
e.stopPropagation();
|
|
2196
|
+
toggleMark(m);
|
|
2197
|
+
},
|
|
2198
|
+
onKeyDown: (e) => {
|
|
2199
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2200
|
+
e.preventDefault();
|
|
2201
|
+
e.stopPropagation();
|
|
2202
|
+
toggleMark(m);
|
|
2203
|
+
}
|
|
2204
|
+
},
|
|
2205
|
+
children: "★"
|
|
2206
|
+
}),
|
|
2207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2208
|
+
className: "dsbt-ind",
|
|
2209
|
+
"aria-hidden": true,
|
|
2210
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsbt-line" })
|
|
2211
|
+
})
|
|
2212
|
+
]
|
|
2213
|
+
}, m.seq);
|
|
2214
|
+
})
|
|
2215
|
+
})
|
|
2216
|
+
]
|
|
2217
|
+
})]
|
|
2218
|
+
}), document.body);
|
|
2219
|
+
}
|
|
2220
|
+
//#endregion
|
|
1095
2221
|
//#region src/client/index.ts
|
|
1096
2222
|
/**
|
|
1097
|
-
* Service injections this entry declares:
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
2223
|
+
* Service injections this entry declares: `slots` + `locale` for the settings
|
|
2224
|
+
* surface and the timeline slot seat, and `sessions` so the timeline rail can
|
|
2225
|
+
* enumerate user messages and jump between them. The settings surface itself
|
|
2226
|
+
* talks to the plugin's own host route over plain same-origin fetch.
|
|
1100
2227
|
*/
|
|
1101
|
-
const inject = [
|
|
2228
|
+
const inject = [
|
|
2229
|
+
"slots",
|
|
2230
|
+
"locale",
|
|
2231
|
+
"sessions"
|
|
2232
|
+
];
|
|
1102
2233
|
/**
|
|
1103
|
-
* Client plugin body: paint the persisted background live
|
|
1104
|
-
* General-settings row.
|
|
1105
|
-
* @param ctx - client cordis context (slots/locale injected).
|
|
2234
|
+
* Client plugin body: paint the persisted background live, register the
|
|
2235
|
+
* General-settings row, and mount the conversation timeline rail.
|
|
2236
|
+
* @param ctx - client cordis context (slots/locale/sessions injected).
|
|
1106
2237
|
*/
|
|
1107
2238
|
function apply(ctx) {
|
|
1108
2239
|
const paint = () => {
|
|
@@ -1126,6 +2257,13 @@ window.__ModuleLoader__.load({
|
|
|
1126
2257
|
order: 600,
|
|
1127
2258
|
locale: "ui-background"
|
|
1128
2259
|
}, BackgroundSettingsRow));
|
|
2260
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
2261
|
+
name: "conversation.input.dock",
|
|
2262
|
+
id: "deepseek-harness-background.timeline",
|
|
2263
|
+
order: 45,
|
|
2264
|
+
locale: "ui-background",
|
|
2265
|
+
inject: () => ({ sessionsService: ctx.sessions })
|
|
2266
|
+
}, TimelineRail));
|
|
1129
2267
|
ctx.effect(() => () => {
|
|
1130
2268
|
unsubscribe();
|
|
1131
2269
|
backgroundPainter.dispose();
|