@johnnren/dsh-zen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1655 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@johnnren/dsh-zen",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
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 __export = (target, all) => {
15
+ for (var name2 in all)
16
+ __defProp(target, name2, { get: all[name2], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
+
36
+ // src/client.ts
37
+ var client_exports = {};
38
+ __export(client_exports, {
39
+ apply: () => apply,
40
+ inject: () => inject,
41
+ name: () => name
42
+ });
43
+ module.exports = __toCommonJS(client_exports);
44
+
45
+ // src/locales.ts
46
+ var NS = "zen";
47
+ var zh = {
48
+ "view.zen": "Zen",
49
+ "zen.loading": "\u8F7D\u5165\u4E2D\u2026",
50
+ "zen.deepDiving1": "\u5728\u6F5C\u6C34\u5462\uFF0C\u522B\u76EF\u4E86\uFF5E",
51
+ "zen.deepDiving2": "\u6C34\u5E95\u4E16\u754C\u5F88\u7CBE\u5F69\uFF5E",
52
+ "zen.deepDiving3": "\u6B63\u5728\u6DF1\u6F5C\u4E2D\uFF0C\u7A0D\u7B49",
53
+ "zen.deepDiving4": "\u5B89\u5FC3\u7B49\u7740\u5C31\u597D",
54
+ "zen.deepDiving5": "\u9C7C\u8FD8\u5728\u6E38\uFF0C\u522B\u62C9\u6746",
55
+ "zen.ready1": "\u641E\u5B9A\u5566 \u2728",
56
+ "zen.ready2": "\u5927\u529F\u544A\u6210 \u{1F389}",
57
+ "zen.ready3": "\u5B8C\u5DE5\uFF01\u6765\u770B\u770B\u5427 \u{1F440}",
58
+ "zen.ready4": "\u9C7C\u4E0A\u94A9\u4E86 \u{1F41F}",
59
+ "zen.ready5": "\u56DE\u6765\u6536\u7F51\u5566 \u{1F3A3}",
60
+ "zen.subRunning1": "\u53BB\u505A\u70B9\u522B\u7684\u5427\uFF0C\u5B8C\u6210\u4E86\u4F1A\u53EB\u4F60",
61
+ "zen.subRunning2": "\u559D\u676F\u8336\uFF0C\u7B49\u7740\u5C31\u597D",
62
+ "zen.subRunning3": "\u53BB\u6563\u4E2A\u6B65\uFF1F",
63
+ "zen.subRunning4": "\u4E0D\u7528\u4E00\u76F4\u76EF\u7740\uFF0C\u653E\u5FC3",
64
+ "zen.subReady1": "\u56DE\u6765\u770B\u770B\u7ED3\u679C\u5427",
65
+ "zen.subReady2": "\u51C6\u5907\u597D\u9A8C\u6536\u4E86\u5417",
66
+ "zen.subReady3": "\u56DE\u6765\u770B\u6210\u679C\u5566",
67
+ "zen.foreground": "\u76EF\u5C4F",
68
+ "zen.total": "\u5DF2\u8FD0\u884C",
69
+ "zen.ratio": "\u76EF\u5C4F\u6BD4\u4F8B",
70
+ "zen.zen": "\u7985",
71
+ "zen.statsTitle": "\u524D\u53F0\u65F6\u95F4\u7EDF\u8BA1",
72
+ "zen.stats": "\u7EDF\u8BA1",
73
+ "zen.noSession": "\u9009\u4E2A\u4F1A\u8BDD\u5F00\u59CB\u5427 \u{1F319}",
74
+ "zen.youSaid": "\u7528\u6237\u6D88\u606F",
75
+ "zen.prevReply": "\u4E0A\u8F6E\u56DE\u590D",
76
+ "zen.currentReply": "\u52A8\u6001",
77
+ "zen.runningTool": "\u6B63\u5728\u6267\u884C",
78
+ "zen.aiSaid": "AI \u56DE\u590D",
79
+ "zen.turnCount": "\u6B65\u6570",
80
+ "zen.runTime": "\u7528\u65F6",
81
+ "zen.todayZen": "\u4ECA\u65E5\u7985",
82
+ "zen.weekZen": "\u672C\u5468\u7985",
83
+ "chatHide.toggle": "\u9690\u85CF\u4E2D\u95F4\u8FC7\u7A0B",
84
+ "chatHide.desc": "\u9690\u85CF\u5DE5\u5177\u8C03\u7528\u3001\u63A8\u7406\u3001\u4E0A\u4E0B\u6587\u6CE8\u5165\u7B49\u4E2D\u95F4\u6B65\u9AA4",
85
+ "settings.title": "Zen \u8BBE\u7F6E",
86
+ "settings.desc": "\u914D\u7F6E Zen \u89C6\u56FE\u4E0E\u76EF\u5C4F\u7EDF\u8BA1",
87
+ "settings.shortcut": "\u5FEB\u6377\u952E\uFF1AAlt+Z \u5207\u6362\u7985 / \u804A\u5929\u89C6\u56FE",
88
+ "settings.showRunningStatus": "\u663E\u793A\u8FDB\u884C\u4E2D\u6807\u9898",
89
+ "settings.showRunningSub": "\u663E\u793A\u8FDB\u884C\u4E2D\u526F\u6807\u9898",
90
+ "settings.showDoneStatus": "\u663E\u793A\u7ED3\u675F\u6807\u9898",
91
+ "settings.showUserMessage": "\u751F\u6210\u4E2D\u663E\u793A\u7528\u6237\u6D88\u606F",
92
+ "settings.showPrevReply": "\u751F\u6210\u4E2D\u663E\u793A\u4E0A\u8F6E\u56DE\u590D",
93
+ "settings.showCurrentReply": "\u751F\u6210\u4E2D\u663E\u793A\u52A8\u6001",
94
+ "settings.showTurnStats": "\u663E\u793A\u672C\u8F6E\u8F6E\u6B21\u548C\u7528\u65F6",
95
+ "settings.showForegroundTooltip": "\u663E\u793A\u76EF\u5C4F\u65F6\u95F4\u7EDF\u8BA1",
96
+ "settings.autoEnterZen": "\u81EA\u52A8\u8FDB\u5165\u7985\uFF08\u4EFB\u52A1\u5F00\u59CB\u65F6\uFF09",
97
+ "settings.autoExitZen": "\u81EA\u52A8\u89E3\u9664\u7985\uFF08\u4EFB\u52A1\u5B8C\u6210\u65F6\uFF09",
98
+ "settings.animation": "\u52A8\u6548"
99
+ };
100
+ var en = {
101
+ "view.zen": "Zen",
102
+ "zen.loading": "Loading\u2026",
103
+ "zen.deepDiving1": "Deep diving, stop staring~",
104
+ "zen.deepDiving2": "Underwater world is amazing~",
105
+ "zen.deepDiving3": "Deep diving, hang tight",
106
+ "zen.deepDiving4": "Just relax and wait",
107
+ "zen.deepDiving5": "Fish still swimming, don't reel in",
108
+ "zen.ready1": "Done \u2728",
109
+ "zen.ready2": "All finished \u{1F389}",
110
+ "zen.ready3": "Complete! Come check it out \u{1F440}",
111
+ "zen.ready4": "Fish caught \u{1F41F}",
112
+ "zen.ready5": "Time to reel in \u{1F3A3}",
113
+ "zen.subRunning1": "Go do something else, I'll ping you when ready",
114
+ "zen.subRunning2": "Have some tea, just wait",
115
+ "zen.subRunning3": "Go for a walk?",
116
+ "zen.subRunning4": "No need to stare, relax",
117
+ "zen.subReady1": "Come check the results",
118
+ "zen.subReady2": "Ready to review?",
119
+ "zen.subReady3": "Come see the results",
120
+ "zen.foreground": "Foreground",
121
+ "zen.total": "Total",
122
+ "zen.ratio": "Foreground ratio",
123
+ "zen.zen": "Zen",
124
+ "zen.statsTitle": "Foreground Time Stats",
125
+ "zen.stats": "Stats",
126
+ "zen.noSession": "Pick a session to start \u{1F319}",
127
+ "zen.youSaid": "User message",
128
+ "zen.prevReply": "Last reply",
129
+ "zen.currentReply": "Activity",
130
+ "zen.runningTool": "Running",
131
+ "zen.aiSaid": "AI replied",
132
+ "zen.turnCount": "Steps",
133
+ "zen.runTime": "Time",
134
+ "zen.todayZen": "Today's Zen",
135
+ "zen.weekZen": "This Week's Zen",
136
+ "chatHide.toggle": "Hide intermediate steps",
137
+ "chatHide.desc": "Hide tool calls, reasoning, context injection and other intermediate steps",
138
+ "settings.title": "Zen Settings",
139
+ "settings.desc": "Configure Zen view and screen-time stats",
140
+ "settings.shortcut": "Shortcut: Alt+Z toggles Zen / chat view",
141
+ "settings.showRunningStatus": "Show running title",
142
+ "settings.showRunningSub": "Show running subtitle",
143
+ "settings.showDoneStatus": "Show done title",
144
+ "settings.showUserMessage": "Show user message while running",
145
+ "settings.showPrevReply": "Show last reply while running",
146
+ "settings.showCurrentReply": "Show activity while running",
147
+ "settings.showTurnStats": "Show turn count and run time",
148
+ "settings.showForegroundTooltip": "Show foreground time stats",
149
+ "settings.autoEnterZen": "Auto enter Zen (on task start)",
150
+ "settings.autoExitZen": "Auto exit Zen (on task complete)",
151
+ "settings.animation": "Animations"
152
+ };
153
+
154
+ // src/foreground-tracker.ts
155
+ var FLUSH_INTERVAL_MS = 5e3;
156
+ var MAX_SESSIONS = 200;
157
+ var PRUNE_AFTER_MS = 30 * 24 * 60 * 60 * 1e3;
158
+ function formatDuration(ms) {
159
+ if (ms < 1e3) return "0s";
160
+ const totalSec = Math.floor(ms / 1e3);
161
+ if (totalSec < 60) return `${totalSec}s`;
162
+ const min = Math.floor(totalSec / 60);
163
+ const sec = totalSec % 60;
164
+ if (min < 60) return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
165
+ const hr = Math.floor(min / 60);
166
+ const remMin = min % 60;
167
+ return remMin > 0 ? `${hr}h ${remMin}m` : `${hr}h`;
168
+ }
169
+ function dayKeyOf(ms) {
170
+ const d = new Date(ms);
171
+ const y = d.getFullYear();
172
+ const m = String(d.getMonth() + 1).padStart(2, "0");
173
+ const day = String(d.getDate()).padStart(2, "0");
174
+ return `${y}-${m}-${day}`;
175
+ }
176
+ var g = globalThis;
177
+ var WINDOW_KEY = "__dshZenStore";
178
+ function initForegroundTracker(ctx) {
179
+ if (g[WINDOW_KEY] && g[WINDOW_KEY].alive) return g[WINDOW_KEY].store;
180
+ if (g[WINDOW_KEY]) g[WINDOW_KEY].dispose();
181
+ g[WINDOW_KEY] = createTracker(ctx);
182
+ return g[WINDOW_KEY].store;
183
+ }
184
+ function createTracker(ctx) {
185
+ const sessions = ctx.sessions;
186
+ const persistKey = "dsh.zen.stats";
187
+ const loadInitial = () => {
188
+ try {
189
+ const raw = localStorage.getItem(persistKey);
190
+ if (raw) {
191
+ const parsed = JSON.parse(raw);
192
+ const out = {};
193
+ for (const [id, s] of Object.entries(parsed.sessions ?? {})) {
194
+ const src = s ?? {};
195
+ out[id] = {
196
+ ...src,
197
+ runningMs: src.runningMs ?? src.foregroundMs ?? 0
198
+ };
199
+ }
200
+ return { sessions: out };
201
+ }
202
+ } catch {
203
+ }
204
+ return { sessions: {} };
205
+ };
206
+ let state = loadInitial();
207
+ const listeners = /* @__PURE__ */ new Set();
208
+ const notify = () => {
209
+ for (const fn of listeners) fn();
210
+ };
211
+ function mergeDays(a, b) {
212
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
213
+ if (keys.size === 0) return void 0;
214
+ const out = {};
215
+ for (const k of keys) {
216
+ const pa = a?.[k] ?? { fg: 0, run: 0 };
217
+ const pb = b?.[k] ?? { fg: 0, run: 0 };
218
+ out[k] = { fg: Math.max(pa.fg, pb.fg), run: Math.max(pa.run, pb.run) };
219
+ }
220
+ return out;
221
+ }
222
+ const mergePersist = () => {
223
+ try {
224
+ let stored = {};
225
+ const raw = localStorage.getItem(persistKey);
226
+ if (raw) {
227
+ const parsed = JSON.parse(raw);
228
+ stored = parsed.sessions ?? {};
229
+ }
230
+ const liveIds = new Set(Object.keys(sessions.list.getSnapshot().byId));
231
+ const merged = {};
232
+ for (const [id, s] of Object.entries(state.sessions)) {
233
+ const old = stored[id];
234
+ if (!old) {
235
+ merged[id] = s;
236
+ continue;
237
+ }
238
+ merged[id] = {
239
+ foregroundMs: Math.max(old.foregroundMs ?? 0, s.foregroundMs ?? 0),
240
+ runningMs: Math.max(old.runningMs ?? 0, s.runningMs ?? 0),
241
+ sessionStartMs: Math.min(old.sessionStartMs ?? s.sessionStartMs, s.sessionStartMs),
242
+ sessionEndMs: s.sessionEndMs ?? old.sessionEndMs ?? null,
243
+ title: s.title || old.title || id,
244
+ days: mergeDays(old.days, s.days)
245
+ };
246
+ }
247
+ for (const [id, s] of Object.entries(stored)) {
248
+ if (!merged[id] && liveIds.has(id)) merged[id] = s;
249
+ }
250
+ state = { sessions: merged };
251
+ localStorage.setItem(persistKey, JSON.stringify({ sessions: merged }));
252
+ } catch {
253
+ }
254
+ };
255
+ const store = {
256
+ getSnapshot: () => state,
257
+ subscribe: (fn) => {
258
+ listeners.add(fn);
259
+ return () => {
260
+ listeners.delete(fn);
261
+ };
262
+ },
263
+ update: (updater) => {
264
+ state = updater(state);
265
+ mergePersist();
266
+ notify();
267
+ },
268
+ set: (next) => {
269
+ state = next;
270
+ mergePersist();
271
+ notify();
272
+ }
273
+ };
274
+ let activeSessionId = null;
275
+ let runSegStart = null;
276
+ let fgSegStart = null;
277
+ let wasRunning = false;
278
+ function flush(sessionId, runStart, fgStart) {
279
+ const now = Date.now();
280
+ const runDelta = runStart !== null ? now - runStart : 0;
281
+ const fgDelta = fgStart !== null ? now - fgStart : 0;
282
+ if (runDelta <= 0 && fgDelta <= 0) return;
283
+ store.update((prev) => {
284
+ const existing = prev.sessions[sessionId] ?? {
285
+ foregroundMs: 0,
286
+ runningMs: 0,
287
+ sessionStartMs: now,
288
+ sessionEndMs: null,
289
+ title: sessionId,
290
+ days: {}
291
+ };
292
+ const dayKey = dayKeyOf(now);
293
+ const prevDay = existing.days?.[dayKey] ?? { fg: 0, run: 0 };
294
+ return {
295
+ sessions: {
296
+ ...prev.sessions,
297
+ [sessionId]: {
298
+ ...existing,
299
+ foregroundMs: existing.foregroundMs + Math.max(0, fgDelta),
300
+ runningMs: (existing.runningMs ?? 0) + Math.max(0, runDelta),
301
+ days: {
302
+ ...existing.days ?? {},
303
+ [dayKey]: {
304
+ fg: prevDay.fg + Math.max(0, fgDelta),
305
+ run: prevDay.run + Math.max(0, runDelta)
306
+ }
307
+ }
308
+ }
309
+ }
310
+ };
311
+ });
312
+ }
313
+ function pruneIfNeeded() {
314
+ const now = Date.now();
315
+ const entries = Object.entries(state.sessions);
316
+ if (entries.length <= MAX_SESSIONS) {
317
+ let changed = false;
318
+ const kept2 = {};
319
+ for (const [id, stats] of entries) {
320
+ if (now - stats.sessionStartMs > PRUNE_AFTER_MS) {
321
+ changed = true;
322
+ continue;
323
+ }
324
+ kept2[id] = stats;
325
+ }
326
+ if (changed) store.set({ sessions: kept2 });
327
+ return;
328
+ }
329
+ entries.sort((a, b) => b[1].sessionStartMs - a[1].sessionStartMs);
330
+ const kept = {};
331
+ for (const [id, stats] of entries.slice(0, MAX_SESSIONS)) {
332
+ if (now - stats.sessionStartMs <= PRUNE_AFTER_MS) kept[id] = stats;
333
+ }
334
+ store.set({ sessions: kept });
335
+ }
336
+ function ensureSession(sessionId, title, running) {
337
+ store.update((prev) => {
338
+ if (prev.sessions[sessionId]) {
339
+ const existing = prev.sessions[sessionId];
340
+ if (existing.sessionEndMs === null && !running) {
341
+ return { sessions: { ...prev.sessions, [sessionId]: { ...existing, title, sessionEndMs: Date.now() } } };
342
+ }
343
+ if (existing.sessionEndMs !== null && running) {
344
+ return { sessions: { ...prev.sessions, [sessionId]: { ...existing, title, sessionEndMs: null } } };
345
+ }
346
+ if (existing.title !== title) {
347
+ return { sessions: { ...prev.sessions, [sessionId]: { ...existing, title } } };
348
+ }
349
+ return prev;
350
+ }
351
+ return {
352
+ sessions: {
353
+ ...prev.sessions,
354
+ [sessionId]: { foregroundMs: 0, runningMs: 0, sessionStartMs: Date.now(), sessionEndMs: running ? null : Date.now(), title, days: {} }
355
+ }
356
+ };
357
+ });
358
+ }
359
+ function reevaluate() {
360
+ const isZenTabActive = typeof document !== "undefined" && document.querySelector(".dsh-zen-root") !== null;
361
+ const isWindowVisible = !isZenTabActive && typeof document !== "undefined" && !document.hidden;
362
+ const listSnap = sessions.list.getSnapshot();
363
+ const currentId = listSnap.current ?? null;
364
+ const currentSession = currentId ? listSnap.byId[currentId] : null;
365
+ const isRunning = (currentSession?.running ?? false) && !currentSession?.pendingInteraction;
366
+ for (const [id, summary] of Object.entries(listSnap.byId)) {
367
+ ensureSession(id, summary.displayTitle, summary.running);
368
+ }
369
+ if (currentId !== activeSessionId) {
370
+ if (activeSessionId !== null) flush(activeSessionId, runSegStart, fgSegStart);
371
+ activeSessionId = currentId;
372
+ runSegStart = null;
373
+ fgSegStart = null;
374
+ wasRunning = isRunning;
375
+ }
376
+ if (isRunning !== wasRunning) {
377
+ if (activeSessionId !== null) flush(activeSessionId, runSegStart, fgSegStart);
378
+ runSegStart = null;
379
+ fgSegStart = null;
380
+ wasRunning = isRunning;
381
+ }
382
+ if (activeSessionId === null) return;
383
+ const shouldRun = isRunning;
384
+ const shouldFg = isRunning && isWindowVisible;
385
+ if (shouldRun && runSegStart === null) {
386
+ runSegStart = Date.now();
387
+ } else if (!shouldRun && runSegStart !== null) {
388
+ flush(activeSessionId, runSegStart, null);
389
+ runSegStart = null;
390
+ }
391
+ if (shouldFg && fgSegStart === null) {
392
+ fgSegStart = Date.now();
393
+ } else if (!shouldFg && fgSegStart !== null) {
394
+ flush(activeSessionId, null, fgSegStart);
395
+ fgSegStart = null;
396
+ }
397
+ }
398
+ const onVisibilityChange = () => reevaluate();
399
+ const onFocus = () => reevaluate();
400
+ const onBlur = () => reevaluate();
401
+ const onPageHide = () => {
402
+ if (activeSessionId !== null) {
403
+ flush(activeSessionId, runSegStart, fgSegStart);
404
+ runSegStart = null;
405
+ fgSegStart = null;
406
+ }
407
+ };
408
+ let alive = true;
409
+ const flushTimer = setInterval(() => {
410
+ if (activeSessionId !== null && (runSegStart !== null || fgSegStart !== null)) {
411
+ flush(activeSessionId, runSegStart, fgSegStart);
412
+ const now = Date.now();
413
+ if (runSegStart !== null) runSegStart = now;
414
+ if (fgSegStart !== null) fgSegStart = now;
415
+ }
416
+ pruneIfNeeded();
417
+ }, FLUSH_INTERVAL_MS);
418
+ const onStorage = (e) => {
419
+ if (e.key !== null && e.key !== persistKey) return;
420
+ mergePersist();
421
+ notify();
422
+ };
423
+ const unsubSessions = sessions.list.subscribe(() => reevaluate());
424
+ document.addEventListener("visibilitychange", onVisibilityChange);
425
+ window.addEventListener("focus", onFocus);
426
+ window.addEventListener("blur", onBlur);
427
+ window.addEventListener("pagehide", onPageHide);
428
+ window.addEventListener("storage", onStorage);
429
+ reevaluate();
430
+ const thisTracker = {
431
+ store: {
432
+ ...store,
433
+ getLiveMs() {
434
+ const now = Date.now();
435
+ return {
436
+ fgDelta: fgSegStart !== null ? Math.max(0, now - fgSegStart) : 0,
437
+ runDelta: runSegStart !== null ? Math.max(0, now - runSegStart) : 0,
438
+ sessionId: activeSessionId
439
+ };
440
+ }
441
+ },
442
+ dispose() {
443
+ alive = false;
444
+ if (activeSessionId !== null) {
445
+ flush(activeSessionId, runSegStart, fgSegStart);
446
+ runSegStart = null;
447
+ fgSegStart = null;
448
+ }
449
+ if (g[WINDOW_KEY] === thisTracker) g[WINDOW_KEY] = null;
450
+ },
451
+ alive
452
+ };
453
+ ctx.effect(() => {
454
+ return () => {
455
+ document.removeEventListener("visibilitychange", onVisibilityChange);
456
+ window.removeEventListener("focus", onFocus);
457
+ window.removeEventListener("blur", onBlur);
458
+ window.removeEventListener("pagehide", onPageHide);
459
+ window.removeEventListener("storage", onStorage);
460
+ clearInterval(flushTimer);
461
+ unsubSessions();
462
+ if (activeSessionId !== null) {
463
+ flush(activeSessionId, runSegStart, fgSegStart);
464
+ runSegStart = null;
465
+ fgSegStart = null;
466
+ }
467
+ thisTracker.dispose();
468
+ };
469
+ }, "zen: foreground tracker");
470
+ return thisTracker;
471
+ }
472
+
473
+ // src/zen-settings.ts
474
+ var PERSIST_KEY = "dsh.zen.settings";
475
+ var DEFAULTS = {
476
+ showRunningStatus: true,
477
+ showRunningSub: true,
478
+ showDoneStatus: false,
479
+ showUserMessage: true,
480
+ showPrevReply: true,
481
+ showCurrentReply: true,
482
+ showTurnStats: true,
483
+ showForegroundTooltip: true,
484
+ autoEnterZen: false,
485
+ autoExitZen: false,
486
+ animation: false
487
+ };
488
+ function createZenSettingsStore() {
489
+ let state = loadInitial();
490
+ const listeners = /* @__PURE__ */ new Set();
491
+ function loadInitial() {
492
+ try {
493
+ const raw = localStorage.getItem(PERSIST_KEY);
494
+ if (raw) {
495
+ const parsed = JSON.parse(raw);
496
+ const { showPet: _p, showEmoji: _e, customRunningMessages: _cr, customDoneMessages: _cd, showAiReply: _ar, ...rest } = parsed;
497
+ if ("showStatus" in rest) {
498
+ const old = rest.showStatus;
499
+ delete rest.showStatus;
500
+ rest.showRunningStatus = old ?? true;
501
+ rest.showDoneStatus = old === true;
502
+ }
503
+ return { ...DEFAULTS, ...rest };
504
+ }
505
+ } catch {
506
+ }
507
+ return { ...DEFAULTS };
508
+ }
509
+ function persist() {
510
+ try {
511
+ localStorage.setItem(PERSIST_KEY, JSON.stringify(state));
512
+ } catch {
513
+ }
514
+ }
515
+ function notify() {
516
+ for (const fn of listeners) fn();
517
+ }
518
+ return {
519
+ getSnapshot: () => state,
520
+ subscribe: (fn) => {
521
+ listeners.add(fn);
522
+ return () => {
523
+ listeners.delete(fn);
524
+ };
525
+ },
526
+ update: (patch) => {
527
+ state = { ...state, ...patch };
528
+ persist();
529
+ notify();
530
+ }
531
+ };
532
+ }
533
+
534
+ // src/components/ZenView.tsx
535
+ var import_react = __toESM(require("react"), 1);
536
+ var CSS = `
537
+ .dsh-zen-root {
538
+ box-sizing: border-box;
539
+ height: 100%;
540
+ min-height: 0;
541
+ display: flex;
542
+ flex-direction: column;
543
+ align-items: center;
544
+ justify-content: center;
545
+ padding: 48px 32px 16px;
546
+ gap: 24px;
547
+ background: var(--dsw-alias-bg-base);
548
+ color: var(--dsw-alias-label-primary);
549
+ overflow: auto;
550
+ position: relative;
551
+ }
552
+
553
+ .dsh-zen-center {
554
+ width: 100%;
555
+ display: flex;
556
+ flex-direction: column;
557
+ align-items: center;
558
+ justify-content: center;
559
+ gap: 8px;
560
+ }
561
+
562
+ /* Status center: always mounted; show/hide via data-hidden + transitions.
563
+ Kept separate from .dsh-zen-center so the done-reply block is never
564
+ height-clipped by max-height. */
565
+ .dsh-zen-statusCenter {
566
+ max-height: 200px;
567
+ overflow: hidden;
568
+ transition: opacity 0.25s ease, max-height 0.25s ease, visibility 0.25s ease;
569
+ }
570
+
571
+ .dsh-zen-statusCenter[data-hidden="true"] {
572
+ opacity: 0;
573
+ visibility: hidden;
574
+ max-height: 0;
575
+ }
576
+
577
+ .dsh-zen-message {
578
+ font-size: 20px;
579
+ font-weight: 500;
580
+ line-height: 28px;
581
+ color: var(--dsw-alias-label-secondary);
582
+ text-align: center;
583
+ }
584
+
585
+ .dsh-zen-sub {
586
+ font-size: 14px;
587
+ line-height: 20px;
588
+ color: var(--dsw-alias-label-tertiary);
589
+ }
590
+
591
+ @keyframes dsh-zen-fade-in {
592
+ from { opacity: 0; transform: translateY(8px); }
593
+ to { opacity: 1; transform: translateY(0); }
594
+ }
595
+
596
+ /* Animation toggle: when off, disable all entry animations and transitions */
597
+ .dsh-zen-root[data-anim="off"],
598
+ .dsh-zen-root[data-anim="off"] * {
599
+ animation: none !important;
600
+ transition: none !important;
601
+ }
602
+
603
+ /* \u2500\u2500 Turn stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
604
+
605
+ .dsh-zen-turnStats {
606
+ font-size: 13px;
607
+ line-height: 20px;
608
+ color: var(--dsw-alias-label-caption);
609
+ font-variant-numeric: tabular-nums;
610
+ display: flex;
611
+ gap: 8px;
612
+ align-items: center;
613
+ animation: dsh-zen-fade-in 0.3s ease-out 0.15s backwards;
614
+ }
615
+
616
+ .dsh-zen-turnStats + .dsh-zen-turnStats {
617
+ animation-delay: 0.2s;
618
+ margin-top: -4px;
619
+ }
620
+
621
+ .dsh-zen-turnStatsSep {
622
+ color: var(--dsw-alias-label-caption);
623
+ opacity: 0.5;
624
+ }
625
+
626
+ .dsh-zen-statVal {
627
+ color: var(--dsw-alias-label-caption);
628
+ }
629
+
630
+ /* \u2500\u2500 Hint row (dotted underline, hover for tooltip) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
631
+
632
+ .dsh-zen-hint {
633
+ position: relative;
634
+ display: inline-flex;
635
+ align-items: center;
636
+ cursor: help;
637
+ }
638
+
639
+ .dsh-zen-hintText {
640
+ color: var(--dsw-alias-label-caption);
641
+ border-bottom: 1px dotted var(--dsw-alias-label-caption);
642
+ transition: color 0.12s, border-color 0.12s;
643
+ }
644
+
645
+ .dsh-zen-tooltip {
646
+ position: absolute;
647
+ bottom: 100%;
648
+ left: 50%;
649
+ transform: translateX(-50%) translateY(4px);
650
+ min-width: 300px;
651
+ padding: 12px 16px;
652
+ background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base));
653
+ border: 1px solid var(--dsw-alias-border-l1);
654
+ border-radius: 10px;
655
+ box-shadow: 0 4px 16px rgba(0,0,0,0.12);
656
+ font-size: 13px;
657
+ line-height: 22px;
658
+ color: var(--dsw-alias-label-secondary);
659
+ font-variant-numeric: tabular-nums;
660
+ opacity: 0;
661
+ visibility: hidden;
662
+ transition: opacity 0.15s, transform 0.15s, visibility 0.15s;
663
+ pointer-events: none;
664
+ z-index: 10;
665
+ }
666
+
667
+ .dsh-zen-hint:hover .dsh-zen-tooltip {
668
+ opacity: 1;
669
+ visibility: visible;
670
+ transform: translateX(-50%) translateY(0);
671
+ pointer-events: auto;
672
+ }
673
+
674
+ .dsh-zen-tooltipTitle {
675
+ font-size: 11px;
676
+ font-weight: 600;
677
+ color: var(--dsw-alias-label-caption);
678
+ margin-bottom: 6px;
679
+ text-transform: uppercase;
680
+ letter-spacing: 0.5px;
681
+ }
682
+
683
+ .dsh-zen-tooltipBody {
684
+ max-width: 400px;
685
+ max-height: 300px;
686
+ overflow: auto;
687
+ font-size: 13px;
688
+ line-height: 22px;
689
+ color: var(--dsw-alias-label-secondary);
690
+ }
691
+
692
+ /* Constrain markdown elements inside the tooltip */
693
+ .dsh-zen-tooltipBody > :first-child {
694
+ margin-top: 0;
695
+ }
696
+ .dsh-zen-tooltipBody > :last-child {
697
+ margin-bottom: 0;
698
+ }
699
+ .dsh-zen-tooltipBody p,
700
+ .dsh-zen-tooltipBody ul,
701
+ .dsh-zen-tooltipBody ol,
702
+ .dsh-zen-tooltipBody pre,
703
+ .dsh-zen-tooltipBody blockquote {
704
+ margin: 6px 0;
705
+ }
706
+ .dsh-zen-tooltipBody h1,
707
+ .dsh-zen-tooltipBody h2,
708
+ .dsh-zen-tooltipBody h3,
709
+ .dsh-zen-tooltipBody h4 {
710
+ margin: 8px 0 4px;
711
+ font-size: 14px;
712
+ line-height: 20px;
713
+ }
714
+ .dsh-zen-tooltipBody pre {
715
+ max-width: 100%;
716
+ overflow-x: auto;
717
+ padding: 8px 10px;
718
+ border-radius: 6px;
719
+ background: var(--dsw-alias-interactive-bg-hover);
720
+ font-size: 12px;
721
+ line-height: 18px;
722
+ }
723
+ .dsh-zen-tooltipBody img {
724
+ max-width: 100%;
725
+ border-radius: 6px;
726
+ }
727
+ .dsh-zen-tooltipBody code {
728
+ word-break: break-word;
729
+ }
730
+
731
+ .dsh-zen-tooltipRow {
732
+ display: flex;
733
+ justify-content: space-between;
734
+ gap: 16px;
735
+ }
736
+
737
+ .dsh-zen-tooltipValue {
738
+ font-weight: 600;
739
+ color: var(--dsw-alias-label-primary);
740
+ }
741
+
742
+ .dsh-zen-tooltipDivider {
743
+ height: 1px;
744
+ background: var(--dsw-alias-border-l1);
745
+ margin: 6px 0;
746
+ }
747
+
748
+ .dsh-zen-tooltipHint {
749
+ font-size: 11px;
750
+ color: var(--dsw-alias-label-caption);
751
+ margin-top: 6px;
752
+ }
753
+
754
+ /* \u2500\u2500 AI reply (markdown, bubble) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
755
+
756
+ .dsh-zen-reply {
757
+ width: 100%;
758
+ max-width: 740px;
759
+ box-sizing: border-box;
760
+ font-size: 15px;
761
+ line-height: 24px;
762
+ color: var(--dsw-alias-label-primary);
763
+ animation: dsh-zen-fade-in 0.3s ease-out 0.1s backwards;
764
+ }
765
+
766
+ /* \u2500\u2500 User message blockquote \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
767
+
768
+ .dsh-zen-userQuote {
769
+ width: 100%;
770
+ max-width: 740px;
771
+ box-sizing: border-box;
772
+ padding: 8px 16px;
773
+ border-left: 3px solid var(--dsw-alias-border-l2);
774
+ border-radius: 0 8px 8px 0;
775
+ background: var(--dsw-alias-interactive-bg-subtle, transparent);
776
+ font-size: 14px;
777
+ line-height: 22px;
778
+ color: var(--dsw-alias-label-secondary);
779
+ white-space: pre-wrap;
780
+ word-break: break-word;
781
+ max-height: 200px;
782
+ overflow: auto;
783
+ animation: dsh-zen-fade-in 0.3s ease-out 0.08s backwards;
784
+ }
785
+ `;
786
+ if (typeof document !== "undefined" && !document.querySelector('style[data-plugin-css="dsh-zen/zen-view"]')) {
787
+ const tag = document.createElement("style");
788
+ tag.setAttribute("data-plugin-css", "dsh-zen/zen-view");
789
+ tag.textContent = CSS;
790
+ document.head.appendChild(tag);
791
+ }
792
+ function computeRatio(fgMs, totalMs) {
793
+ if (totalMs <= 0) return 0;
794
+ return Math.round(Math.min(fgMs, totalMs) / totalMs * 100);
795
+ }
796
+ function extractAssistantText(node) {
797
+ const blocks = node?.data?.blocks;
798
+ if (!Array.isArray(blocks)) return null;
799
+ const texts = [];
800
+ for (const block of blocks) {
801
+ if (block?.kind === "text" && typeof block.text === "string" && block.text.trim()) {
802
+ texts.push(block.text.trim());
803
+ }
804
+ }
805
+ return texts.length ? texts.join("\n") : null;
806
+ }
807
+ function extractLastReply(chatSnapshot) {
808
+ const order = chatSnapshot?.order ?? [];
809
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
810
+ for (let i = order.length - 1; i >= 0; i--) {
811
+ const node = nodes.get(order[i]);
812
+ if (!node || node.kind !== "assistant-step") continue;
813
+ const text = extractAssistantText(node);
814
+ if (text) return text;
815
+ }
816
+ return null;
817
+ }
818
+ function extractPrevRoundReply(chatSnapshot) {
819
+ const order = chatSnapshot?.order ?? [];
820
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
821
+ const start = findCurrentRoundStart(chatSnapshot);
822
+ if (!start) return null;
823
+ for (let i = start.idx - 1; i >= 0; i--) {
824
+ const node = nodes.get(order[i]);
825
+ if (!node || node.kind !== "assistant-step") continue;
826
+ const text = extractAssistantText(node);
827
+ if (text) return text;
828
+ }
829
+ return null;
830
+ }
831
+ function extractCurrentRoundReply(chatSnapshot) {
832
+ const order = chatSnapshot?.order ?? [];
833
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
834
+ const start = findCurrentRoundStart(chatSnapshot);
835
+ if (!start) return null;
836
+ for (let i = order.length - 1; i > start.idx; i--) {
837
+ const node = nodes.get(order[i]);
838
+ if (!node || node.kind !== "assistant-step") continue;
839
+ const text = extractAssistantText(node);
840
+ if (text) return text;
841
+ }
842
+ return null;
843
+ }
844
+ function extractLastUserMessage(chatSnapshot) {
845
+ const order = chatSnapshot?.order ?? [];
846
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
847
+ for (let i = order.length - 1; i >= 0; i--) {
848
+ const node = nodes.get(order[i]);
849
+ if (!node) continue;
850
+ if (node.kind === "user" || node.kind === "steering") {
851
+ const data = node.data;
852
+ if (!data) return null;
853
+ if (Array.isArray(data.content)) {
854
+ const texts = [];
855
+ for (const block of data.content) {
856
+ if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
857
+ texts.push(block.text.trim());
858
+ }
859
+ }
860
+ if (texts.length) return texts.join("\n");
861
+ }
862
+ if (typeof data.text === "string" && data.text.trim()) return data.text.trim();
863
+ return null;
864
+ }
865
+ }
866
+ return null;
867
+ }
868
+ function getLastUserKey(chatSnapshot) {
869
+ const order = chatSnapshot?.order ?? [];
870
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
871
+ for (let i = order.length - 1; i >= 0; i--) {
872
+ const node = nodes.get(order[i]);
873
+ if (node && (node.kind === "user" || node.kind === "steering")) return order[i];
874
+ }
875
+ return null;
876
+ }
877
+ function findCurrentRoundStart(chatSnapshot) {
878
+ const order = chatSnapshot?.order ?? [];
879
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
880
+ for (let i = order.length - 1; i >= 0; i--) {
881
+ const node = nodes.get(order[i]);
882
+ if (node && (node.kind === "user" || node.kind === "steering")) {
883
+ return { idx: i, startTime: node.data?.time ?? 0 };
884
+ }
885
+ }
886
+ return null;
887
+ }
888
+ function countRoundSteps(chatSnapshot) {
889
+ const order = chatSnapshot?.order ?? [];
890
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
891
+ const start = findCurrentRoundStart(chatSnapshot);
892
+ if (!start) return 0;
893
+ let count = 0;
894
+ for (let i = start.idx + 1; i < order.length; i++) {
895
+ const node = nodes.get(order[i]);
896
+ if (node && node.kind === "assistant-step") count++;
897
+ }
898
+ return count;
899
+ }
900
+ function computeRoundMs(chatSnapshot, running) {
901
+ const order = chatSnapshot?.order ?? [];
902
+ const nodes = chatSnapshot?.nodes ?? /* @__PURE__ */ new Map();
903
+ const start = findCurrentRoundStart(chatSnapshot);
904
+ if (!start || start.startTime === 0) return 0;
905
+ if (running) return Math.max(0, Date.now() - start.startTime);
906
+ let endTime = start.startTime;
907
+ for (let i = order.length - 1; i > start.idx; i--) {
908
+ const node = nodes.get(order[i]);
909
+ if (node?.data?.time) {
910
+ endTime = node.data.time;
911
+ break;
912
+ }
913
+ }
914
+ return Math.max(0, endTime - start.startTime);
915
+ }
916
+ function pickRandom(t, baseKey, count) {
917
+ const idx = 1 + Math.floor(Math.random() * count);
918
+ return t(`${baseKey}${idx}`);
919
+ }
920
+ function renderMarkdown(MarkdownText, text) {
921
+ if (MarkdownText) {
922
+ return import_react.default.createElement(MarkdownText, { text });
923
+ }
924
+ return import_react.default.createElement("div", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word" } }, text);
925
+ }
926
+ var ZenView = import_react.default.memo(function ZenView2(props) {
927
+ const { useSession, sessionId, t, foregroundStore, MarkdownText, zenSettingsStore } = props;
928
+ const rawRunning = useSession((s) => s.running);
929
+ const blank = useSession((s) => s.blank);
930
+ const chat = useSession((s) => s.chat);
931
+ const runningCalls = useSession((s) => s?.runningCalls);
932
+ const openState = useSession((s) => s?.openState);
933
+ const isLoading = !blank && (openState === "cold" || openState === "loading");
934
+ const [statusRunning, setStatusRunning] = import_react.default.useState(rawRunning);
935
+ import_react.default.useEffect(() => {
936
+ if (rawRunning) {
937
+ setStatusRunning(true);
938
+ return;
939
+ }
940
+ const id = setTimeout(() => setStatusRunning(false), 800);
941
+ return () => clearTimeout(id);
942
+ }, [rawRunning]);
943
+ const running = rawRunning;
944
+ const settings = zenSettingsStore.getSnapshot();
945
+ const currentUserKey = !blank ? getLastUserKey(chat) : null;
946
+ const lastUserKeyRef = import_react.default.useRef(null);
947
+ import_react.default.useEffect(() => {
948
+ if (!statusRunning) lastUserKeyRef.current = getLastUserKey(chat);
949
+ }, [statusRunning, chat]);
950
+ const userMsg = !blank ? extractLastUserMessage(chat) : null;
951
+ const userMsgVisible = !!userMsg && !blank && !isLoading && (statusRunning ? settings.showUserMessage && currentUserKey !== lastUserKeyRef.current : true);
952
+ const [, forceUpdate] = import_react.default.useReducer((c) => c + 1, 0);
953
+ import_react.default.useEffect(() => foregroundStore.subscribe(forceUpdate), [foregroundStore]);
954
+ import_react.default.useEffect(() => zenSettingsStore.subscribe(forceUpdate), [zenSettingsStore]);
955
+ const statsState = foregroundStore.getSnapshot();
956
+ const stats = statsState.sessions[sessionId];
957
+ const { fgDelta, runDelta } = foregroundStore.getLiveMs();
958
+ const totalMs = (stats?.runningMs ?? 0) + runDelta;
959
+ const fgMs = Math.min((stats?.foregroundMs ?? 0) + fgDelta, totalMs);
960
+ const ratio = computeRatio(fgMs, totalMs);
961
+ const zenPct = 100 - ratio;
962
+ const lastReply = !running && !blank ? extractLastReply(chat) : null;
963
+ const prevReply = !blank ? extractPrevRoundReply(chat) : null;
964
+ const currentReply = !blank ? extractCurrentRoundReply(chat) : null;
965
+ const roundSteps = !blank ? countRoundSteps(chat) : 0;
966
+ const roundMs = !blank ? computeRoundMs(chat, running) : 0;
967
+ const runningTools = Array.isArray(runningCalls) ? runningCalls.map((c) => c?.name).filter((n) => typeof n === "string" && !!n) : [];
968
+ const [, clockTick] = import_react.default.useReducer((c) => c + 1, 0);
969
+ import_react.default.useEffect(() => {
970
+ const id = setInterval(clockTick, 1e3);
971
+ return () => clearInterval(id);
972
+ }, []);
973
+ const message = import_react.default.useMemo(
974
+ () => blank ? t("zen.noSession") : isLoading ? t("zen.loading") : statusRunning ? pickRandom(t, "zen.deepDiving", 5) : pickRandom(t, "zen.ready", 5),
975
+ [blank, isLoading, statusRunning]
976
+ );
977
+ const sub = import_react.default.useMemo(
978
+ () => statusRunning ? pickRandom(t, "zen.subRunning", 4) : blank || isLoading ? "" : pickRandom(t, "zen.subReady", 3),
979
+ [blank, isLoading, statusRunning]
980
+ );
981
+ const showStatusCenter = blank ? settings.showRunningStatus : isLoading || statusRunning ? settings.showRunningStatus : settings.showDoneStatus;
982
+ return import_react.default.createElement(
983
+ "div",
984
+ { className: "dsh-zen-root", "data-anim": settings.animation ? "on" : "off" },
985
+ // Status message (loading, running or done) — always mounted, fades via data-hidden
986
+ import_react.default.createElement(
987
+ "div",
988
+ { className: "dsh-zen-center dsh-zen-statusCenter", "data-hidden": String(!showStatusCenter) },
989
+ import_react.default.createElement("div", { className: "dsh-zen-message" }, message),
990
+ // Second-line description, gated by the running-subtitle toggle
991
+ (isLoading || statusRunning) && settings.showRunningSub && sub && import_react.default.createElement("div", { className: "dsh-zen-sub" }, sub)
992
+ ),
993
+ // User message blockquote (below status, above reply) — while generating only
994
+ !blank && !isLoading && userMsgVisible && import_react.default.createElement("blockquote", { className: "dsh-zen-userQuote" }, userMsg),
995
+ // Done: AI reply with markdown (debounced — no flash on intermediate step gaps; always shown)
996
+ !statusRunning && lastReply && !blank && !isLoading && import_react.default.createElement(
997
+ "div",
998
+ { className: "dsh-zen-center" },
999
+ import_react.default.createElement(
1000
+ "div",
1001
+ { className: "dsh-zen-reply" },
1002
+ MarkdownText ? import_react.default.createElement(MarkdownText, { text: lastReply }) : lastReply
1003
+ )
1004
+ ),
1005
+ // Turn stats + current reply tooltip + prev reply tooltip + foreground tooltip (single line)
1006
+ !blank && !isLoading && (settings.showTurnStats || settings.showCurrentReply || settings.showPrevReply || settings.showForegroundTooltip) && import_react.default.createElement(
1007
+ "div",
1008
+ { className: "dsh-zen-turnStats" },
1009
+ settings.showTurnStats && import_react.default.createElement(
1010
+ import_react.default.Fragment,
1011
+ null,
1012
+ import_react.default.createElement("span", { className: "dsh-zen-statVal" }, `${t("zen.turnCount")} ${roundSteps}`),
1013
+ import_react.default.createElement("span", { className: "dsh-zen-turnStatsSep" }, "\xB7"),
1014
+ import_react.default.createElement("span", { className: "dsh-zen-statVal" }, `${t("zen.runTime")} ${formatDuration(roundMs)}`)
1015
+ ),
1016
+ statusRunning && settings.showCurrentReply && (currentReply || runningTools.length > 0) && import_react.default.createElement(
1017
+ import_react.default.Fragment,
1018
+ null,
1019
+ import_react.default.createElement("span", { className: "dsh-zen-turnStatsSep" }, "\xB7"),
1020
+ import_react.default.createElement(
1021
+ "div",
1022
+ { className: "dsh-zen-hint" },
1023
+ import_react.default.createElement("span", { className: "dsh-zen-hintText" }, t("zen.currentReply")),
1024
+ import_react.default.createElement(
1025
+ "div",
1026
+ { className: "dsh-zen-tooltip" },
1027
+ import_react.default.createElement("div", { className: "dsh-zen-tooltipTitle" }, t("zen.currentReply")),
1028
+ runningTools.length > 0 && import_react.default.createElement(
1029
+ import_react.default.Fragment,
1030
+ null,
1031
+ import_react.default.createElement(
1032
+ "div",
1033
+ { className: "dsh-zen-tooltipRow" },
1034
+ import_react.default.createElement("span", null, t("zen.runningTool")),
1035
+ import_react.default.createElement("span", { className: "dsh-zen-tooltipValue" }, runningTools.join(" \xB7 "))
1036
+ ),
1037
+ currentReply && import_react.default.createElement("div", { className: "dsh-zen-tooltipDivider" })
1038
+ ),
1039
+ currentReply && import_react.default.createElement("div", { className: "dsh-zen-tooltipBody" }, renderMarkdown(MarkdownText, currentReply))
1040
+ )
1041
+ )
1042
+ ),
1043
+ statusRunning && settings.showPrevReply && prevReply && import_react.default.createElement(
1044
+ import_react.default.Fragment,
1045
+ null,
1046
+ import_react.default.createElement("span", { className: "dsh-zen-turnStatsSep" }, "\xB7"),
1047
+ import_react.default.createElement(
1048
+ "div",
1049
+ { className: "dsh-zen-hint" },
1050
+ import_react.default.createElement("span", { className: "dsh-zen-hintText" }, t("zen.prevReply")),
1051
+ import_react.default.createElement(
1052
+ "div",
1053
+ { className: "dsh-zen-tooltip" },
1054
+ import_react.default.createElement("div", { className: "dsh-zen-tooltipTitle" }, t("zen.prevReply")),
1055
+ import_react.default.createElement("div", { className: "dsh-zen-tooltipBody" }, renderMarkdown(MarkdownText, prevReply))
1056
+ )
1057
+ )
1058
+ ),
1059
+ settings.showForegroundTooltip && import_react.default.createElement(
1060
+ import_react.default.Fragment,
1061
+ null,
1062
+ import_react.default.createElement("span", { className: "dsh-zen-turnStatsSep" }, "\xB7"),
1063
+ import_react.default.createElement(
1064
+ "div",
1065
+ { className: "dsh-zen-hint" },
1066
+ import_react.default.createElement("span", { className: "dsh-zen-hintText" }, t("zen.stats")),
1067
+ import_react.default.createElement(
1068
+ "div",
1069
+ { className: "dsh-zen-tooltip" },
1070
+ import_react.default.createElement("div", { className: "dsh-zen-tooltipTitle" }, t("zen.statsTitle")),
1071
+ import_react.default.createElement(
1072
+ "div",
1073
+ { className: "dsh-zen-tooltipRow" },
1074
+ import_react.default.createElement("span", null, t("zen.foreground")),
1075
+ import_react.default.createElement("span", { className: "dsh-zen-tooltipValue" }, formatDuration(fgMs))
1076
+ ),
1077
+ import_react.default.createElement(
1078
+ "div",
1079
+ { className: "dsh-zen-tooltipRow" },
1080
+ import_react.default.createElement("span", null, t("zen.total")),
1081
+ import_react.default.createElement("span", { className: "dsh-zen-tooltipValue" }, formatDuration(totalMs))
1082
+ ),
1083
+ import_react.default.createElement("div", { className: "dsh-zen-tooltipDivider" }),
1084
+ import_react.default.createElement(
1085
+ "div",
1086
+ { className: "dsh-zen-tooltipRow" },
1087
+ import_react.default.createElement("span", null, t("zen.ratio")),
1088
+ import_react.default.createElement("span", { className: "dsh-zen-tooltipValue" }, ratio + "%")
1089
+ ),
1090
+ import_react.default.createElement(
1091
+ "div",
1092
+ { className: "dsh-zen-tooltipRow" },
1093
+ import_react.default.createElement("span", null, t("zen.zen")),
1094
+ import_react.default.createElement("span", { className: "dsh-zen-tooltipValue" }, zenPct + "%")
1095
+ )
1096
+ )
1097
+ )
1098
+ )
1099
+ )
1100
+ );
1101
+ });
1102
+
1103
+ // src/components/ChatHideToggle.tsx
1104
+ var import_react2 = __toESM(require("react"), 1);
1105
+ var CSS2 = `
1106
+ .dsh-zen-chatHideBtn {
1107
+ box-sizing: border-box;
1108
+ height: 28px;
1109
+ color: var(--dsw-alias-label-secondary);
1110
+ cursor: pointer;
1111
+ background: 0 0;
1112
+ border: none;
1113
+ border-radius: 999px;
1114
+ align-items: center;
1115
+ gap: 4px;
1116
+ padding: 0 10px;
1117
+ font-family: inherit;
1118
+ font-size: 12px;
1119
+ font-weight: 500;
1120
+ line-height: 18px;
1121
+ display: inline-flex;
1122
+ transition: background 0.12s;
1123
+ }
1124
+
1125
+ .dsh-zen-chatHideBtn:hover {
1126
+ background: var(--dsw-alias-interactive-bg-hover);
1127
+ }
1128
+
1129
+ .dsh-zen-chatHideBtn[data-active="true"] {
1130
+ color: var(--dsw-alias-state-business-primary);
1131
+ background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent);
1132
+ }
1133
+
1134
+ .dsh-zen-chatHideIcon {
1135
+ flex: none;
1136
+ display: inline-flex;
1137
+ align-items: center;
1138
+ }
1139
+
1140
+ .dsh-zen-chatHideIcon svg {
1141
+ width: 14px;
1142
+ height: 14px;
1143
+ }
1144
+
1145
+ /* \u2500\u2500 Hide rules (activated by body[data-chat-hide="on"]) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1146
+
1147
+ /* Hide entire flowItems for these kinds */
1148
+ body[data-chat-hide="on"] [data-chat-flow-kind="tool-call"] { display: none !important; }
1149
+ body[data-chat-hide="on"] [data-chat-flow-kind="command"] { display: none !important; }
1150
+ body[data-chat-hide="on"] [data-chat-flow-kind="context"] { display: none !important; }
1151
+ body[data-chat-hide="on"] [data-chat-flow-kind="compaction"] { display: none !important; }
1152
+ body[data-chat-hide="on"] [data-chat-flow-kind="model-retry"] { display: none !important; }
1153
+ body[data-chat-hide="on"] [data-chat-flow-kind="turn-error"] { display: none !important; }
1154
+ body[data-chat-hide="on"] [data-chat-flow-kind="turn-max-tokens"] { display: none !important; }
1155
+
1156
+ /* Hide reasoning rows inside assistant-step */
1157
+ body[data-chat-hide="on"] [data-chat-flow-kind="assistant-step"] [data-variant="think"] { display: none !important; }
1158
+
1159
+ /* Hide assistant-step that only contains think (no reply) \u2014 prevents extra flex gap */
1160
+ body[data-chat-hide="on"] [data-chat-flow-kind="assistant-step"]:has(.Sxvs8a_body > [data-variant="think"]:only-child) { display: none !important; }
1161
+
1162
+ /* Collapse context/compaction rows inside user messages (they have padding:2px 0) */
1163
+ body[data-chat-hide="on"] .gdEzaW_contextRow:empty,
1164
+ body[data-chat-hide="on"] .gdEzaW_compactionRow:empty { display: none !important; }
1165
+
1166
+ /* Zero out gap in AssistantMarkdown body when reasoning hidden */
1167
+ body[data-chat-hide="on"] [data-chat-flow-kind="assistant-step"] .Sxvs8a_body { gap: 0 !important; }
1168
+ `;
1169
+ if (typeof document !== "undefined" && !document.querySelector('style[data-plugin-css="dsh-zen/chat-hide"]')) {
1170
+ const tag = document.createElement("style");
1171
+ tag.setAttribute("data-plugin-css", "dsh-zen/chat-hide");
1172
+ tag.textContent = CSS2;
1173
+ document.head.appendChild(tag);
1174
+ }
1175
+ var PERSIST_KEY2 = "dsh.zen.chat-hide";
1176
+ function loadToggleState() {
1177
+ try {
1178
+ return localStorage.getItem(PERSIST_KEY2) === "true";
1179
+ } catch {
1180
+ return false;
1181
+ }
1182
+ }
1183
+ function saveToggleState(on) {
1184
+ try {
1185
+ localStorage.setItem(PERSIST_KEY2, String(on));
1186
+ } catch {
1187
+ }
1188
+ }
1189
+ var _setView = null;
1190
+ function switchView(view) {
1191
+ if (_setView) {
1192
+ _setView(view);
1193
+ return true;
1194
+ }
1195
+ return false;
1196
+ }
1197
+ var ChatHideToggle = import_react2.default.memo(function ChatHideToggle2(props) {
1198
+ const { t } = props;
1199
+ const [active, setActive] = import_react2.default.useState(() => loadToggleState());
1200
+ import_react2.default.useEffect(() => {
1201
+ try {
1202
+ const el = document.querySelector(".dsh-zen-chatHideBtn");
1203
+ if (!el) return;
1204
+ const fk = Object.keys(el).find((k) => k.startsWith("__reactFiber"));
1205
+ if (!fk) return;
1206
+ let fiber = el[fk];
1207
+ let depth = 0;
1208
+ while (fiber && depth < 30) {
1209
+ const mp = fiber.memoizedProps;
1210
+ if (mp && mp.actions && typeof mp.actions.setView === "function") {
1211
+ _setView = mp.actions.setView;
1212
+ break;
1213
+ }
1214
+ fiber = fiber.return;
1215
+ depth++;
1216
+ }
1217
+ } catch {
1218
+ }
1219
+ }, []);
1220
+ import_react2.default.useEffect(() => {
1221
+ if (active) {
1222
+ document.body.setAttribute("data-chat-hide", "on");
1223
+ } else {
1224
+ document.body.removeAttribute("data-chat-hide");
1225
+ }
1226
+ }, [active]);
1227
+ const toggle = import_react2.default.useCallback(() => {
1228
+ setActive((prev) => {
1229
+ const next = !prev;
1230
+ saveToggleState(next);
1231
+ return next;
1232
+ });
1233
+ }, []);
1234
+ return import_react2.default.createElement(
1235
+ "button",
1236
+ {
1237
+ type: "button",
1238
+ className: "dsh-zen-chatHideBtn",
1239
+ "data-active": active ? "true" : "false",
1240
+ title: t("chatHide.desc"),
1241
+ onClick: toggle
1242
+ },
1243
+ import_react2.default.createElement(
1244
+ "span",
1245
+ { className: "dsh-zen-chatHideIcon" },
1246
+ import_react2.default.createElement(
1247
+ "svg",
1248
+ { viewBox: "0 0 16 16", "aria-hidden": true },
1249
+ import_react2.default.createElement("path", {
1250
+ d: active ? "M1 8 C3 4, 6 2, 8 2 C10 2, 13 4, 15 8 C13 12, 10 14, 8 14 C6 14, 3 12, 1 8 Z M8 5 A3 3 0 1 1 8 11 A3 3 0 1 1 8 5 Z" : "M1 8 C3 4, 6 2, 8 2 C10 2, 13 4, 15 8 C13 12, 10 14, 8 14 C6 14, 3 12, 1 8 Z M5 5 L11 11 M11 5 L5 11",
1251
+ stroke: "currentColor",
1252
+ strokeWidth: "1.3",
1253
+ strokeLinecap: "round",
1254
+ fill: active ? "currentColor" : "none"
1255
+ })
1256
+ )
1257
+ ),
1258
+ import_react2.default.createElement("span", null, t("chatHide.toggle"))
1259
+ );
1260
+ });
1261
+
1262
+ // src/components/ZenSettingsSection.tsx
1263
+ var import_react3 = __toESM(require("react"), 1);
1264
+ var CSS3 = `
1265
+ .dsh-zen-settings {
1266
+ padding: 16px 20px;
1267
+ display: flex;
1268
+ flex-direction: column;
1269
+ gap: 4px;
1270
+ }
1271
+
1272
+ .dsh-zen-settingsTitle {
1273
+ font-size: 16px;
1274
+ font-weight: 600;
1275
+ color: var(--dsw-alias-label-primary);
1276
+ margin-bottom: 4px;
1277
+ }
1278
+
1279
+ .dsh-zen-settingsDesc {
1280
+ font-size: 13px;
1281
+ line-height: 20px;
1282
+ color: var(--dsw-alias-label-tertiary);
1283
+ margin-bottom: 12px;
1284
+ }
1285
+
1286
+ .dsh-zen-settingsShortcut {
1287
+ font-size: 12px;
1288
+ line-height: 18px;
1289
+ color: var(--dsw-alias-label-caption);
1290
+ margin-bottom: 12px;
1291
+ font-variant-numeric: tabular-nums;
1292
+ }
1293
+
1294
+ /* \u2500\u2500 Zen stats card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1295
+
1296
+ .dsh-zen-statsCard {
1297
+ padding: 16px;
1298
+ border-radius: 12px;
1299
+ background: var(--dsw-alias-interactive-bg-hover);
1300
+ margin-bottom: 12px;
1301
+ }
1302
+
1303
+ .dsh-zen-statsGrid {
1304
+ display: grid;
1305
+ grid-template-columns: 1fr 1fr;
1306
+ gap: 16px;
1307
+ }
1308
+
1309
+ .dsh-zen-statBox {
1310
+ display: flex;
1311
+ flex-direction: column;
1312
+ gap: 4px;
1313
+ }
1314
+
1315
+ .dsh-zen-statLabel {
1316
+ font-size: 12px;
1317
+ color: var(--dsw-alias-label-caption);
1318
+ }
1319
+
1320
+ .dsh-zen-statValue {
1321
+ font-size: 24px;
1322
+ font-weight: 700;
1323
+ color: var(--dsw-alias-label-primary);
1324
+ font-variant-numeric: tabular-nums;
1325
+ line-height: 32px;
1326
+ }
1327
+
1328
+ .dsh-zen-statSub {
1329
+ font-size: 11px;
1330
+ color: var(--dsw-alias-label-tertiary);
1331
+ }
1332
+
1333
+ .dsh-zen-levelRow {
1334
+ margin-top: 16px;
1335
+ padding-top: 12px;
1336
+ border-top: 1px solid var(--dsw-alias-border-l2);
1337
+ display: flex;
1338
+ align-items: center;
1339
+ justify-content: space-between;
1340
+ }
1341
+
1342
+ .dsh-zen-levelLeft {
1343
+ display: flex;
1344
+ flex-direction: column;
1345
+ gap: 2px;
1346
+ }
1347
+
1348
+ .dsh-zen-levelName {
1349
+ font-size: 15px;
1350
+ font-weight: 600;
1351
+ color: var(--dsw-alias-label-primary);
1352
+ }
1353
+
1354
+ .dsh-zen-levelDesc {
1355
+ font-size: 12px;
1356
+ color: var(--dsw-alias-label-caption);
1357
+ }
1358
+
1359
+ .dsh-zen-levelBadge {
1360
+ font-size: 28px;
1361
+ line-height: 1;
1362
+ }
1363
+
1364
+ /* \u2500\u2500 Toggle items \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1365
+
1366
+ .dsh-zen-settingsItem {
1367
+ display: flex;
1368
+ align-items: center;
1369
+ justify-content: space-between;
1370
+ padding: 10px 0;
1371
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
1372
+ }
1373
+
1374
+ .dsh-zen-settingsItem:last-child {
1375
+ border-bottom: none;
1376
+ }
1377
+
1378
+ .dsh-zen-settingsItemLabel {
1379
+ font-size: 14px;
1380
+ color: var(--dsw-alias-label-secondary);
1381
+ }
1382
+
1383
+ .dsh-zen-settingsSwitch {
1384
+ width: 36px;
1385
+ height: 20px;
1386
+ border-radius: 10px;
1387
+ border: none;
1388
+ cursor: pointer;
1389
+ position: relative;
1390
+ transition: background 0.15s;
1391
+ flex: none;
1392
+ }
1393
+
1394
+ .dsh-zen-settingsSwitch[data-on="true"] {
1395
+ background: var(--dsw-alias-state-business-primary);
1396
+ }
1397
+
1398
+ .dsh-zen-settingsSwitch[data-on="false"] {
1399
+ background: var(--dsw-alias-interactive-bg-hover-solid);
1400
+ }
1401
+
1402
+ .dsh-zen-settingsSwitchKnob {
1403
+ width: 16px;
1404
+ height: 16px;
1405
+ border-radius: 50%;
1406
+ background: #fff;
1407
+ position: absolute;
1408
+ top: 2px;
1409
+ left: 2px;
1410
+ transition: left 0.15s;
1411
+ }
1412
+
1413
+ .dsh-zen-settingsSwitch[data-on="true"] .dsh-zen-settingsSwitchKnob {
1414
+ left: 18px;
1415
+ }
1416
+ `;
1417
+ if (typeof document !== "undefined" && !document.querySelector('style[data-plugin-css="dsh-zen/settings"]')) {
1418
+ const tag = document.createElement("style");
1419
+ tag.setAttribute("data-plugin-css", "dsh-zen/settings");
1420
+ tag.textContent = CSS3;
1421
+ document.head.appendChild(tag);
1422
+ }
1423
+ var ZEN_LEVELS = [
1424
+ { min: 95, emoji: "\u{1F9D8}", nameZh: "\u7985\u5B97\u5927\u5E08", nameEn: "Zen Master", descZh: "\u51E0\u4E4E\u4E0D\u76EF\u5C4F\uFF0C\u6DF1\u5F97\u7985\u610F", descEn: "Barely glanced at the screen" },
1425
+ { min: 90, emoji: "\u{1F375}", nameZh: "\u8336\u9053\u884C\u8005", nameEn: "Tea Adept", descZh: "\u60A0\u95F2\u81EA\u5728\uFF0C\u76EF\u5C4F\u6781\u5C11", descEn: "Relaxed and minimal screen time" },
1426
+ { min: 80, emoji: "\u{1F33F}", nameZh: "\u9752\u82D4\u9690\u58EB", nameEn: "Moss Hermit", descZh: "\u6DE1\u5B9A\u4ECE\u5BB9\uFF0C\u5076\u5C14\u7784\u4E00\u773C", descEn: "Calm, checking in occasionally" },
1427
+ { min: 60, emoji: "\u{1F300}", nameZh: "\u534A\u9192\u534A\u68A6", nameEn: "Half Dreaming", descZh: "\u8FD8\u5728\u9002\u5E94\uFF0C\u4F1A\u5FCD\u4E0D\u4F4F\u770B\u770B", descEn: "Still adapting, peeking sometimes" },
1428
+ { min: 40, emoji: "\u{1F41D}", nameZh: "\u5FD9\u788C\u871C\u8702", nameEn: "Busy Bee", descZh: "\u76EF\u5C4F\u8F83\u591A\uFF0C\u8BD5\u7740\u653E\u624B\u5427", descEn: "Quite a bit of screen staring" },
1429
+ { min: 20, emoji: "\u{1F440}", nameZh: "\u76EF\u5C4F\u72C2\u9B54", nameEn: "Screen Goblin", descZh: "\u76EE\u4E0D\u8F6C\u775B\uFF0C\u8BE5\u6B47\u6B47\u4E86", descEn: "Eyes glued to the screen" },
1430
+ { min: 0, emoji: "\u{1F635}", nameZh: "\u5F7B\u5E95\u6CA6\u9677", nameEn: "Fully Lost", descZh: "\u5168\u7A0B\u76EF\u5C4F\uFF0C\u65E0\u836F\u53EF\u6551", descEn: "Glued to screen the entire time" }
1431
+ ];
1432
+ function getZenLevel(zenPct) {
1433
+ for (const lvl of ZEN_LEVELS) {
1434
+ if (zenPct >= lvl.min) return lvl;
1435
+ }
1436
+ return ZEN_LEVELS[ZEN_LEVELS.length - 1];
1437
+ }
1438
+ var DAY_MS = 24 * 60 * 60 * 1e3;
1439
+ function startOfToday() {
1440
+ const now = /* @__PURE__ */ new Date();
1441
+ now.setHours(0, 0, 0, 0);
1442
+ return now.getTime();
1443
+ }
1444
+ function startOfWeek() {
1445
+ const now = /* @__PURE__ */ new Date();
1446
+ const day = now.getDay();
1447
+ const diff = day === 0 ? 6 : day - 1;
1448
+ now.setDate(now.getDate() - diff);
1449
+ now.setHours(0, 0, 0, 0);
1450
+ return now.getTime();
1451
+ }
1452
+ function computePeriodStats(sessions, sinceMs, liveDelta) {
1453
+ let fgMs = 0;
1454
+ let totalMs = 0;
1455
+ const sinceKey = dayKeyOf(sinceMs);
1456
+ for (const stats of Object.values(sessions)) {
1457
+ const days = stats.days;
1458
+ if (!days) continue;
1459
+ for (const [dayKey, d] of Object.entries(days)) {
1460
+ if (dayKey < sinceKey) continue;
1461
+ const run = d.run ?? 0;
1462
+ const fg = Math.min(d.fg ?? 0, run);
1463
+ fgMs += fg;
1464
+ totalMs += run;
1465
+ }
1466
+ }
1467
+ fgMs += liveDelta.fgDelta;
1468
+ totalMs += liveDelta.runDelta;
1469
+ fgMs = Math.min(fgMs, totalMs);
1470
+ const ratio = totalMs > 0 ? Math.round(fgMs / totalMs * 100) : 0;
1471
+ const zenPct = 100 - ratio;
1472
+ return { fgMs, totalMs, ratio, zenPct };
1473
+ }
1474
+ var ITEMS = [
1475
+ { key: "showRunningStatus", labelKey: "settings.showRunningStatus" },
1476
+ { key: "showRunningSub", labelKey: "settings.showRunningSub" },
1477
+ { key: "showDoneStatus", labelKey: "settings.showDoneStatus" },
1478
+ { key: "showUserMessage", labelKey: "settings.showUserMessage" },
1479
+ { key: "showPrevReply", labelKey: "settings.showPrevReply" },
1480
+ { key: "showCurrentReply", labelKey: "settings.showCurrentReply" },
1481
+ { key: "showTurnStats", labelKey: "settings.showTurnStats" },
1482
+ { key: "showForegroundTooltip", labelKey: "settings.showForegroundTooltip" },
1483
+ { key: "autoEnterZen", labelKey: "settings.autoEnterZen" },
1484
+ { key: "autoExitZen", labelKey: "settings.autoExitZen" },
1485
+ { key: "animation", labelKey: "settings.animation" }
1486
+ ];
1487
+ var ZenSettingsSection = import_react3.default.memo(function ZenSettingsSection2(props) {
1488
+ const { zenSettingsStore, foregroundStore, t } = props;
1489
+ const [, forceUpdate] = import_react3.default.useReducer((c) => c + 1, 0);
1490
+ import_react3.default.useEffect(() => zenSettingsStore.subscribe(forceUpdate), [zenSettingsStore]);
1491
+ import_react3.default.useEffect(() => foregroundStore.subscribe(forceUpdate), [foregroundStore]);
1492
+ const settings = zenSettingsStore.getSnapshot();
1493
+ const statsState = foregroundStore.getSnapshot();
1494
+ const liveDelta = foregroundStore.getLiveMs();
1495
+ const todayStats = computePeriodStats(statsState.sessions, startOfToday(), liveDelta);
1496
+ const weekStats = computePeriodStats(statsState.sessions, startOfWeek(), liveDelta);
1497
+ const todayLevel = getZenLevel(todayStats.zenPct);
1498
+ const isZh = t("zen.zen") === "\u7985";
1499
+ const [, clockTick] = import_react3.default.useReducer((c) => c + 1, 0);
1500
+ import_react3.default.useEffect(() => {
1501
+ const id = setInterval(clockTick, 1e3);
1502
+ return () => clearInterval(id);
1503
+ }, []);
1504
+ return import_react3.default.createElement(
1505
+ "div",
1506
+ { className: "dsh-zen-settings" },
1507
+ import_react3.default.createElement("div", { className: "dsh-zen-settingsTitle" }, t("settings.title")),
1508
+ import_react3.default.createElement("div", { className: "dsh-zen-settingsDesc" }, t("settings.desc")),
1509
+ import_react3.default.createElement("div", { className: "dsh-zen-settingsShortcut" }, t("settings.shortcut")),
1510
+ // Zen stats card
1511
+ import_react3.default.createElement(
1512
+ "div",
1513
+ { className: "dsh-zen-statsCard" },
1514
+ import_react3.default.createElement(
1515
+ "div",
1516
+ { className: "dsh-zen-statsGrid" },
1517
+ import_react3.default.createElement(
1518
+ "div",
1519
+ { className: "dsh-zen-statBox" },
1520
+ import_react3.default.createElement("div", { className: "dsh-zen-statLabel" }, t("zen.todayZen")),
1521
+ import_react3.default.createElement("div", { className: "dsh-zen-statValue" }, todayStats.zenPct + "%"),
1522
+ import_react3.default.createElement("div", { className: "dsh-zen-statSub" }, `${t("zen.foreground")} ${formatDuration(todayStats.fgMs)} \xB7 ${t("zen.total")} ${formatDuration(todayStats.totalMs)}`)
1523
+ ),
1524
+ import_react3.default.createElement(
1525
+ "div",
1526
+ { className: "dsh-zen-statBox" },
1527
+ import_react3.default.createElement("div", { className: "dsh-zen-statLabel" }, t("zen.weekZen")),
1528
+ import_react3.default.createElement("div", { className: "dsh-zen-statValue" }, weekStats.zenPct + "%"),
1529
+ import_react3.default.createElement("div", { className: "dsh-zen-statSub" }, `${t("zen.foreground")} ${formatDuration(weekStats.fgMs)} \xB7 ${t("zen.total")} ${formatDuration(weekStats.totalMs)}`)
1530
+ )
1531
+ ),
1532
+ import_react3.default.createElement(
1533
+ "div",
1534
+ { className: "dsh-zen-levelRow" },
1535
+ import_react3.default.createElement(
1536
+ "div",
1537
+ { className: "dsh-zen-levelLeft" },
1538
+ import_react3.default.createElement("div", { className: "dsh-zen-levelName" }, isZh ? todayLevel.nameZh : todayLevel.nameEn),
1539
+ import_react3.default.createElement("div", { className: "dsh-zen-levelDesc" }, isZh ? todayLevel.descZh : todayLevel.descEn)
1540
+ ),
1541
+ import_react3.default.createElement("div", { className: "dsh-zen-levelBadge" }, todayLevel.emoji)
1542
+ )
1543
+ ),
1544
+ // Toggle items
1545
+ ITEMS.map(
1546
+ (item) => import_react3.default.createElement(
1547
+ "div",
1548
+ { key: item.key, className: "dsh-zen-settingsItem" },
1549
+ import_react3.default.createElement("span", { className: "dsh-zen-settingsItemLabel" }, t(item.labelKey)),
1550
+ import_react3.default.createElement(
1551
+ "button",
1552
+ {
1553
+ className: "dsh-zen-settingsSwitch",
1554
+ "data-on": String(settings[item.key]),
1555
+ onClick: () => zenSettingsStore.update({ [item.key]: !settings[item.key] })
1556
+ },
1557
+ import_react3.default.createElement("span", { className: "dsh-zen-settingsSwitchKnob" })
1558
+ )
1559
+ )
1560
+ )
1561
+ );
1562
+ });
1563
+
1564
+ // src/client.ts
1565
+ var name = "zen";
1566
+ var inject = ["slots", "sessions", "locale"];
1567
+ function initAutoZenSwitcher(ctx, zenSettingsStore) {
1568
+ const sessions = ctx.sessions;
1569
+ let prevRunning = null;
1570
+ let prevSessionId = null;
1571
+ function check() {
1572
+ const snap = sessions.list.getSnapshot();
1573
+ const currentId = snap.current;
1574
+ const session = currentId ? snap.byId[currentId] : null;
1575
+ const running = session?.running ?? false;
1576
+ if (currentId !== prevSessionId) {
1577
+ prevSessionId = currentId;
1578
+ prevRunning = running;
1579
+ return;
1580
+ }
1581
+ const settings = zenSettingsStore.getSnapshot();
1582
+ if (prevRunning === false && running === true) {
1583
+ if (settings.autoEnterZen) switchView("zen");
1584
+ } else if (prevRunning === true && running === false) {
1585
+ if (settings.autoExitZen) switchView("chat");
1586
+ }
1587
+ prevRunning = running;
1588
+ }
1589
+ const unsub = sessions.list.subscribe(check);
1590
+ check();
1591
+ ctx.effect(() => () => unsub(), "zen: auto zen switcher");
1592
+ }
1593
+ function initZenShortcut(ctx) {
1594
+ ctx.effect(() => {
1595
+ const onKeyDown = (e) => {
1596
+ if (!e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
1597
+ if (e.key !== "z" && e.key !== "Z") return;
1598
+ e.preventDefault();
1599
+ const inZen = typeof document !== "undefined" && document.querySelector(".dsh-zen-root") !== null;
1600
+ switchView(inZen ? "chat" : "zen");
1601
+ };
1602
+ document.addEventListener("keydown", onKeyDown);
1603
+ return () => document.removeEventListener("keydown", onKeyDown);
1604
+ }, "zen: alt+z toggle");
1605
+ }
1606
+ function apply(ctx) {
1607
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "zen: dictionaries");
1608
+ const t = ctx.locale.bind(NS);
1609
+ const foregroundStore = initForegroundTracker(ctx);
1610
+ const zenSettingsStore = createZenSettingsStore();
1611
+ initAutoZenSwitcher(ctx, zenSettingsStore);
1612
+ initZenShortcut(ctx);
1613
+ let MarkdownText = null;
1614
+ try {
1615
+ MarkdownText = require("@deepseek-ai/dsh-client-ui-primitives").MarkdownText;
1616
+ } catch {
1617
+ }
1618
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
1619
+ name: "conversation.view",
1620
+ id: "zen",
1621
+ order: 5,
1622
+ label: () => t("view.zen"),
1623
+ locale: NS,
1624
+ inject: (_sessionId) => ({
1625
+ foregroundStore,
1626
+ MarkdownText,
1627
+ zenSettingsStore
1628
+ })
1629
+ }, ZenView));
1630
+ ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
1631
+ name: "conversation.session.header.actions",
1632
+ id: "chat-hide-toggle",
1633
+ order: 50,
1634
+ locale: NS,
1635
+ inject: () => ({
1636
+ t
1637
+ })
1638
+ }, ChatHideToggle));
1639
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1640
+ name: "settings.section",
1641
+ id: "zen",
1642
+ order: 50,
1643
+ label: () => t("settings.title"),
1644
+ locale: NS,
1645
+ inject: () => ({
1646
+ zenSettingsStore,
1647
+ foregroundStore,
1648
+ t
1649
+ })
1650
+ }, ZenSettingsSection));
1651
+ }
1652
+
1653
+ return module.exports;
1654
+ }
1655
+ });