@tianyulinfeng1/dsh-plugin-deepseek-chat 1.1.1 → 1.1.2
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/CHANGELOG.md +137 -67
- package/README.en.md +169 -166
- package/README.md +189 -186
- package/docs/RELEASE_NOTES_v1.1.1.md +64 -64
- package/docs/RELEASE_NOTES_v1.1.2.md +149 -0
- package/docs/login-persistence.md +187 -187
- package/lib/client.js +948 -942
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +68 -67
- package/scripts/patch-dsh-persistent-partition.cjs +174 -174
- package/scripts/patch-dsh-persistent-partition.mjs +16 -16
package/lib/client.js
CHANGED
|
@@ -1,942 +1,948 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser half of dsh-plugin-deepseek-chat (v1.1.1).
|
|
3
|
-
*
|
|
4
|
-
* Registers one global left-sidebar panel — the same mechanism the Plugins and
|
|
5
|
-
* Scheduled tasks entries use:
|
|
6
|
-
* `sidebar.panellist` (list) contributes the icon row, and the root `main`
|
|
7
|
-
* keyed slot with the same key renders the panel page.
|
|
8
|
-
*
|
|
9
|
-
* The page hosts chat.deepseek.com in an Electron `<webview>`. The site sends
|
|
10
|
-
* `content-security-policy: frame-ancestors 'none'`, so an iframe cannot carry
|
|
11
|
-
* it; the desktop shell exposes `globalThis.dshDesktop.browser`, the same guest
|
|
12
|
-
* lease bridge the built-in sidebar browser uses: acquire a lease → create a
|
|
13
|
-
* webview named by the lease with its approved partition → load the URL.
|
|
14
|
-
*
|
|
15
|
-
* Stable login session (v1.1.1)
|
|
16
|
-
* -----------------------------
|
|
17
|
-
* The desktop shell caches ONE process-lifetime session per `workspace` string
|
|
18
|
-
* passed to acquire(), and release() destroys only the guest, never the
|
|
19
|
-
* partition's cookies. So the workspace MUST be an absolute constant:
|
|
20
|
-
* `panel:deepseek-chat`. v1.1.0 keyed it to an async account probe, which
|
|
21
|
-
* transiently fails during reconnects — every change handed the panel a fresh
|
|
22
|
-
* cookie jar and forced another QR scan. That regression is fixed here.
|
|
23
|
-
*
|
|
24
|
-
* Account awareness (display only)
|
|
25
|
-
* --------------------------------
|
|
26
|
-
* DSH's own sign-in is a platform.deepseek.com OAuth grant; the chat site uses
|
|
27
|
-
* a different, site-owned Token. There is no official exchange, and the
|
|
28
|
-
* renderer bridge exposes no token/cookie API, so a pure plugin cannot reuse
|
|
29
|
-
* the DSH token for the web chat (see docs/login-persistence.md §6). All this
|
|
30
|
-
* file does with the account is DISPLAY it:
|
|
31
|
-
*
|
|
32
|
-
* - a soft probe reads the Client `remote.account` namespace
|
|
33
|
-
* (`getState`/`getProfile`, generated `{ ok, value }` envelopes) and shows
|
|
34
|
-
* the state/name in a small strip;
|
|
35
|
-
* - the account state NEVER feeds the lease identity, the storage key, an
|
|
36
|
-
* epoch, or the guest lifecycle — no rebuild, no partition change, ever;
|
|
37
|
-
* - a transient `unknown` is ignored; only a confirmed `signed-out` updates
|
|
38
|
-
* the strip; a missing/late Remote never blocks the guest from starting.
|
|
39
|
-
*
|
|
40
|
-
* Failure containment is still the hard rule of this file. DSH treats any
|
|
41
|
-
* uncaught error thrown by a plugin as a crash of its slot entry, and a crash
|
|
42
|
-
* inside the `root` slot unmounts the entire application UI. Electron throws
|
|
43
|
-
* from webview methods such as `getURL()` / `loadURL()` as soon as the element
|
|
44
|
-
* leaves the document, and React runs effect cleanups after the DOM removal —
|
|
45
|
-
* so every guest call is attached-checked and wrapped, every event handler
|
|
46
|
-
* catches, a React error boundary keeps a render failure inside this panel, and
|
|
47
|
-
* a failure removes the dead guest before showing the in-panel Retry card.
|
|
48
|
-
*/
|
|
49
|
-
window.__ModuleLoader__.load({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
*
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (stopped) return;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
},
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
react.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
let
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
if (
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (
|
|
718
|
-
remember(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (event
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
["
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
} catch
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of dsh-plugin-deepseek-chat (v1.1.1).
|
|
3
|
+
*
|
|
4
|
+
* Registers one global left-sidebar panel — the same mechanism the Plugins and
|
|
5
|
+
* Scheduled tasks entries use:
|
|
6
|
+
* `sidebar.panellist` (list) contributes the icon row, and the root `main`
|
|
7
|
+
* keyed slot with the same key renders the panel page.
|
|
8
|
+
*
|
|
9
|
+
* The page hosts chat.deepseek.com in an Electron `<webview>`. The site sends
|
|
10
|
+
* `content-security-policy: frame-ancestors 'none'`, so an iframe cannot carry
|
|
11
|
+
* it; the desktop shell exposes `globalThis.dshDesktop.browser`, the same guest
|
|
12
|
+
* lease bridge the built-in sidebar browser uses: acquire a lease → create a
|
|
13
|
+
* webview named by the lease with its approved partition → load the URL.
|
|
14
|
+
*
|
|
15
|
+
* Stable login session (v1.1.1)
|
|
16
|
+
* -----------------------------
|
|
17
|
+
* The desktop shell caches ONE process-lifetime session per `workspace` string
|
|
18
|
+
* passed to acquire(), and release() destroys only the guest, never the
|
|
19
|
+
* partition's cookies. So the workspace MUST be an absolute constant:
|
|
20
|
+
* `panel:deepseek-chat`. v1.1.0 keyed it to an async account probe, which
|
|
21
|
+
* transiently fails during reconnects — every change handed the panel a fresh
|
|
22
|
+
* cookie jar and forced another QR scan. That regression is fixed here.
|
|
23
|
+
*
|
|
24
|
+
* Account awareness (display only)
|
|
25
|
+
* --------------------------------
|
|
26
|
+
* DSH's own sign-in is a platform.deepseek.com OAuth grant; the chat site uses
|
|
27
|
+
* a different, site-owned Token. There is no official exchange, and the
|
|
28
|
+
* renderer bridge exposes no token/cookie API, so a pure plugin cannot reuse
|
|
29
|
+
* the DSH token for the web chat (see docs/login-persistence.md §6). All this
|
|
30
|
+
* file does with the account is DISPLAY it:
|
|
31
|
+
*
|
|
32
|
+
* - a soft probe reads the Client `remote.account` namespace
|
|
33
|
+
* (`getState`/`getProfile`, generated `{ ok, value }` envelopes) and shows
|
|
34
|
+
* the state/name in a small strip;
|
|
35
|
+
* - the account state NEVER feeds the lease identity, the storage key, an
|
|
36
|
+
* epoch, or the guest lifecycle — no rebuild, no partition change, ever;
|
|
37
|
+
* - a transient `unknown` is ignored; only a confirmed `signed-out` updates
|
|
38
|
+
* the strip; a missing/late Remote never blocks the guest from starting.
|
|
39
|
+
*
|
|
40
|
+
* Failure containment is still the hard rule of this file. DSH treats any
|
|
41
|
+
* uncaught error thrown by a plugin as a crash of its slot entry, and a crash
|
|
42
|
+
* inside the `root` slot unmounts the entire application UI. Electron throws
|
|
43
|
+
* from webview methods such as `getURL()` / `loadURL()` as soon as the element
|
|
44
|
+
* leaves the document, and React runs effect cleanups after the DOM removal —
|
|
45
|
+
* so every guest call is attached-checked and wrapped, every event handler
|
|
46
|
+
* catches, a React error boundary keeps a render failure inside this panel, and
|
|
47
|
+
* a failure removes the dead guest before showing the in-panel Retry card.
|
|
48
|
+
*/
|
|
49
|
+
window.__ModuleLoader__.load({
|
|
50
|
+
// MUST equal package.json "name" (the scoped npm name). The Host keys every
|
|
51
|
+
// client graph row by the package name and, after executing a bundle script,
|
|
52
|
+
// checks `factories.has(<row id>)`. A mismatched id makes arrive() think the
|
|
53
|
+
// bundle registered nothing, re-execute the same script, and throw
|
|
54
|
+
// "duplicate factory registration ... (bundle executed twice without
|
|
55
|
+
// invalidate?)" during web boot. See docs/RELEASE_NOTES_v1.1.1.md.
|
|
56
|
+
id: "@tianyulinfeng1/dsh-plugin-deepseek-chat",
|
|
57
|
+
factory: (require) => {
|
|
58
|
+
var module = { exports: {} };
|
|
59
|
+
var exports = module.exports;
|
|
60
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
61
|
+
const react = require("react");
|
|
62
|
+
|
|
63
|
+
const PANEL_ID = "deepseek-chat";
|
|
64
|
+
/**
|
|
65
|
+
* DSH runtime version sent as `x-client-version` on Platform account
|
|
66
|
+
* reads. The boot manifest carries the exact connected release; the
|
|
67
|
+
* minimum supported release is the fallback for a page without it.
|
|
68
|
+
*/
|
|
69
|
+
const HOST_VERSION_FALLBACK = "0.2.2-rc.1";
|
|
70
|
+
const CHAT_URL = "https://chat.deepseek.com/";
|
|
71
|
+
/**
|
|
72
|
+
* Constant storage identities for the guest partition and the
|
|
73
|
+
* last-conversation memory. The main process keeps one partition per
|
|
74
|
+
* `workspace` string for the process lifetime; both values MUST NOT
|
|
75
|
+
* depend on account state, or a transient probe failure would swap the
|
|
76
|
+
* cookie jar and force a fresh QR scan. v1.1.1 regression fix.
|
|
77
|
+
*/
|
|
78
|
+
const PARTITION_BASE = "panel:deepseek-chat";
|
|
79
|
+
const STORAGE_BASE = "dsh-plugin-deepseek-chat:last-url";
|
|
80
|
+
/** Paths that are not a conversation: a brand-new chat, or an auth page. */
|
|
81
|
+
const NON_CONVERSATION_PATHS = new Set(["/", "/sign_in", "/login", "/signup", "/auth"]);
|
|
82
|
+
|
|
83
|
+
const english = typeof navigator !== "undefined" && /^en\b/i.test(navigator.language ?? "");
|
|
84
|
+
const COPY = english ? {
|
|
85
|
+
label: "DeepSeek Chat",
|
|
86
|
+
loading: "Loading DeepSeek Chat…",
|
|
87
|
+
unsupported: "The embedded chat needs the desktop app.",
|
|
88
|
+
failed: "The chat page could not be opened",
|
|
89
|
+
crashed: "The embedded chat stopped responding",
|
|
90
|
+
timeout: "The embedded chat did not start",
|
|
91
|
+
retry: "Retry",
|
|
92
|
+
probing: "Syncing the DSH account…",
|
|
93
|
+
synced: "Synced with the DSH account",
|
|
94
|
+
syncedAs: (name) => `Synced with the DSH account · ${name}`,
|
|
95
|
+
unsigned: "No DSH account signed in — the chat panel is not synced",
|
|
96
|
+
unknown: "Account state unavailable — conversation kept locally"
|
|
97
|
+
} : {
|
|
98
|
+
label: "DeepSeek 聊天",
|
|
99
|
+
loading: "正在加载 DeepSeek 聊天…",
|
|
100
|
+
unsupported: "内嵌聊天窗口需要桌面版 App。",
|
|
101
|
+
failed: "聊天页面打开失败",
|
|
102
|
+
crashed: "内嵌聊天进程已退出",
|
|
103
|
+
timeout: "内嵌聊天页面没有启动",
|
|
104
|
+
retry: "重试",
|
|
105
|
+
probing: "正在同步 DSH 登录账号…",
|
|
106
|
+
synced: "已与 DSH 登录账号同步",
|
|
107
|
+
syncedAs: (name) => `已与 DSH 登录账号同步 · ${name}`,
|
|
108
|
+
unsigned: "未登录 DSH 账号,聊天面板暂未同步",
|
|
109
|
+
unknown: "账号状态不可用,会话按本地模式保存"
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Normalized account states the panel can distinguish. */
|
|
113
|
+
const ACCOUNT_UNKNOWN = "unknown";
|
|
114
|
+
const ACCOUNT_SIGNED_IN = "signed-in";
|
|
115
|
+
const ACCOUNT_SIGNED_OUT = "signed-out";
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ONE stable guest partition for the whole process.
|
|
119
|
+
*
|
|
120
|
+
* The desktop shell keys a process-lifetime partition Map by the
|
|
121
|
+
* `workspace` string passed to acquire() and reuses the same session for
|
|
122
|
+
* every acquire() with that key. release() destroys only the guest, never
|
|
123
|
+
* the partition's cookies. Consequently, changing this value — even once
|
|
124
|
+
* — hands the panel a fresh cookie jar and forces the user to scan the
|
|
125
|
+
* chat login QR code again. It MUST NOT depend on the DSH account probe:
|
|
126
|
+
* that probe is asynchronous, runs every 5 seconds, and can transiently
|
|
127
|
+
* return "unknown" during a reconnect or session switch.
|
|
128
|
+
* @returns the fixed workspace identity for guest leases.
|
|
129
|
+
*/
|
|
130
|
+
function partitionKeyFor() {
|
|
131
|
+
return PARTITION_BASE;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* ONE stable conversation-memory key. Account status changes must not
|
|
136
|
+
* move it either, or a transient probe failure would silently lose the
|
|
137
|
+
* remembered conversation.
|
|
138
|
+
* @returns the fixed localStorage key.
|
|
139
|
+
*/
|
|
140
|
+
function storageKeyFor() {
|
|
141
|
+
return STORAGE_BASE;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Parse a chat.deepseek.com page URL.
|
|
146
|
+
* @param value - candidate URL.
|
|
147
|
+
* @returns the parsed URL for the site origin, or undefined.
|
|
148
|
+
*/
|
|
149
|
+
function parseChatUrl(value) {
|
|
150
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) return void 0;
|
|
151
|
+
try {
|
|
152
|
+
const url = new URL(value);
|
|
153
|
+
if (url.protocol !== "https:" || url.hostname !== "chat.deepseek.com") return void 0;
|
|
154
|
+
if (url.username !== "" || url.password !== "" || url.port !== "") return void 0;
|
|
155
|
+
return url;
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Destinations a guest popup may open. The chat site and the Platform
|
|
163
|
+
* login pages are the only hosts this panel deliberately navigates to;
|
|
164
|
+
* an arbitrary URL from the open-request bridge is never loaded.
|
|
165
|
+
* @param value - candidate URL.
|
|
166
|
+
* @returns the normalized URL, or undefined.
|
|
167
|
+
*/
|
|
168
|
+
function parsePopupUrl(value) {
|
|
169
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) return void 0;
|
|
170
|
+
try {
|
|
171
|
+
const url = new URL(value);
|
|
172
|
+
if (url.protocol !== "https:") return void 0;
|
|
173
|
+
if (url.username !== "" || url.password !== "" || url.port !== "") return void 0;
|
|
174
|
+
if (url.hostname !== "chat.deepseek.com" && url.hostname !== "platform.deepseek.com") return void 0;
|
|
175
|
+
return url.toString();
|
|
176
|
+
} catch {
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Whether a URL deep enough to be a conversation page.
|
|
183
|
+
* @param url - parsed chat URL, possibly undefined.
|
|
184
|
+
* @returns true for a conversation, false for the new-chat root or an auth page.
|
|
185
|
+
*/
|
|
186
|
+
function isConversation(url) {
|
|
187
|
+
if (url === void 0) return false;
|
|
188
|
+
const path = url.pathname.replace(/\/+$/u, "") || "/";
|
|
189
|
+
return !NON_CONVERSATION_PATHS.has(path);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Read the conversation remembered from the last visit for one account.
|
|
194
|
+
* @param storageKey - account-scoped storage key.
|
|
195
|
+
* @returns the URL to open; the site root when nothing usable is remembered.
|
|
196
|
+
*/
|
|
197
|
+
function rememberedTarget(storageKey) {
|
|
198
|
+
try {
|
|
199
|
+
const saved = parseChatUrl(window.localStorage.getItem(storageKey));
|
|
200
|
+
return isConversation(saved) ? saved.toString() : CHAT_URL;
|
|
201
|
+
} catch {
|
|
202
|
+
return CHAT_URL;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Remember where the guest currently is.
|
|
208
|
+
*
|
|
209
|
+
* A brand-new chat clears the memory so the next visit starts clean. An auth
|
|
210
|
+
* page never overwrites it, so a login detour does not lose the conversation.
|
|
211
|
+
* @param raw - current page URL reported by the guest.
|
|
212
|
+
* @param storageKey - account-scoped storage key.
|
|
213
|
+
*/
|
|
214
|
+
function rememberTarget(raw, storageKey) {
|
|
215
|
+
const url = parseChatUrl(raw);
|
|
216
|
+
if (url === void 0) return;
|
|
217
|
+
try {
|
|
218
|
+
const path = url.pathname.replace(/\/+$/u, "") || "/";
|
|
219
|
+
if (isConversation(url)) window.localStorage.setItem(storageKey, url.toString());
|
|
220
|
+
else if (path === "/") window.localStorage.removeItem(storageKey);
|
|
221
|
+
} catch {
|
|
222
|
+
/* storage unavailable: the panel still works, it just forgets */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── account sync store ────────────────────────────────────────────────
|
|
227
|
+
/**
|
|
228
|
+
* One module-level snapshot shared by every mounted panel. The account
|
|
229
|
+
* Remote is display/awareness only; it never feeds the lease identity or
|
|
230
|
+
* the guest lifecycle, so a flaky probe can never recreate the webview.
|
|
231
|
+
*/
|
|
232
|
+
let accountSnapshot = { status: ACCOUNT_UNKNOWN, label: "", probed: false };
|
|
233
|
+
const accountListeners = new Set();
|
|
234
|
+
|
|
235
|
+
function notifyAccount() {
|
|
236
|
+
for (const listener of [...accountListeners]) {
|
|
237
|
+
try {
|
|
238
|
+
listener(accountSnapshot);
|
|
239
|
+
} catch {
|
|
240
|
+
/* a dead panel must not break another one */
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Subscribe a mounted panel; returns its disposer. */
|
|
246
|
+
function subscribeAccount(listener) {
|
|
247
|
+
accountListeners.add(listener);
|
|
248
|
+
return () => {
|
|
249
|
+
accountListeners.delete(listener);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Publish a probed account state. This ONLY updates the account strip:
|
|
255
|
+
* it never bumps an epoch and never rebuilds the guest. A transient
|
|
256
|
+
* "unknown" during reconnect is deliberately ignored so the displayed
|
|
257
|
+
* state does not flicker; a confirmed sign-out is shown immediately.
|
|
258
|
+
* @param status - normalized account state.
|
|
259
|
+
*/
|
|
260
|
+
function publishAccount(status) {
|
|
261
|
+
const previous = accountSnapshot;
|
|
262
|
+
// Ignore a transient unknown once we know a real state: it is usually
|
|
263
|
+
// a reconnect, not a sign-out, and must not disturb the panel.
|
|
264
|
+
const settled = status === ACCOUNT_UNKNOWN && previous.probed && previous.status !== ACCOUNT_UNKNOWN
|
|
265
|
+
? previous.status
|
|
266
|
+
: status;
|
|
267
|
+
accountSnapshot = {
|
|
268
|
+
status: settled,
|
|
269
|
+
// Preserve the name through a transient unknown; clear it only on a
|
|
270
|
+
// confirmed sign-out when the display explicitly publishes "".
|
|
271
|
+
label: settled === ACCOUNT_SIGNED_OUT ? "" : previous.label,
|
|
272
|
+
probed: true
|
|
273
|
+
};
|
|
274
|
+
notifyAccount();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Publish a display name; a stale result from an older probe is dropped. */
|
|
278
|
+
function publishAccountLabel(label) {
|
|
279
|
+
if (accountSnapshot.label === label) return;
|
|
280
|
+
accountSnapshot = { ...accountSnapshot, label };
|
|
281
|
+
notifyAccount();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Read one generated `ctx.remote.account` method. Generated Remotes answer
|
|
286
|
+
* with an `{ ok, value, error }` envelope, so a bare payload is never
|
|
287
|
+
* assumed; an absent namespace or a refused call degrades to undefined.
|
|
288
|
+
* @param ctx - client plugin context.
|
|
289
|
+
* @param method - generated account method name.
|
|
290
|
+
* @param args - method arguments.
|
|
291
|
+
* @returns the envelope's `value`, or undefined when unavailable.
|
|
292
|
+
*/
|
|
293
|
+
async function callAccount(ctx, method, args = []) {
|
|
294
|
+
let remote;
|
|
295
|
+
try {
|
|
296
|
+
remote = typeof ctx.get === "function" ? ctx.get("remote") : ctx.remote;
|
|
297
|
+
} catch {
|
|
298
|
+
remote = void 0;
|
|
299
|
+
}
|
|
300
|
+
const account = remote?.account;
|
|
301
|
+
const call = account?.[method];
|
|
302
|
+
if (typeof call !== "function") return void 0;
|
|
303
|
+
const result = await call.apply(account, args);
|
|
304
|
+
if (result === null || typeof result !== "object" || result.ok !== true) return void 0;
|
|
305
|
+
return result.value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Platform client identity headers, read at call time as the UI does. */
|
|
309
|
+
function accountClient(ctx) {
|
|
310
|
+
return {
|
|
311
|
+
version: globalThis.__DSH_BOOT__?.release?.version || HOST_VERSION_FALLBACK,
|
|
312
|
+
locale: typeof navigator !== "undefined" ? navigator.language || "en-US" : "en-US",
|
|
313
|
+
timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Read the live DSH account state and display name. This is awareness
|
|
319
|
+
* ONLY: no value here may ever feed the guest lease or rebuild the
|
|
320
|
+
* webview, or a transient Remote failure would drop the chat session.
|
|
321
|
+
* @param ctx - client plugin context.
|
|
322
|
+
* @returns `{ status, label }`.
|
|
323
|
+
*/
|
|
324
|
+
async function probeAccount(ctx) {
|
|
325
|
+
let state;
|
|
326
|
+
try {
|
|
327
|
+
state = await callAccount(ctx, "getState");
|
|
328
|
+
} catch {
|
|
329
|
+
state = void 0;
|
|
330
|
+
}
|
|
331
|
+
if (state?.status !== "credential-stored") {
|
|
332
|
+
return {
|
|
333
|
+
status: state?.status === "signed-out" ? ACCOUNT_SIGNED_OUT : ACCOUNT_UNKNOWN,
|
|
334
|
+
label: ""
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const probe = { status: ACCOUNT_SIGNED_IN, label: "" };
|
|
338
|
+
try {
|
|
339
|
+
const outcome = await callAccount(ctx, "getProfile", [accountClient(ctx)]);
|
|
340
|
+
const value = outcome?.status === "ready" ? outcome.value : void 0;
|
|
341
|
+
probe.label = [value?.name, value?.contact, value?.id]
|
|
342
|
+
.find((item) => typeof item === "string" && item.length > 0) ?? "";
|
|
343
|
+
} catch {
|
|
344
|
+
/* profile is display-only: failure keeps the bare state */
|
|
345
|
+
}
|
|
346
|
+
return probe;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Start the account watcher once per plugin activation. A 5-second poll
|
|
351
|
+
* plus `connection/reset` gives self-heal without depending on the exact
|
|
352
|
+
* Client shape of the account `watch` stream, and every resource is
|
|
353
|
+
* released through the plugin's own ctx effect.
|
|
354
|
+
* @param ctx - client plugin context.
|
|
355
|
+
*/
|
|
356
|
+
let accountSyncStarted = false;
|
|
357
|
+
function startAccountSync(ctx) {
|
|
358
|
+
if (accountSyncStarted) return;
|
|
359
|
+
accountSyncStarted = true;
|
|
360
|
+
let stopped = false;
|
|
361
|
+
let timer;
|
|
362
|
+
|
|
363
|
+
const refresh = async () => {
|
|
364
|
+
if (stopped) return;
|
|
365
|
+
let probe = { status: ACCOUNT_UNKNOWN, label: "" };
|
|
366
|
+
try {
|
|
367
|
+
probe = await probeAccount(ctx);
|
|
368
|
+
} catch {
|
|
369
|
+
/* Remote unavailable: publishAccount ignores a transient unknown */
|
|
370
|
+
probe = { status: ACCOUNT_UNKNOWN, label: "" };
|
|
371
|
+
}
|
|
372
|
+
if (stopped) return;
|
|
373
|
+
publishAccount(probe.status);
|
|
374
|
+
if (probe.label !== "") publishAccountLabel(probe.label);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const schedule = () => {
|
|
378
|
+
if (stopped) return;
|
|
379
|
+
timer = setTimeout(() => {
|
|
380
|
+
void refresh().finally(schedule);
|
|
381
|
+
}, 5000);
|
|
382
|
+
// A long-lived page keeps the poll; a minimal context (or a Node
|
|
383
|
+
// self-check) must not be held open by its timer alone.
|
|
384
|
+
try {
|
|
385
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
386
|
+
} catch {
|
|
387
|
+
/* not a Node timer */
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
void refresh().finally(schedule);
|
|
392
|
+
|
|
393
|
+
let offReset;
|
|
394
|
+
try {
|
|
395
|
+
if (typeof ctx.on === "function") offReset = ctx.on("connection/reset", () => {
|
|
396
|
+
void refresh();
|
|
397
|
+
});
|
|
398
|
+
} catch {
|
|
399
|
+
/* optional: the poll still self-heals */
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let hasDisposal = false;
|
|
403
|
+
try {
|
|
404
|
+
if (typeof ctx.effect === "function") {
|
|
405
|
+
ctx.effect(() => () => {
|
|
406
|
+
stopped = true;
|
|
407
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
408
|
+
try {
|
|
409
|
+
if (typeof offReset === "function") offReset();
|
|
410
|
+
} catch {
|
|
411
|
+
/* already detached */
|
|
412
|
+
}
|
|
413
|
+
accountListeners.clear();
|
|
414
|
+
accountSyncStarted = false;
|
|
415
|
+
}, "deepseek-chat: account sync");
|
|
416
|
+
hasDisposal = true;
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
/* fall through to the no-disposal path */
|
|
420
|
+
}
|
|
421
|
+
if (!hasDisposal) {
|
|
422
|
+
stopped = true;
|
|
423
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
424
|
+
try {
|
|
425
|
+
if (typeof offReset === "function") offReset();
|
|
426
|
+
} catch {
|
|
427
|
+
/* already detached */
|
|
428
|
+
}
|
|
429
|
+
// Without a real ctx effect there is no teardown to stop the poll;
|
|
430
|
+
// keep the first probe but never leave a repeating timer behind.
|
|
431
|
+
accountSyncStarted = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ── guest helpers ─────────────────────────────────────────────────────
|
|
436
|
+
/**
|
|
437
|
+
* Whether the guest element is still in the document. Electron's webview
|
|
438
|
+
* methods throw once it is detached, so this gates every guest call.
|
|
439
|
+
* @param element - webview element.
|
|
440
|
+
* @returns true while the element can still host a guest.
|
|
441
|
+
*/
|
|
442
|
+
function attached(element) {
|
|
443
|
+
try {
|
|
444
|
+
return element.isConnected === true;
|
|
445
|
+
} catch {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Guest URL, or undefined when the element cannot report one. */
|
|
451
|
+
function readUrl(element) {
|
|
452
|
+
if (!attached(element)) return void 0;
|
|
453
|
+
try {
|
|
454
|
+
const url = element.getURL();
|
|
455
|
+
return typeof url === "string" && url.length > 0 ? url : void 0;
|
|
456
|
+
} catch {
|
|
457
|
+
return void 0;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Navigate the guest without ever throwing.
|
|
463
|
+
* @returns false when the guest is gone, so the caller can report it.
|
|
464
|
+
*/
|
|
465
|
+
function navigate(element, url) {
|
|
466
|
+
if (!attached(element)) return false;
|
|
467
|
+
try {
|
|
468
|
+
const pending = element.loadURL(url);
|
|
469
|
+
if (pending !== void 0 && pending !== null && typeof pending.catch === "function") pending.catch(() => {});
|
|
470
|
+
return true;
|
|
471
|
+
} catch {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Panel-list glyph, drawn on currentColor so the sidebar's active state owns
|
|
478
|
+
* the ink. Matches the sidebar's 16/18px icon seat.
|
|
479
|
+
*/
|
|
480
|
+
function ChatGlyph({ size = 18 }) {
|
|
481
|
+
return react.createElement("svg", {
|
|
482
|
+
width: size,
|
|
483
|
+
height: size,
|
|
484
|
+
viewBox: "0 0 24 24",
|
|
485
|
+
fill: "none",
|
|
486
|
+
"aria-hidden": "true",
|
|
487
|
+
focusable: "false"
|
|
488
|
+
}, react.createElement("path", {
|
|
489
|
+
d: "M4.5 5.75C4.5 4.784 5.284 4 6.25 4h11.5c.966 0 1.75.784 1.75 1.75v8.5c0 .966-.784 1.75-1.75 1.75H9.56l-3.34 2.784c-.497.414-1.22.06-1.22-.586V5.75Z",
|
|
490
|
+
stroke: "currentColor",
|
|
491
|
+
strokeWidth: 1.3,
|
|
492
|
+
strokeLinejoin: "round"
|
|
493
|
+
}), react.createElement("path", {
|
|
494
|
+
d: "M8.5 9.25h7M8.5 12.25h4.5",
|
|
495
|
+
stroke: "currentColor",
|
|
496
|
+
strokeWidth: 1.3,
|
|
497
|
+
strokeLinecap: "round"
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const styles = {
|
|
502
|
+
root: {
|
|
503
|
+
display: "flex",
|
|
504
|
+
flexDirection: "column",
|
|
505
|
+
height: "100%",
|
|
506
|
+
minHeight: "0",
|
|
507
|
+
background: "var(--dsw-alias-bg-base)",
|
|
508
|
+
color: "var(--dsw-alias-label-primary)"
|
|
509
|
+
},
|
|
510
|
+
accountBar: {
|
|
511
|
+
flex: "none",
|
|
512
|
+
display: "flex",
|
|
513
|
+
alignItems: "center",
|
|
514
|
+
gap: "6px",
|
|
515
|
+
minHeight: "24px",
|
|
516
|
+
padding: "3px 10px",
|
|
517
|
+
borderBottom: ".5px solid var(--dsw-alias-border-l3)",
|
|
518
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
519
|
+
font: "var(--dsw-font-xxxs-11)"
|
|
520
|
+
},
|
|
521
|
+
accountDot: {
|
|
522
|
+
width: "6px",
|
|
523
|
+
height: "6px",
|
|
524
|
+
borderRadius: "50%",
|
|
525
|
+
background: "currentColor",
|
|
526
|
+
flex: "none"
|
|
527
|
+
},
|
|
528
|
+
host: {
|
|
529
|
+
position: "relative",
|
|
530
|
+
flex: "1",
|
|
531
|
+
minHeight: "0",
|
|
532
|
+
display: "flex"
|
|
533
|
+
},
|
|
534
|
+
guest: {
|
|
535
|
+
width: "100%",
|
|
536
|
+
height: "100%",
|
|
537
|
+
border: "0",
|
|
538
|
+
background: "transparent",
|
|
539
|
+
display: "flex"
|
|
540
|
+
},
|
|
541
|
+
notice: {
|
|
542
|
+
position: "absolute",
|
|
543
|
+
inset: "0",
|
|
544
|
+
display: "flex",
|
|
545
|
+
flexDirection: "column",
|
|
546
|
+
alignItems: "center",
|
|
547
|
+
justifyContent: "center",
|
|
548
|
+
gap: "10px",
|
|
549
|
+
padding: "24px",
|
|
550
|
+
textAlign: "center",
|
|
551
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
552
|
+
font: "var(--dsw-font-xxs-12)",
|
|
553
|
+
background: "var(--dsw-alias-bg-base)"
|
|
554
|
+
},
|
|
555
|
+
button: {
|
|
556
|
+
color: "var(--dsw-alias-label-primary)",
|
|
557
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
558
|
+
border: ".5px solid var(--dsw-alias-border-l3)",
|
|
559
|
+
borderRadius: "var(--dsw-radius-sm)",
|
|
560
|
+
padding: "4px 12px",
|
|
561
|
+
cursor: "pointer",
|
|
562
|
+
font: "var(--dsw-font-xxs-12)"
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/** Copy for the account strip; the dot's opacity encodes the state. */
|
|
567
|
+
function accountCopy(account) {
|
|
568
|
+
if (account.status === ACCOUNT_SIGNED_IN) return {
|
|
569
|
+
text: account.label ? COPY.syncedAs(account.label) : COPY.synced,
|
|
570
|
+
opacity: "1"
|
|
571
|
+
};
|
|
572
|
+
if (account.status === ACCOUNT_SIGNED_OUT) return { text: COPY.unsigned, opacity: ".45" };
|
|
573
|
+
if (!account.probed) return { text: COPY.probing, opacity: ".3" };
|
|
574
|
+
return { text: COPY.unknown, opacity: ".3" };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Keeps a rendering failure inside this panel. DSH has no boundary of its own
|
|
579
|
+
* around slot entries: an escaping error unmounts the root slot, which is the
|
|
580
|
+
* whole application UI.
|
|
581
|
+
*/
|
|
582
|
+
class PanelBoundary extends react.Component {
|
|
583
|
+
constructor(props) {
|
|
584
|
+
super(props);
|
|
585
|
+
this.state = { failure: void 0 };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
static getDerivedStateFromError(error) {
|
|
589
|
+
return { failure: String((error && error.message) || error) };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
componentDidCatch(error) {
|
|
593
|
+
try {
|
|
594
|
+
console.error("dsh-plugin-deepseek-chat: panel render failed", error);
|
|
595
|
+
} catch {
|
|
596
|
+
/* console unavailable */
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
render() {
|
|
601
|
+
if (this.state.failure !== void 0) {
|
|
602
|
+
return react.createElement("div", { style: styles.root },
|
|
603
|
+
react.createElement("div", { style: styles.notice },
|
|
604
|
+
react.createElement("div", null, `${COPY.failed}: ${this.state.failure}`)));
|
|
605
|
+
}
|
|
606
|
+
return this.props.children ?? null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* The global chat panel: an account strip and a guest webview filling the
|
|
612
|
+
* main column.
|
|
613
|
+
*
|
|
614
|
+
* The guest is created only after the lease resolves, and it is mounted with
|
|
615
|
+
* `name`/`partition`/`src` already set, because the main process inspects
|
|
616
|
+
* exactly that bootstrap src when it approves the attachment. The identity
|
|
617
|
+
* and the conversation memory are both scoped to the current DSH account;
|
|
618
|
+
* an account transition bumps `epoch`, which tears down the old guest and
|
|
619
|
+
* re-acquires a lease for the new identity.
|
|
620
|
+
*/
|
|
621
|
+
function ChatPanel() {
|
|
622
|
+
const hostRef = react.useRef(null);
|
|
623
|
+
const lastUrlRef = react.useRef(void 0);
|
|
624
|
+
const [account, setAccount] = react.useState(accountSnapshot);
|
|
625
|
+
const [failure, setFailure] = react.useState(void 0);
|
|
626
|
+
const [loading, setLoading] = react.useState(true);
|
|
627
|
+
const [attempt, setAttempt] = react.useState(0);
|
|
628
|
+
|
|
629
|
+
react.useEffect(() => subscribeAccount((next) => {
|
|
630
|
+
try {
|
|
631
|
+
setAccount(next);
|
|
632
|
+
} catch {
|
|
633
|
+
/* the panel is gone */
|
|
634
|
+
}
|
|
635
|
+
}), []);
|
|
636
|
+
|
|
637
|
+
const retry = react.useCallback(() => {
|
|
638
|
+
try {
|
|
639
|
+
setFailure(void 0);
|
|
640
|
+
setLoading(true);
|
|
641
|
+
setAttempt((value) => value + 1);
|
|
642
|
+
} catch {
|
|
643
|
+
/* the panel is gone */
|
|
644
|
+
}
|
|
645
|
+
}, []);
|
|
646
|
+
|
|
647
|
+
react.useEffect(() => {
|
|
648
|
+
const host = hostRef.current;
|
|
649
|
+
const bridge = globalThis.dshDesktop?.browser;
|
|
650
|
+
if (host === null) return;
|
|
651
|
+
if (bridge === void 0) {
|
|
652
|
+
try {
|
|
653
|
+
setFailure(COPY.unsupported);
|
|
654
|
+
setLoading(false);
|
|
655
|
+
} catch {
|
|
656
|
+
/* the panel is gone */
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Both identities are constants. The desktop shell caches one
|
|
662
|
+
// session per workspace string; keeping them fixed is what makes
|
|
663
|
+
// switching panels/sessions preserve the chat login.
|
|
664
|
+
const storageKey = storageKeyFor();
|
|
665
|
+
const partitionKey = partitionKeyFor();
|
|
666
|
+
let disposed = false;
|
|
667
|
+
let failed = false;
|
|
668
|
+
let unsubscribeOpen;
|
|
669
|
+
let lease;
|
|
670
|
+
let watchdog;
|
|
671
|
+
const element = document.createElement("webview");
|
|
672
|
+
const initialTarget = rememberedTarget(storageKey);
|
|
673
|
+
let restoring = initialTarget !== CHAT_URL;
|
|
674
|
+
|
|
675
|
+
Object.assign(element.style, styles.guest);
|
|
676
|
+
element.setAttribute("allowpopups", "");
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Report a guest failure inside the panel; never throw. The dead
|
|
680
|
+
* guest element is removed immediately so a Retry cannot stack a
|
|
681
|
+
* second webview, and later events are suppressed. The lease is
|
|
682
|
+
* still released by the effect cleanup, exactly once.
|
|
683
|
+
*/
|
|
684
|
+
const fail = (error) => {
|
|
685
|
+
if (disposed || failed) return;
|
|
686
|
+
failed = true;
|
|
687
|
+
try {
|
|
688
|
+
element.remove();
|
|
689
|
+
} catch {
|
|
690
|
+
/* already detached */
|
|
691
|
+
}
|
|
692
|
+
if (watchdog !== void 0) {
|
|
693
|
+
clearTimeout(watchdog);
|
|
694
|
+
watchdog = void 0;
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
setLoading(false);
|
|
698
|
+
setFailure(String((error && error.message) || error));
|
|
699
|
+
} catch {
|
|
700
|
+
/* the panel is gone */
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
/** Remember the last document the guest reported. */
|
|
705
|
+
const remember = (raw) => {
|
|
706
|
+
if (raw === void 0) return;
|
|
707
|
+
lastUrlRef.current = raw;
|
|
708
|
+
rememberTarget(raw, storageKey);
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const onDomReady = () => {
|
|
712
|
+
if (disposed || failed) return;
|
|
713
|
+
if (watchdog !== void 0) {
|
|
714
|
+
clearTimeout(watchdog);
|
|
715
|
+
watchdog = void 0;
|
|
716
|
+
}
|
|
717
|
+
if (!navigate(element, initialTarget)) fail(new Error(COPY.crashed));
|
|
718
|
+
else remember(initialTarget);
|
|
719
|
+
};
|
|
720
|
+
const onStopLoading = () => {
|
|
721
|
+
if (disposed || failed) return;
|
|
722
|
+
const raw = readUrl(element);
|
|
723
|
+
if (raw === void 0) return;
|
|
724
|
+
remember(raw);
|
|
725
|
+
restoring = false;
|
|
726
|
+
try {
|
|
727
|
+
setLoading(false);
|
|
728
|
+
} catch {
|
|
729
|
+
/* the panel is gone */
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
const onNavigate = () => {
|
|
733
|
+
if (disposed || failed) return;
|
|
734
|
+
remember(readUrl(element));
|
|
735
|
+
};
|
|
736
|
+
const onNavigateInPage = (event) => {
|
|
737
|
+
if (disposed || failed || event?.isMainFrame !== true) return;
|
|
738
|
+
onNavigate();
|
|
739
|
+
};
|
|
740
|
+
const onFailLoad = (event) => {
|
|
741
|
+
if (disposed || failed || event?.isMainFrame !== true) return;
|
|
742
|
+
// -3 is ERR_ABORTED: a superseded navigation, not a failure.
|
|
743
|
+
if (event.errorCode === -3) return;
|
|
744
|
+
if (restoring) {
|
|
745
|
+
// The remembered conversation is gone or unreachable; start clean.
|
|
746
|
+
restoring = false;
|
|
747
|
+
if (!navigate(element, CHAT_URL)) fail(new Error(COPY.crashed));
|
|
748
|
+
else remember(CHAT_URL);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
fail(`${event.errorDescription ?? event.errorCode}`);
|
|
752
|
+
};
|
|
753
|
+
/** The guest process exited or the guest was torn down underneath us. */
|
|
754
|
+
const onGuestGone = () => {
|
|
755
|
+
if (disposed || failed) return;
|
|
756
|
+
fail(new Error(COPY.crashed));
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const listeners = [
|
|
760
|
+
["dom-ready", onDomReady],
|
|
761
|
+
["did-stop-loading", onStopLoading],
|
|
762
|
+
["did-navigate", onNavigate],
|
|
763
|
+
["did-navigate-in-page", onNavigateInPage],
|
|
764
|
+
["did-fail-load", onFailLoad],
|
|
765
|
+
["render-process-gone", onGuestGone],
|
|
766
|
+
["destroyed", onGuestGone]
|
|
767
|
+
];
|
|
768
|
+
|
|
769
|
+
// A listener that cannot be attached leaves a guest nobody can
|
|
770
|
+
// drive; abort the start instead of mounting a silent webview.
|
|
771
|
+
let listenersReady = false;
|
|
772
|
+
try {
|
|
773
|
+
for (const [name, handler] of listeners) {
|
|
774
|
+
element.addEventListener(name, handler, name === "dom-ready" ? { once: true } : void 0);
|
|
775
|
+
}
|
|
776
|
+
listenersReady = true;
|
|
777
|
+
} catch (error) {
|
|
778
|
+
fail(error);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const start = async () => {
|
|
782
|
+
try {
|
|
783
|
+
const reservation = await bridge.acquire(partitionKey);
|
|
784
|
+
if (disposed || failed) {
|
|
785
|
+
try {
|
|
786
|
+
Promise.resolve(bridge.release(reservation.lease)).catch(() => {});
|
|
787
|
+
} catch {
|
|
788
|
+
/* lease already gone */
|
|
789
|
+
}
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
lease = reservation.lease;
|
|
793
|
+
element.setAttribute("name", reservation.lease);
|
|
794
|
+
element.setAttribute("partition", reservation.partition);
|
|
795
|
+
element.setAttribute("src", `about:blank#${reservation.lease}`);
|
|
796
|
+
try {
|
|
797
|
+
unsubscribeOpen = bridge.onOpenRequested(reservation.lease, (raw) => {
|
|
798
|
+
if (disposed || failed || typeof raw !== "string") return;
|
|
799
|
+
const target = parsePopupUrl(raw);
|
|
800
|
+
if (target !== void 0) navigate(element, target);
|
|
801
|
+
});
|
|
802
|
+
} catch {
|
|
803
|
+
/* popup bridge unavailable: the guest still works */
|
|
804
|
+
}
|
|
805
|
+
host.append(element);
|
|
806
|
+
watchdog = setTimeout(() => {
|
|
807
|
+
watchdog = void 0;
|
|
808
|
+
if (disposed || failed) return;
|
|
809
|
+
if (readUrl(element) === void 0) fail(new Error(COPY.timeout));
|
|
810
|
+
}, 15000);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
fail(error);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
if (listenersReady) void start();
|
|
816
|
+
|
|
817
|
+
return () => {
|
|
818
|
+
disposed = true;
|
|
819
|
+
// Read before removal: React cleans up after taking the DOM away, and
|
|
820
|
+
// Electron's getURL() throws on a detached webview.
|
|
821
|
+
remember(readUrl(element) ?? lastUrlRef.current);
|
|
822
|
+
if (watchdog !== void 0) {
|
|
823
|
+
clearTimeout(watchdog);
|
|
824
|
+
watchdog = void 0;
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
if (typeof unsubscribeOpen === "function") unsubscribeOpen();
|
|
828
|
+
} catch {
|
|
829
|
+
/* already detached */
|
|
830
|
+
}
|
|
831
|
+
for (const [name, handler] of listeners) {
|
|
832
|
+
try {
|
|
833
|
+
element.removeEventListener(name, handler);
|
|
834
|
+
} catch {
|
|
835
|
+
/* already detached */
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
element.remove();
|
|
840
|
+
} catch {
|
|
841
|
+
/* already detached */
|
|
842
|
+
}
|
|
843
|
+
const released = lease;
|
|
844
|
+
lease = void 0;
|
|
845
|
+
if (released !== void 0) {
|
|
846
|
+
try {
|
|
847
|
+
Promise.resolve(bridge.release(released)).catch(() => {});
|
|
848
|
+
} catch {
|
|
849
|
+
/* lease already gone */
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
}, [attempt]);
|
|
854
|
+
|
|
855
|
+
const copy = accountCopy(account);
|
|
856
|
+
|
|
857
|
+
return react.createElement("div", { style: styles.root },
|
|
858
|
+
react.createElement("div", { style: styles.accountBar },
|
|
859
|
+
react.createElement("span", { style: { ...styles.accountDot, opacity: copy.opacity }, "aria-hidden": "true" }),
|
|
860
|
+
react.createElement("span", null, copy.text)),
|
|
861
|
+
react.createElement("div", { style: styles.host, ref: hostRef },
|
|
862
|
+
failure === void 0 && loading
|
|
863
|
+
? react.createElement("div", { style: styles.notice }, COPY.loading)
|
|
864
|
+
: null,
|
|
865
|
+
failure === void 0
|
|
866
|
+
? null
|
|
867
|
+
: react.createElement("div", { style: styles.notice },
|
|
868
|
+
react.createElement("div", null, `${COPY.failed}: ${failure}`),
|
|
869
|
+
react.createElement("button", { type: "button", style: styles.button, onClick: retry }, COPY.retry))));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/** The registered panel: the boundary owns every rendering failure below it. */
|
|
873
|
+
function ChatPanelShell() {
|
|
874
|
+
return react.createElement(PanelBoundary, null, react.createElement(ChatPanel));
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/** Client service this plugin reads; the loader waits for the slot registry. */
|
|
878
|
+
const inject = ["slots"];
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Register the left-sidebar entry and its main panel.
|
|
882
|
+
* @param ctx - client plugin context.
|
|
883
|
+
*/
|
|
884
|
+
function apply(ctx) {
|
|
885
|
+
try {
|
|
886
|
+
// The panel needs the desktop guest bridge; on the Web surface an iframe
|
|
887
|
+
// cannot carry the site, so the plugin stays inert there.
|
|
888
|
+
if (globalThis.dshDesktop?.browser === void 0) return;
|
|
889
|
+
} catch {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
try {
|
|
894
|
+
startAccountSync(ctx);
|
|
895
|
+
} catch (error) {
|
|
896
|
+
try {
|
|
897
|
+
console.error("dsh-plugin-deepseek-chat: account sync failed", error);
|
|
898
|
+
} catch {
|
|
899
|
+
/* console unavailable */
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
try {
|
|
904
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
905
|
+
name: "main",
|
|
906
|
+
key: PANEL_ID
|
|
907
|
+
}, ChatPanelShell));
|
|
908
|
+
|
|
909
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
910
|
+
name: "sidebar.panellist",
|
|
911
|
+
id: PANEL_ID,
|
|
912
|
+
order: 5,
|
|
913
|
+
label: () => COPY.label
|
|
914
|
+
}, ChatGlyph));
|
|
915
|
+
} catch (error) {
|
|
916
|
+
try {
|
|
917
|
+
console.error("dsh-plugin-deepseek-chat: registration failed", error);
|
|
918
|
+
} catch {
|
|
919
|
+
/* console unavailable */
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
exports.apply = apply;
|
|
925
|
+
exports.inject = inject;
|
|
926
|
+
exports.__test__ = {
|
|
927
|
+
ACCOUNT_UNKNOWN,
|
|
928
|
+
ACCOUNT_SIGNED_IN,
|
|
929
|
+
ACCOUNT_SIGNED_OUT,
|
|
930
|
+
CHAT_URL,
|
|
931
|
+
STORAGE_BASE,
|
|
932
|
+
accountCopy,
|
|
933
|
+
getSnapshot: () => accountSnapshot,
|
|
934
|
+
partitionKeyFor,
|
|
935
|
+
storageKeyFor,
|
|
936
|
+
publishAccount,
|
|
937
|
+
publishAccountLabel,
|
|
938
|
+
probeAccount,
|
|
939
|
+
callAccount,
|
|
940
|
+
resetAccountSync() {
|
|
941
|
+
accountSnapshot = { status: ACCOUNT_UNKNOWN, label: "", probed: false };
|
|
942
|
+
accountListeners.clear();
|
|
943
|
+
accountSyncStarted = false;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
return module.exports;
|
|
947
|
+
}
|
|
948
|
+
});
|