dsh-think-translate 1.0.9 → 1.0.11
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 +1347 -1306
- package/lib/index.js +806 -758
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,1306 +1,1347 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
2
|
-
id: "dsh-think-translate",
|
|
3
|
-
factory: (require) => {
|
|
4
|
-
var module = { exports: {} };
|
|
5
|
-
var exports = module.exports;
|
|
6
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
var react = require("react");
|
|
8
|
-
var React = react.default !== undefined ? react.default : react;
|
|
9
|
-
// Official Markdown renderer (loaded via the client module graph, declared
|
|
10
|
-
// in dsh.client.external). Using it keeps official formatting — code-block
|
|
11
|
-
// copy, syntax highlighting, tables, footnotes, file mentions — while this
|
|
12
|
-
// plugin only supplies translated text. Falls back to the local Markdownish
|
|
13
|
-
// if the dependency is unavailable.
|
|
14
|
-
var primitives = null;
|
|
15
|
-
try { primitives = require("@deepseek-ai/dsh-client-ui-primitives"); } catch (e) { primitives = null; }
|
|
16
|
-
var MarkdownText = primitives && primitives.MarkdownText ? primitives.MarkdownText : null;
|
|
17
|
-
var
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
".xl-think
|
|
36
|
-
".xl-
|
|
37
|
-
".xl-
|
|
38
|
-
".xl-
|
|
39
|
-
".xl-
|
|
40
|
-
".xl-
|
|
41
|
-
".xl-
|
|
42
|
-
".xl-
|
|
43
|
-
".xl-
|
|
44
|
-
".xl-
|
|
45
|
-
".xl-
|
|
46
|
-
".xl-
|
|
47
|
-
".xl-
|
|
48
|
-
".xl-md
|
|
49
|
-
".xl-md .xl-
|
|
50
|
-
".xl-md .xl-
|
|
51
|
-
".xl-md .xl-
|
|
52
|
-
".xl-
|
|
53
|
-
".xl-
|
|
54
|
-
".xl-
|
|
55
|
-
".xl-
|
|
56
|
-
".xl-
|
|
57
|
-
".xl-
|
|
58
|
-
".xl-
|
|
59
|
-
".xl-
|
|
60
|
-
".xl-
|
|
61
|
-
".xl-
|
|
62
|
-
".xl-
|
|
63
|
-
".xl-
|
|
64
|
-
".xl-
|
|
65
|
-
".xl-
|
|
66
|
-
".xl-
|
|
67
|
-
".xl-
|
|
68
|
-
".xl-
|
|
69
|
-
".xl-
|
|
70
|
-
".xl-
|
|
71
|
-
".xl-
|
|
72
|
-
".xl-
|
|
73
|
-
".xl-
|
|
74
|
-
".xl-
|
|
75
|
-
".xl-
|
|
76
|
-
".xl-
|
|
77
|
-
".xl-
|
|
78
|
-
".xl-
|
|
79
|
-
".xl-
|
|
80
|
-
".xl-
|
|
81
|
-
".xl-
|
|
82
|
-
".xl-
|
|
83
|
-
".xl-
|
|
84
|
-
".xl-
|
|
85
|
-
".xl-
|
|
86
|
-
".xl-
|
|
87
|
-
".xl-
|
|
88
|
-
".xl-table
|
|
89
|
-
".xl-
|
|
90
|
-
".xl-
|
|
91
|
-
".xl-
|
|
92
|
-
".xl-
|
|
93
|
-
".xl-dock-
|
|
94
|
-
".xl-dock-
|
|
95
|
-
".xl-dock-
|
|
96
|
-
".xl-dock-
|
|
97
|
-
".xl-dock-
|
|
98
|
-
".xl-dock-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
var
|
|
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
|
-
function
|
|
304
|
-
|
|
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
|
-
var
|
|
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
|
-
|
|
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
|
-
var
|
|
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
|
-
var
|
|
533
|
-
var
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
var
|
|
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
|
-
if (
|
|
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
|
-
|
|
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
|
-
var
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
while (i < lines.length &&
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}
|
|
710
|
-
if (
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
var
|
|
724
|
-
|
|
725
|
-
})
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
-
var
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
var
|
|
772
|
-
var
|
|
773
|
-
var
|
|
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
|
-
if (
|
|
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
|
-
var
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
var
|
|
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
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
className: "xl-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
var
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
var
|
|
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
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
var
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
.
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
createElement("
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
});
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-think-translate",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
var react = require("react");
|
|
8
|
+
var React = react.default !== undefined ? react.default : react;
|
|
9
|
+
// Official Markdown renderer (loaded via the client module graph, declared
|
|
10
|
+
// in dsh.client.external). Using it keeps official formatting — code-block
|
|
11
|
+
// copy, syntax highlighting, tables, footnotes, file mentions — while this
|
|
12
|
+
// plugin only supplies translated text. Falls back to the local Markdownish
|
|
13
|
+
// if the dependency is unavailable.
|
|
14
|
+
var primitives = null;
|
|
15
|
+
try { primitives = require("@deepseek-ai/dsh-client-ui-primitives"); } catch (e) { primitives = null; }
|
|
16
|
+
var MarkdownText = primitives && primitives.MarkdownText ? primitives.MarkdownText : null;
|
|
17
|
+
var IconChecklist = primitives && primitives.IconChecklistOutline14 ? primitives.IconChecklistOutline14 : null;
|
|
18
|
+
var IconChevronUp = primitives && primitives.IconChevronUpOutline14 ? primitives.IconChevronUpOutline14 : null;
|
|
19
|
+
var IconChevronDown = primitives && primitives.IconChevronDownOutline14 ? primitives.IconChevronDownOutline14 : null;
|
|
20
|
+
var createElement = function (type, props) {
|
|
21
|
+
var children = [];
|
|
22
|
+
for (var i = 2; i < arguments.length; i++) children.push(arguments[i]);
|
|
23
|
+
return React.createElement.apply(React, [type, props].concat(children));
|
|
24
|
+
};
|
|
25
|
+
var Fragment = React.Fragment;
|
|
26
|
+
var useState = React.useState;
|
|
27
|
+
var useEffect = React.useEffect;
|
|
28
|
+
var useId = React.useId;
|
|
29
|
+
var Component = React.Component;
|
|
30
|
+
|
|
31
|
+
// ------------------------------------------------------------------
|
|
32
|
+
// stylesheet (data-plugin-css pattern, same as shipped client bundles)
|
|
33
|
+
// ------------------------------------------------------------------
|
|
34
|
+
var CSS_TEXT = [
|
|
35
|
+
".xl-think{margin:2px 0}",
|
|
36
|
+
".xl-think-row{display:flex;align-items:baseline;gap:6px;width:100%;background:none;border:0;padding:2px 0;cursor:pointer;font:inherit;color:inherit;text-align:left}",
|
|
37
|
+
".xl-chev{font-size:9px;opacity:.55;transition:transform .12s}",
|
|
38
|
+
".xl-chev[data-open]{transform:rotate(90deg)}",
|
|
39
|
+
".xl-think-title{font-size:12px;font-weight:600;opacity:.75;white-space:nowrap}",
|
|
40
|
+
".xl-sep{flex:0 0 14px;height:1px;background:currentColor;opacity:.18;align-self:center}",
|
|
41
|
+
".xl-think-summary{font-size:12px;opacity:.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:64ch}",
|
|
42
|
+
".xl-think-body{margin:4px 0 6px 14px;padding:6px 10px;border-left:2px solid rgba(127,127,127,.35);font-size:12.5px;line-height:1.6;opacity:.85;white-space:pre-wrap;word-break:break-word}",
|
|
43
|
+
".xl-trans{padding:4px 0}",
|
|
44
|
+
".xl-orig{margin-top:4px;opacity:.75}",
|
|
45
|
+
".xl-orig summary{cursor:pointer;font-size:11px;opacity:.6}",
|
|
46
|
+
".xl-orig-text{margin-top:2px;opacity:.8}",
|
|
47
|
+
".xl-warn{font-size:10px;color:#c0392b;opacity:.8}",
|
|
48
|
+
".xl-md{font-size:14px;line-height:1.65}",
|
|
49
|
+
".xl-md .xl-p{margin:6px 0;white-space:pre-wrap;word-break:break-word}",
|
|
50
|
+
".xl-md .xl-h{margin:10px 0 4px;font-weight:600}",
|
|
51
|
+
".xl-md .xl-pre{background:rgba(127,127,127,.12);border-radius:6px;padding:8px 10px;overflow:auto;font-size:12.5px}",
|
|
52
|
+
".xl-md .xl-code{background:rgba(127,127,127,.15);border-radius:4px;padding:0 4px;font-size:.92em}",
|
|
53
|
+
".xl-md .xl-bq{border-left:3px solid rgba(127,127,127,.35);margin:6px 0;padding:2px 10px;opacity:.85}",
|
|
54
|
+
".xl-md .xl-ul,.xl-md .xl-ol{margin:6px 0;padding-left:22px}",
|
|
55
|
+
".xl-md .xl-link{color:#4c9aff;text-decoration:none}",
|
|
56
|
+
".xl-trans-after{margin:2px 0 8px;padding:4px 10px;border-left:2px solid rgba(76,154,255,.45);font-size:13.5px;opacity:.9}",
|
|
57
|
+
".xl-todo{margin:2px 0}",
|
|
58
|
+
".xl-row{display:flex;align-items:center;gap:8px;width:100%;background:none;border:0;padding:3px 0;cursor:pointer;font:inherit;color:inherit;text-align:left}",
|
|
59
|
+
".xl-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:rgba(127,127,127,.5)}",
|
|
60
|
+
".xl-dot-ok{background:#3fa45b}",
|
|
61
|
+
".xl-title{font-size:12.5px;font-weight:600;opacity:.8;white-space:nowrap}",
|
|
62
|
+
".xl-summary{font-size:12.5px;opacity:.65;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
63
|
+
".xl-suffix{font-size:11px;opacity:.6}",
|
|
64
|
+
".xl-items{margin:4px 0 6px 16px;display:flex;flex-direction:column;gap:3px}",
|
|
65
|
+
".xl-item{display:flex;gap:8px;font-size:12.5px;line-height:1.5;align-items:baseline}",
|
|
66
|
+
".xl-glyph{flex:0 0 auto;opacity:.7}",
|
|
67
|
+
".xl-item-text{opacity:.85;word-break:break-word}",
|
|
68
|
+
".xl-panel{display:flex;flex-direction:column;gap:10px;font-size:13px}",
|
|
69
|
+
".xl-panel label{display:flex;gap:10px;align-items:center;min-height:28px}",
|
|
70
|
+
".xl-panel label > span:first-child{flex:0 0 112px;font-size:12.5px;opacity:.85}",
|
|
71
|
+
".xl-panel input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#4c9aff);width:14px;height:14px;cursor:pointer}",
|
|
72
|
+
".xl-note{font-size:11.5px;opacity:.6;line-height:1.5}",
|
|
73
|
+
".xl-err{font-size:12px;color:var(--dsw-alias-state-error-primary,#c0392b);opacity:.9}",
|
|
74
|
+
".xl-inspect{margin-left:auto;border:0;background:none;color:inherit;opacity:.45;cursor:pointer;font-size:11px}",
|
|
75
|
+
".xl-kv{display:flex;gap:8px;font-size:12px}",
|
|
76
|
+
".xl-kv b{opacity:.55;font-weight:500;flex:0 0 auto}",
|
|
77
|
+
".xl-sel{font-size:12.5px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));border-radius:6px;padding:4px 8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));color:var(--dsw-alias-label-primary,inherit);cursor:pointer;outline:none;transition:border-color .12s}",
|
|
78
|
+
".xl-sel:hover{border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.6))}",
|
|
79
|
+
".xl-sel:focus{box-shadow:0 0 0 2px rgba(76,154,255,.28)}",
|
|
80
|
+
".xl-sel option{background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-base,#fff));color:var(--dsw-alias-label-primary,#000)}",
|
|
81
|
+
".xl-btn{border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.45));background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,inherit);font-size:11.5px;border-radius:6px;padding:4px 12px;cursor:pointer;transition:background .12s,border-color .12s}",
|
|
82
|
+
".xl-btn:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12));border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.7))}",
|
|
83
|
+
".xl-hr{border:0;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));margin:2px 0}",
|
|
84
|
+
".xl-cfg{width:100%;box-sizing:border-box;font-family:monospace;font-size:11px;line-height:1.5;padding:8px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));background:var(--dsw-alias-bg-base,rgba(127,127,127,.08));color:var(--dsw-alias-label-primary,inherit);outline:none}",
|
|
85
|
+
".xl-cfg:focus{border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.6));box-shadow:0 0 0 2px rgba(76,154,255,.2)}",
|
|
86
|
+
".xl-progress{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.15));overflow:hidden;margin:4px 0}",
|
|
87
|
+
".xl-progress-bar{height:100%;background:var(--dsw-alias-brand-primary,#4c9aff);border-radius:3px;transition:width .4s}",
|
|
88
|
+
".xl-table-wrap{overflow-x:auto;margin:6px 0}",
|
|
89
|
+
".xl-table{border-collapse:collapse;min-width:100%;font-size:13px;line-height:1.5}",
|
|
90
|
+
".xl-table th,.xl-table td{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));padding:6px 10px;text-align:left;vertical-align:top}",
|
|
91
|
+
".xl-table th{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));font-weight:600}",
|
|
92
|
+
".xl-table code{font-family:var(--dsw-font-mono,monospace);font-size:12px}",
|
|
93
|
+
".xl-dock{box-sizing:border-box;flex:none;overflow:hidden;margin:0 auto;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2)}",
|
|
94
|
+
".xl-dock-body{display:flex;flex-direction:column;gap:8px;padding:6px 12px}",
|
|
95
|
+
".xl-dock-header{display:flex;align-items:center;gap:10px;width:100%;padding:0;border:none;background:transparent;text-align:left;cursor:pointer}",
|
|
96
|
+
".xl-dock-lead{display:grid;flex:none;place-items:center;color:var(--dsw-alias-label-tertiary)}",
|
|
97
|
+
".xl-dock-title{flex:none;font-size:13px;line-height:24px;font-weight:500;color:var(--dsw-alias-label-primary)}",
|
|
98
|
+
".xl-dock-progress{flex:1 1 auto;min-width:0;overflow:hidden;font-size:13px;line-height:20px;font-weight:400;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap}",
|
|
99
|
+
".xl-dock-chev{display:grid;flex:none;place-items:center;color:var(--dsw-alias-label-tertiary)}",
|
|
100
|
+
".xl-dock-list{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;max-height:180px;overflow-y:auto}",
|
|
101
|
+
".xl-dock-item{display:flex;align-items:center;gap:10px;min-width:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
|
|
102
|
+
".xl-dock-glyph{display:grid;flex:none;place-items:center;width:16px;height:16px}",
|
|
103
|
+
".xl-dock-glyph-ok{color:var(--dsw-alias-state-success-primary)}",
|
|
104
|
+
".xl-dock-glyph-wait{color:var(--dsw-alias-label-caption)}",
|
|
105
|
+
".xl-dock-glyph-run{color:var(--dsw-alias-state-business-primary);animation:xl-dock-spin 1s linear infinite}",
|
|
106
|
+
".xl-dock-content{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
107
|
+
"@keyframes xl-dock-spin{to{transform:rotate(360deg)}}"
|
|
108
|
+
].join("\n");
|
|
109
|
+
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="dsh-think-translate/styles.css"]') === null) {
|
|
110
|
+
var tag = document.createElement("style");
|
|
111
|
+
tag.dataset.plugin = "dsh-think-translate";
|
|
112
|
+
tag.dataset.pluginCss = "dsh-think-translate/styles.css";
|
|
113
|
+
tag.textContent = CSS_TEXT;
|
|
114
|
+
document.head.appendChild(tag);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ------------------------------------------------------------------
|
|
118
|
+
// settings store (persisted to localStorage; refreshes keep the choice)
|
|
119
|
+
// ------------------------------------------------------------------
|
|
120
|
+
function loadPref(k, d) {
|
|
121
|
+
try {
|
|
122
|
+
var v = localStorage.getItem("dsh-think-translate:" + k);
|
|
123
|
+
return v === null ? d : v;
|
|
124
|
+
} catch (e) { return d; }
|
|
125
|
+
}
|
|
126
|
+
function savePref(k, v) {
|
|
127
|
+
try { localStorage.setItem("dsh-think-translate:" + k, String(v)); } catch (e) {}
|
|
128
|
+
}
|
|
129
|
+
var store = {
|
|
130
|
+
lang: loadPref("lang", "zh-CN"), think: loadPref("think", "true") !== "false",
|
|
131
|
+
todo: loadPref("todo", "true") !== "false", ans: loadPref("ans", "false") === "true", mode: loadPref("mode", "lazy"),
|
|
132
|
+
listeners: new Set(),
|
|
133
|
+
set: function (patch) {
|
|
134
|
+
var changed = false;
|
|
135
|
+
for (var k in patch) { if (store[k] !== patch[k]) { store[k] = patch[k]; changed = true; } }
|
|
136
|
+
if (changed) {
|
|
137
|
+
for (var k2 in patch) savePref(k2, store[k2]);
|
|
138
|
+
store.listeners.forEach(function (f) { f(); });
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
subscribe: function (f) { store.listeners.add(f); return function () { store.listeners.delete(f); }; }
|
|
142
|
+
};
|
|
143
|
+
function useStore() {
|
|
144
|
+
var pair = useState(0);
|
|
145
|
+
useEffect(function () {
|
|
146
|
+
return store.subscribe(function () { pair[1](function (n) { return n + 1; }); });
|
|
147
|
+
}, []);
|
|
148
|
+
return store;
|
|
149
|
+
}
|
|
150
|
+
var LANGS = [["zh-CN", "中文"], ["en", "English"], ["ja", "日本語"], ["ko", "한국어"], ["es", "Español"], ["fr", "Français"], ["de", "Deutsch"], ["ru", "Русский"]];
|
|
151
|
+
// Settings UI strings, keyed by target language (follows store.lang).
|
|
152
|
+
var UI_TEXT = {
|
|
153
|
+
"zh-CN": {
|
|
154
|
+
targetLang: "目标语言", think: "翻译思考链(Think 行)", todo: "翻译任务卡片条目", ans: "翻译回答正文(追加译文)", mode: "翻译时机", modeEager: "全部预翻译(最高负荷)", modeLazy: "懒加载(推荐)", modeExpand: "仅展开时翻译(最低负荷)",
|
|
155
|
+
sectionTitle: "思考链翻译", thinking: "思考中", thinkTitle: "思考", original: "原文", transFail: "翻译失败·原文",
|
|
156
|
+
tasks: "任务", taskList: "任务清单", doneFmt: "{d} 已完成 · {t} 项", stopped: "已停止",
|
|
157
|
+
noLocalModel: "本地模型未安装", downloadModel: "下载模型", downloading: "下载中", verifying: "校验中", pullDone: "已安装并启用", pullError: "下载失败",
|
|
158
|
+
installPrompt: "首次使用本地模型:选择后自动下载并启用(需 Ollama 正在运行)。", recommended: "推荐", otherModel: "其他模型", startDownload: "开始下载",
|
|
159
|
+
provider: "首选提供方", model: "模型", loadingModels: "加载模型…", noModels: "无可用模型",
|
|
160
|
+
testConn: "测试连接", testing: "测试中…", connOk: "连通", connFail: "链路失败(将回退浏览器直连)",
|
|
161
|
+
clearCache: "清空缓存", status: "成功/失败", providerOf: "提供方", cacheEntries: "缓存条目", lastError: "最近错误",
|
|
162
|
+
providerUnavailable: "翻译服务未加载:插件的 host 半边未生效(常见于刚改过配置或更新后)。请重启 Web 再试——重启后翻译与设置将恢复正常。",
|
|
163
|
+
providerDesc: "Google / Bing / 本地模型(自动选择)",
|
|
164
|
+
labels: { google: "google gtx(免费)", bing: "bing(免费)", openai: "本地部署模型(Ollama)" },
|
|
165
|
+
copy: "复制", copied: "已复制",
|
|
166
|
+
note: "界面翻译:思考链、任务卡片、回答正文会显示为你选的语言,原文保留在会话记录中可随时对照。优先使用本地模型,其次 Google/Bing;文件路径、代码等自动跳过。",
|
|
167
|
+
},
|
|
168
|
+
"en": {
|
|
169
|
+
targetLang: "Target language", think: "Translate thinking chain", todo: "Translate task card items", ans: "Translate answer text (append translation)", mode: "When to translate", modeEager: "Pre-translate all (highest load)", modeLazy: "Lazy-load (recommended)", modeExpand: "Only on expand (lowest load)",
|
|
170
|
+
sectionTitle: "Think Translation", thinking: "Thinking", thinkTitle: "Think", original: "Original", transFail: "untranslated",
|
|
171
|
+
tasks: "Tasks", taskList: "Tasks", doneFmt: "{d}/{t} done", stopped: "stopped",
|
|
172
|
+
noLocalModel: "No local model installed", downloadModel: "Download model", downloading: "Downloading", verifying: "Verifying", pullDone: "Installed and enabled", pullError: "Download failed",
|
|
173
|
+
installPrompt: "First time with a local model: picking one downloads and enables it automatically (Ollama must be running).", recommended: "Recommended", otherModel: "Other model", startDownload: "Start download",
|
|
174
|
+
provider: "Preferred provider", model: "Model", loadingModels: "Loading models…", noModels: "No models available",
|
|
175
|
+
testConn: "Test connection", testing: "Testing…", connOk: "Connected", connFail: "Chain failed (will fall back)",
|
|
176
|
+
clearCache: "Clear cache", status: "ok / fail", providerOf: "provider", cacheEntries: "cache entries", lastError: "last error",
|
|
177
|
+
providerUnavailable: "Translation service not loaded: the plugin host half is inactive (common after config changes or updates). Restart the web app to restore translation and settings.",
|
|
178
|
+
providerDesc: "Google / Bing / local model (auto)",
|
|
179
|
+
labels: { google: "google gtx (free)", bing: "bing (free)", openai: "Local model (Ollama)" },
|
|
180
|
+
copy: "Copy", copied: "Copied",
|
|
181
|
+
note: "UI translation: the thinking chain, task cards and answer text display in your chosen language; originals stay in the transcript for comparison. Local model first, then Google/Bing; file paths and code are skipped.",
|
|
182
|
+
},
|
|
183
|
+
"ja": {
|
|
184
|
+
targetLang: "対象言語", think: "思考チェーンを翻訳(Think 行)", todo: "タスクカード項目を翻訳", ans: "回答本文を翻訳(訳文を追加)", mode: "翻訳タイミング", modeEager: "すべて事前翻訳(最大負荷)", modeLazy: "遅延ロード(推奨)", modeExpand: "展開時のみ翻訳(最小負荷)",
|
|
185
|
+
sectionTitle: "思考チェーン翻訳", thinking: "思考中", thinkTitle: "思考", original: "原文", transFail: "翻訳失敗・原文",
|
|
186
|
+
tasks: "タスク", taskList: "タスク一覧", doneFmt: "{d} 完了 · 全{t}件", stopped: "停止済み",
|
|
187
|
+
noLocalModel: "ローカルモデル未インストール", downloadModel: "モデルをダウンロード", downloading: "ダウンロード中", verifying: "検証中", pullDone: "インストール済み・有効", pullError: "ダウンロード失敗",
|
|
188
|
+
installPrompt: "初めてのローカルモデル:選択すると自動ダウンロードして有効化します(Ollama の起動が必要)。", recommended: "おすすめ", otherModel: "他のモデル", startDownload: "ダウンロード開始",
|
|
189
|
+
provider: "優先プロバイダー", model: "モデル", loadingModels: "モデル読込中…", noModels: "利用可能なモデルなし",
|
|
190
|
+
testConn: "接続テスト", testing: "テスト中…", connOk: "接続OK", connFail: "リンク失敗(フォールバック)",
|
|
191
|
+
clearCache: "キャッシュをクリア", status: "成功/失敗", providerOf: "提供元", cacheEntries: "キャッシュ件数", lastError: "最近のエラー",
|
|
192
|
+
providerUnavailable: "翻訳サービスが読み込まれていません:プラグインのホスト側が無効です(設定変更やアップデート後に発生しがち)。Web を再起動すると翻訳と設定が復旧します。",
|
|
193
|
+
providerDesc: "Google / Bing / ローカルモデル(自動)",
|
|
194
|
+
labels: { google: "google gtx(無料)", bing: "bing(無料)", openai: "ローカルモデル(Ollama)" },
|
|
195
|
+
note: "UI 翻訳:思考チェーン・タスクカード・回答本文が選んだ言語で表示されます。原文は会話履歴に残り、いつでも比較できます。優先はローカルモデル、次に Google/Bing。ファイルパスやコードは自動的にスキップされます。",
|
|
196
|
+
},
|
|
197
|
+
"ko": {
|
|
198
|
+
targetLang: "대상 언어", think: "사고 체인 번역(Think 행)", todo: "작업 카드 항목 번역", ans: "답변 본문 번역(번역 추가)", mode: "번역 시점", modeEager: "모두 사전 번역(최대 부하)", modeLazy: "지연 로딩(권장)", modeExpand: "펼칠 때만 번역(최소 부하)",
|
|
199
|
+
sectionTitle: "사고 체인 번역", thinking: "생각 중", thinkTitle: "생각", original: "원문", transFail: "번역 실패·원문",
|
|
200
|
+
tasks: "작업", taskList: "작업 목록", doneFmt: "{d}/{t} 완료", stopped: "중지됨",
|
|
201
|
+
noLocalModel: "로컬 모델 미설치", downloadModel: "모델 다운로드", downloading: "다운로드 중", verifying: "검증 중", pullDone: "설치 완료·활성화", pullError: "다운로드 실패",
|
|
202
|
+
installPrompt: "첫 로컬 모델: 선택하면 자동 다운로드 후 활성화됩니다(Ollama 실행 필요).", recommended: "권장", otherModel: "기타 모델", startDownload: "다운로드 시작",
|
|
203
|
+
provider: "기본 제공자", model: "모델", loadingModels: "모델 로딩 중…", noModels: "사용 가능한 모델 없음",
|
|
204
|
+
testConn: "연결 테스트", testing: "테스트 중…", connOk: "연결됨", connFail: "체인 실패(폴백)",
|
|
205
|
+
clearCache: "캐시 지우기", status: "성공/실패", providerOf: "제공자", cacheEntries: "캐시 항목", lastError: "최근 오류",
|
|
206
|
+
providerUnavailable: "번역 서비스가 로드되지 않았습니다: 플러그인 호스트 측이 비활성 상태입니다(설정 변경이나 업데이트 후 흔함). 웹을 재시작하면 번역과 설정이 복구됩니다.",
|
|
207
|
+
providerDesc: "Google / Bing / 로컬 모델(자동)",
|
|
208
|
+
labels: { google: "google gtx(무료)", bing: "bing(무료)", openai: "로컬 모델(Ollama)" },
|
|
209
|
+
note: "UI 번역: 사고 체인, 작업 카드, 답변 본문이 선택한 언어로 표시됩니다. 원문은 대화 기록에 남아 언제든 비교할 수 있습니다. 우선 로컬 모델, 다음 Google/Bing. 파일 경로와 코드는 자동으로 건너뜁니다.",
|
|
210
|
+
},
|
|
211
|
+
"es": {
|
|
212
|
+
targetLang: "Idioma de destino", think: "Traducir cadena de pensamiento", todo: "Traducir elementos de tarjeta de tareas", ans: "Traducir texto de respuesta", mode: "Cuándo traducir", modeEager: "Pre-traducir todo (carga máxima)", modeLazy: "Carga diferida (recomendado)", modeExpand: "Solo al expandir (carga mínima)",
|
|
213
|
+
sectionTitle: "Traducción de cadena de pensamiento", thinking: "Pensando…", thinkTitle: "Pensar", original: "Original", transFail: "Error de traducción",
|
|
214
|
+
tasks: "Tareas", taskList: "Lista de tareas", doneFmt: "{d}/{t} completado", stopped: "detenido",
|
|
215
|
+
noLocalModel: "Sin modelo local instalado", downloadModel: "Descargar modelo", downloading: "Descargando", verifying: "Verificando", pullDone: "Instalado y activado", pullError: "Error de descarga",
|
|
216
|
+
installPrompt: "Primer uso: al elegir se descarga y activa automáticamente (Ollama debe estar en ejecución).", recommended: "Recomendado", otherModel: "Otro modelo", startDownload: "Iniciar descarga",
|
|
217
|
+
provider: "Proveedor preferido", model: "Modelo", loadingModels: "Cargando modelos…", noModels: "Sin modelos disponibles",
|
|
218
|
+
testConn: "Probar conexión", testing: "Probando…", connOk: "Conectado", connFail: "Fallo de cadena (usará respaldo)",
|
|
219
|
+
clearCache: "Limpiar caché", status: "éxito / fallo", providerOf: "proveedor", cacheEntries: "entradas de caché", lastError: "último error",
|
|
220
|
+
providerUnavailable: "Servicio de traducción no cargado: la mitad host del plugin está inactiva (común tras cambios de configuración o actualizaciones). Reinicie la web para restaurar la traducción y los ajustes.",
|
|
221
|
+
providerDesc: "Google / Bing / modelo local (automático)",
|
|
222
|
+
labels: { google: "google gtx (gratis)", bing: "bing (gratis)", openai: "Modelo local (Ollama)" },
|
|
223
|
+
note: "Traducción de la interfaz: la cadena de pensamiento, las tarjetas de tareas y el texto de respuesta se muestran en el idioma elegido; el original permanece en el historial. Modelo local primero, luego Google/Bing; rutas y código se omiten.",
|
|
224
|
+
},
|
|
225
|
+
"fr": {
|
|
226
|
+
targetLang: "Langue cible", think: "Traduire la chaîne de réflexion", todo: "Traduire les éléments de carte de tâches", ans: "Traduire le texte de réponse", mode: "Quand traduire", modeEager: "Tout pré-traduire (charge maximale)", modeLazy: "Chargement différé (recommandé)", modeExpand: "Uniquement à l'ouverture (charge minimale)",
|
|
227
|
+
sectionTitle: "Traduction de chaîne de réflexion", thinking: "Réflexion…", thinkTitle: "Réflexion", original: "Original", transFail: "Échec de traduction",
|
|
228
|
+
tasks: "Tâches", taskList: "Liste des tâches", doneFmt: "{d}/{t} terminé", stopped: "arrêté",
|
|
229
|
+
noLocalModel: "Aucun modèle local installé", downloadModel: "Télécharger le modèle", downloading: "Téléchargement…", verifying: "Vérification…", pullDone: "Installé et activé", pullError: "Échec du téléchargement",
|
|
230
|
+
installPrompt: "Première utilisation : le choix télécharge et active automatiquement (Ollama doit tourner).", recommended: "Recommandé", otherModel: "Autre modèle", startDownload: "Démarrer le téléchargement",
|
|
231
|
+
provider: "Fournisseur préféré", model: "Modèle", loadingModels: "Chargement des modèles…", noModels: "Aucun modèle disponible",
|
|
232
|
+
testConn: "Tester la connexion", testing: "Test en cours…", connOk: "Connecté", connFail: "Échec de chaîne (repli)",
|
|
233
|
+
clearCache: "Vider le cache", status: "ok / échec", providerOf: "fournisseur", cacheEntries: "entrées de cache", lastError: "dernière erreur",
|
|
234
|
+
providerUnavailable: "Service de traduction non chargé : la partie hôte du plugin est inactive (fréquent après des changements de config ou des mises à jour). Redémarrez la web pour restaurer la traduction et les réglages.",
|
|
235
|
+
providerDesc: "Google / Bing / modèle local (auto)",
|
|
236
|
+
labels: { google: "google gtx (gratuit)", bing: "bing (gratuit)", openai: "Modèle local (Ollama)" },
|
|
237
|
+
note: "Traduction de l'interface : la chaîne de réflexion, les cartes de tâches et le texte de réponse s'affichent dans la langue choisie ; l'original reste dans l'historique. Modèle local d'abord, puis Google/Bing ; chemins et code ignorés.",
|
|
238
|
+
},
|
|
239
|
+
"de": {
|
|
240
|
+
targetLang: "Zielsprache", think: "Gedankenkette übersetzen", todo: "Aufgabenkartenelemente übersetzen", ans: "Antworttext übersetzen", mode: "Wann übersetzen", modeEager: "Alles vorübersetzen (höchste Last)", modeLazy: "Lazy laden (empfohlen)", modeExpand: "Nur beim Aufklappen (geringste Last)",
|
|
241
|
+
sectionTitle: "Übersetzung der Gedankenkette", thinking: "Denkt…", thinkTitle: "Denken", original: "Original", transFail: "Übersetzung fehlgeschlagen",
|
|
242
|
+
tasks: "Aufgaben", taskList: "Aufgabenliste", doneFmt: "{d}/{t} erledigt", stopped: "gestoppt",
|
|
243
|
+
noLocalModel: "Kein lokales Modell installiert", downloadModel: "Modell herunterladen", downloading: "Lade herunter…", verifying: "Prüfe…", pullDone: "Installiert und aktiviert", pullError: "Download fehlgeschlagen",
|
|
244
|
+
installPrompt: "Erstnutzung: Auswahl lädt automatisch herunter und aktiviert (Ollama muss laufen).", recommended: "Empfohlen", otherModel: "Anderes Modell", startDownload: "Download starten",
|
|
245
|
+
provider: "Bevorzugter Anbieter", model: "Modell", loadingModels: "Modelle laden…", noModels: "Keine Modelle verfügbar",
|
|
246
|
+
testConn: "Verbindung testen", testing: "Teste…", connOk: "Verbunden", connFail: "Kette fehlgeschlagen (Fallback)",
|
|
247
|
+
clearCache: "Cache leeren", status: "ok / fehlgeschlagen", providerOf: "Anbieter", cacheEntries: "Cache-Einträge", lastError: "letzter Fehler",
|
|
248
|
+
providerUnavailable: "Übersetzungsdienst nicht geladen: Die Host-Seite des Plugins ist inaktiv (häufig nach Konfigurationsänderungen oder Updates). Starten Sie die Web-App neu, um Übersetzung und Einstellungen wiederherzustellen.",
|
|
249
|
+
providerDesc: "Google / Bing / lokales Modell (automatisch)",
|
|
250
|
+
labels: { google: "google gtx (kostenlos)", bing: "bing (kostenlos)", openai: "Lokales Modell (Ollama)" },
|
|
251
|
+
note: "UI-Übersetzung: Gedankenkette, Aufgabenkarten und Antworttext erscheinen in der gewählten Sprache; das Original bleibt im Verlauf. Erst lokales Modell, dann Google/Bing; Pfade und Code werden übersprungen.",
|
|
252
|
+
},
|
|
253
|
+
"ru": {
|
|
254
|
+
targetLang: "Целевой язык", think: "Переводить цепочку размышлений", todo: "Переводить элементы карточек задач", ans: "Переводить текст ответа", mode: "Когда переводить", modeEager: "Переводить всё заранее (макс. нагрузка)", modeLazy: "Ленивая загрузка (рекомендуется)", modeExpand: "Только при раскрытии (мин. нагрузка)",
|
|
255
|
+
sectionTitle: "Перевод цепочки размышлений", thinking: "Размышление…", thinkTitle: "Размышление", original: "Оригинал", transFail: "Ошибка перевода",
|
|
256
|
+
tasks: "Задачи", taskList: "Список задач", doneFmt: "{d}/{t} выполнено", stopped: "остановлено",
|
|
257
|
+
noLocalModel: "Локальная модель не установлена", downloadModel: "Скачать модель", downloading: "Загрузка…", verifying: "Проверка…", pullDone: "Установлено и включено", pullError: "Ошибка загрузки",
|
|
258
|
+
installPrompt: "Первое использование: выбор автоматически скачает и включит (Ollama должен работать).", recommended: "Рекомендуется", otherModel: "Другая модель", startDownload: "Начать загрузку",
|
|
259
|
+
provider: "Предпочтительный провайдер", model: "Модель", loadingModels: "Загрузка моделей…", noModels: "Нет доступных моделей",
|
|
260
|
+
testConn: "Проверить соединение", testing: "Проверка…", connOk: "Подключено", connFail: "Сбой цепочки (запасной вариант)",
|
|
261
|
+
clearCache: "Очистить кэш", status: "ок / сбой", providerOf: "провайдер", cacheEntries: "записи кэша", lastError: "последняя ошибка",
|
|
262
|
+
providerUnavailable: "Сервис перевода не загружен: host-часть плагина неактивна (часто после изменения конфигурации или обновления). Перезапустите web, чтобы восстановить перевод и настройки.",
|
|
263
|
+
providerDesc: "Google / Bing / локальная модель (авто)",
|
|
264
|
+
labels: { google: "google gtx (бесплатно)", bing: "bing (бесплатно)", openai: "Локальная модель (Ollama)" },
|
|
265
|
+
note: "Перевод интерфейса: цепочка размышлений, карточки задач и текст ответа отображаются на выбранном языке; оригинал остаётся в истории. Сначала локальная модель, затем Google/Bing; пути и код пропускаются.",
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// ------------------------------------------------------------------
|
|
270
|
+
// translation pipeline (browser-side Google gtx; browser applies the
|
|
271
|
+
// system proxy automatically; endpoint returns CORS *)
|
|
272
|
+
// ------------------------------------------------------------------
|
|
273
|
+
var mem = new Map();
|
|
274
|
+
// Translation cache persisted to localStorage so switching sessions or
|
|
275
|
+
// refreshing a page reuses prior results instead of re-translating every
|
|
276
|
+
// historical message (which wedged the UI when the local model was slow,
|
|
277
|
+
// 2026-08-26). In-memory map is the fast path; localStorage is the
|
|
278
|
+
// durable cross-session floor.
|
|
279
|
+
var MEM_KEY = "dsh-xlate-cache:";
|
|
280
|
+
function cacheMemGet(key) {
|
|
281
|
+
var v = mem.get(key);
|
|
282
|
+
if (v !== undefined) return v;
|
|
283
|
+
try {
|
|
284
|
+
var lv = localStorage.getItem(MEM_KEY + key);
|
|
285
|
+
if (lv !== null) {
|
|
286
|
+
var parsed = JSON.parse(lv);
|
|
287
|
+
if (parsed && parsed.ok) { mem.set(key, parsed); return parsed; }
|
|
288
|
+
}
|
|
289
|
+
} catch (e) {}
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
function cacheMemSet(key, entry) {
|
|
293
|
+
mem.set(key, entry);
|
|
294
|
+
if (mem.size > 800) mem.delete(mem.keys().next().value);
|
|
295
|
+
try { localStorage.setItem(MEM_KEY + key, JSON.stringify(entry)); } catch (e) {}
|
|
296
|
+
}
|
|
297
|
+
var pend = new Map();
|
|
298
|
+
var stats = { ok: 0, fail: 0, lastError: null, lastProvider: null, lastModel: null };
|
|
299
|
+
// Diagnostic ledger surfaced in the settings panel (2026-08-24): bundle
|
|
300
|
+
// version, apply execution, per-slot registration outcome, current lang.
|
|
301
|
+
var diag = { version: "v5-mode", applied: false, lang: "?", slots: {}, lastLangChange: 0 };
|
|
302
|
+
|
|
303
|
+
function fnv(s) {
|
|
304
|
+
var h = 0x811c9dc5;
|
|
305
|
+
for (var i = 0; i < s.length; i++) {
|
|
306
|
+
h ^= s.charCodeAt(i);
|
|
307
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
308
|
+
}
|
|
309
|
+
return h.toString(36) + "." + s.length.toString(36);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isCjk(code) {
|
|
313
|
+
return (code >= 0x3400 && code <= 0x4dbf) || (code >= 0x4e00 && code <= 0x9fff)
|
|
314
|
+
|| (code >= 0xf900 && code <= 0xfaff) || (code >= 0x20000 && code <= 0x2ffff)
|
|
315
|
+
|| (code >= 0x3000 && code <= 0x303f) || (code >= 0xff00 && code <= 0xffef)
|
|
316
|
+
|| (code >= 0x3040 && code <= 0x30ff) || (code >= 0x31f0 && code <= 0x31ff);
|
|
317
|
+
}
|
|
318
|
+
function isLatin(code) { return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a); }
|
|
319
|
+
function cjkRatio(text) {
|
|
320
|
+
var c = 0, l = 0;
|
|
321
|
+
for (var i = 0; i < text.length; i++) {
|
|
322
|
+
var code = text.codePointAt(i);
|
|
323
|
+
if (code === undefined) continue;
|
|
324
|
+
if (isCjk(code)) c++;
|
|
325
|
+
else if (isLatin(code)) l++;
|
|
326
|
+
else if (code > 0xffff) i++;
|
|
327
|
+
}
|
|
328
|
+
var total = c + l;
|
|
329
|
+
return total === 0 ? 0 : c / total;
|
|
330
|
+
}
|
|
331
|
+
function isCodeOnlyLine(line) {
|
|
332
|
+
return /^(?:[$>#]\s*)?(?:(?:npm|pnpm|yarn|bun|npx|node|git|cd|curl|pwsh|reg)\b|(?:const|let|var|import|export|function|class|return|if|for|while|async|await)\b|[\[{(]|['"]?[\w.-]+['"]?\s*(?:[:=]|=>)|[\w$.]+\s*\(|(?:[~./][\w@~./-]*|[\w.-]+\/[\w@~./-]+)(?::\d+)?$)/.test(line);
|
|
333
|
+
}
|
|
334
|
+
function hasProse(text) {
|
|
335
|
+
var outside = text
|
|
336
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
337
|
+
.replace(/`[^`\n]*`/g, " ")
|
|
338
|
+
.replace(/https?:\/\/\S+/g, " ");
|
|
339
|
+
var lines = outside.split("\n").map(function (l) { return l.trim(); }).filter(Boolean);
|
|
340
|
+
if (lines.length === 0) return false;
|
|
341
|
+
var hasLetters = false;
|
|
342
|
+
for (var i = 0; i < outside.length; i++) {
|
|
343
|
+
var code = outside.codePointAt(i);
|
|
344
|
+
if (code === undefined) continue;
|
|
345
|
+
if (isLatin(code) || (code >= 0x3400 && code <= 0x9fff)) { hasLetters = true; break; }
|
|
346
|
+
if (code > 0xffff) i++;
|
|
347
|
+
}
|
|
348
|
+
if (!hasLetters) return false;
|
|
349
|
+
for (var j = 0; j < lines.length; j++) if (!isCodeOnlyLine(lines[j])) return true;
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
function needsTranslate(text, target) {
|
|
353
|
+
if (!text || !text.trim()) return false;
|
|
354
|
+
if (!hasProse(text)) return false;
|
|
355
|
+
if (target.indexOf("zh") === 0 && cjkRatio(text) >= 0.3) return false;
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
function chunkText(text) {
|
|
359
|
+
if (text.length <= 1200) return [text];
|
|
360
|
+
var sentences = text.match(/[\s\S]*?[.。!?!?;\n]|[\s\S]+$/g) || [text];
|
|
361
|
+
var chunks = [], cur = "";
|
|
362
|
+
for (var i = 0; i < sentences.length; i++) {
|
|
363
|
+
var sn = sentences[i];
|
|
364
|
+
if (cur && cur.length + sn.length > 1200) { chunks.push(cur); cur = ""; }
|
|
365
|
+
if (sn.length > 1200) {
|
|
366
|
+
for (var k = 0; k < sn.length; k += 1100) chunks.push(sn.slice(k, k + 1100));
|
|
367
|
+
cur = "";
|
|
368
|
+
} else {
|
|
369
|
+
cur += sn;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (cur.trim()) chunks.push(cur);
|
|
373
|
+
return chunks.length ? chunks : [text];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function gtxChunk(text, target) {
|
|
377
|
+
var qs = "translate_a/single?client=gtx&sl=auto&tl="
|
|
378
|
+
+ encodeURIComponent(target) + "&dt=t&q=" + encodeURIComponent(text);
|
|
379
|
+
// translate.googleapis.com is TLS-reset by Clash/GFW in CN networks;
|
|
380
|
+
// translate.google.com serves the same gtx JSON and is reachable there.
|
|
381
|
+
// Try both so CN and the rest of the world both work.
|
|
382
|
+
var hosts = ["https://translate.google.com/", "https://translate.googleapis.com/"];
|
|
383
|
+
var lastErr = null;
|
|
384
|
+
for (var hi = 0; hi < hosts.length; hi++) {
|
|
385
|
+
var url = hosts[hi] + qs;
|
|
386
|
+
// Bounded wait: when direct google is unreachable this must fail fast
|
|
387
|
+
// instead of hanging on the browser's own timeout (2026-08-22: direct
|
|
388
|
+
// gtx blocked in this network stalled every fallback for ~8s+).
|
|
389
|
+
try {
|
|
390
|
+
var res = await fetchWithTimeout(url, null, 6000);
|
|
391
|
+
if (!res.ok) throw new Error("gtx HTTP " + res.status);
|
|
392
|
+
var arr = await res.json();
|
|
393
|
+
var segs = arr && arr[0];
|
|
394
|
+
if (!Array.isArray(segs)) throw new Error("gtx unexpected shape");
|
|
395
|
+
var out = "";
|
|
396
|
+
for (var i = 0; i < segs.length; i++) {
|
|
397
|
+
if (Array.isArray(segs[i]) && typeof segs[i][0] === "string") out += segs[i][0];
|
|
398
|
+
}
|
|
399
|
+
if (!out.trim()) throw new Error("gtx empty translation");
|
|
400
|
+
return out;
|
|
401
|
+
} catch (e) { lastErr = e; }
|
|
402
|
+
}
|
|
403
|
+
throw lastErr || new Error("gtx unavailable");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function sleepMs(ms) { return new Promise(function (res) { setTimeout(res, ms); }); }
|
|
407
|
+
|
|
408
|
+
function fetchWithTimeout(url, opts, ms) {
|
|
409
|
+
var ctrl = typeof AbortController === "function" ? new AbortController() : null;
|
|
410
|
+
var o = opts ? Object.assign({}, opts) : {};
|
|
411
|
+
var timer = null;
|
|
412
|
+
if (ctrl) {
|
|
413
|
+
o.signal = ctrl.signal;
|
|
414
|
+
timer = setTimeout(function () { ctrl.abort(); }, ms);
|
|
415
|
+
}
|
|
416
|
+
return fetch(url, o).then(
|
|
417
|
+
function (res) { if (timer !== null) clearTimeout(timer); return res; },
|
|
418
|
+
function (err) { if (timer !== null) clearTimeout(timer); throw err; }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function hostAttempt(text, target) {
|
|
423
|
+
var res = await fetchWithTimeout("/_xlate/translate", {
|
|
424
|
+
method: "POST",
|
|
425
|
+
headers: { "Content-Type": "application/json" },
|
|
426
|
+
body: JSON.stringify({ text: text, target: target })
|
|
427
|
+
}, 30000);
|
|
428
|
+
if (!res.ok) throw new Error("route HTTP " + res.status);
|
|
429
|
+
var j = await res.json();
|
|
430
|
+
if (!(j && j.ok)) throw new Error("route ok=false");
|
|
431
|
+
return j;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function requestTranslate(text, target) {
|
|
435
|
+
var key = target + "|" + fnv(text);
|
|
436
|
+
var hit = cacheMemGet(key);
|
|
437
|
+
if (hit !== undefined) return Promise.resolve(hit);
|
|
438
|
+
var busy = pend.get(key);
|
|
439
|
+
if (busy !== undefined) return busy;
|
|
440
|
+
var p = (async function () {
|
|
441
|
+
try {
|
|
442
|
+
if (!needsTranslate(text, target)) {
|
|
443
|
+
var skipped = { ok: true, text: text, skipped: true };
|
|
444
|
+
memSet(key, skipped);
|
|
445
|
+
return skipped;
|
|
446
|
+
}
|
|
447
|
+
// Host chain with bounded retries: a single transient google/proxy
|
|
448
|
+
// blip must not flip the row into its permanent failed state
|
|
449
|
+
// (2026-08-22: one blip used to kill translation for the whole block).
|
|
450
|
+
var r = null, lastErr = null;
|
|
451
|
+
var backoff = [0, 400, 1500];
|
|
452
|
+
for (var i = 0; i < backoff.length; i++) {
|
|
453
|
+
if (backoff[i] > 0) await sleepMs(backoff[i]);
|
|
454
|
+
try { r = await hostAttempt(text, target); break; }
|
|
455
|
+
catch (e) { r = null; lastErr = e; }
|
|
456
|
+
}
|
|
457
|
+
if (!(r && r.ok)) {
|
|
458
|
+
try {
|
|
459
|
+
var chunks = chunkText(text);
|
|
460
|
+
var outs = [];
|
|
461
|
+
for (var k = 0; k < chunks.length; k++) outs.push(await gtxChunk(chunks[k], target));
|
|
462
|
+
r = { ok: true, text: outs.join(""), provider: "google-direct" };
|
|
463
|
+
} catch (e2) { r = null; if (e2 && e2.message) lastErr = e2; }
|
|
464
|
+
}
|
|
465
|
+
if (r && r.ok) {
|
|
466
|
+
stats.ok++; stats.lastError = null; stats.lastProvider = r.provider; stats.lastModel = r.model || null;
|
|
467
|
+
memSet(key, r);
|
|
468
|
+
return r;
|
|
469
|
+
}
|
|
470
|
+
throw (lastErr || new Error("translate unavailable"));
|
|
471
|
+
} catch (e) {
|
|
472
|
+
stats.fail++;
|
|
473
|
+
stats.lastError = e instanceof Error ? e.message : String(e);
|
|
474
|
+
return { ok: false, text: text, skipped: false };
|
|
475
|
+
}
|
|
476
|
+
})();
|
|
477
|
+
pend.set(key, p);
|
|
478
|
+
p.then(function () { pend.delete(key); }, function () { pend.delete(key); });
|
|
479
|
+
return p;
|
|
480
|
+
}
|
|
481
|
+
function memSet(key, entry) {
|
|
482
|
+
cacheMemSet(key, entry);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function useXlate(text, enabled) {
|
|
486
|
+
var s = useStore();
|
|
487
|
+
var active = !!(enabled && text && text.trim());
|
|
488
|
+
var key = active ? s.lang + "|" + fnv(text) : null;
|
|
489
|
+
var pair = useState(function () { return key !== null ? cacheMemGet(key) : undefined; });
|
|
490
|
+
var entry = pair[0];
|
|
491
|
+
var setEntry = pair[1];
|
|
492
|
+
useEffect(function () {
|
|
493
|
+
if (!active || key === null) { setEntry(undefined); return undefined; }
|
|
494
|
+
var alive = true;
|
|
495
|
+
var hit = cacheMemGet(key);
|
|
496
|
+
if (hit !== undefined) { setEntry(hit); return undefined; }
|
|
497
|
+
requestTranslate(text, s.lang).then(function (r) {
|
|
498
|
+
if (!alive) return;
|
|
499
|
+
setEntry(r);
|
|
500
|
+
if (!(r && r.ok)) {
|
|
501
|
+
// One delayed retry for answer text: failures are not cached, so
|
|
502
|
+
// this re-issues a real request after the backoff window.
|
|
503
|
+
setTimeout(function () {
|
|
504
|
+
if (!alive) return;
|
|
505
|
+
requestTranslate(text, s.lang).then(function (r2) { if (alive) setEntry(r2); });
|
|
506
|
+
}, 3000);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
return function () { alive = false; };
|
|
510
|
+
}, [key, active]);
|
|
511
|
+
return entry;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ------------------------------------------------------------------
|
|
515
|
+
// components
|
|
516
|
+
// ------------------------------------------------------------------
|
|
517
|
+
class XBoundary extends Component {
|
|
518
|
+
constructor(p) { super(p); this.state = { err: null }; }
|
|
519
|
+
static getDerivedStateFromError(e) { return { err: e }; }
|
|
520
|
+
render() {
|
|
521
|
+
if (this.state.err) {
|
|
522
|
+
return createElement("div", { className: "xl-err" }, "[xlate] " + String(this.state.err && this.state.err.message ? this.state.err.message : this.state.err));
|
|
523
|
+
}
|
|
524
|
+
return this.props.children;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function firstLine(t) { var i = t.indexOf("\n"); return i === -1 ? t : t.slice(0, i); }
|
|
529
|
+
function latestLine(t) { var v = t.trimEnd(); var i = v.lastIndexOf("\n"); return i === -1 ? v : v.slice(i + 1); }
|
|
530
|
+
|
|
531
|
+
function splitSentences(text) {
|
|
532
|
+
var re = /[^。!?!?;\n]*[。!?!?;\n]+|[^。!?!?;\n]+/g;
|
|
533
|
+
var out = [], m;
|
|
534
|
+
while ((m = re.exec(text)) !== null) {
|
|
535
|
+
if (m[0].length === 0) { re.lastIndex++; continue; }
|
|
536
|
+
out.push({ start: m.index, end: m.index + m[0].length, text: m[0] });
|
|
537
|
+
}
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
var XL_STREAM_MARGIN = 120;
|
|
541
|
+
var XL_STREAM_MIN_BATCH = 24;
|
|
542
|
+
var XL_BATCH_CHARS = 900;
|
|
543
|
+
function ThinkRow(props) {
|
|
544
|
+
var text = props.text;
|
|
545
|
+
var running = props.running;
|
|
546
|
+
var s = useStore();
|
|
547
|
+
var pairOpen = useState(false);
|
|
548
|
+
var open = pairOpen[0], setOpen = pairOpen[1];
|
|
549
|
+
var enabled = s.think;
|
|
550
|
+
var lang = s.lang; var eager = props.eager !== false; var mode = s.mode || "lazy";
|
|
551
|
+
var refPair = useState(function () { return { current: { segIdx: 0, transText: "", done: false, failed: false, busy: false } }; });
|
|
552
|
+
var tRef = refPair[0];
|
|
553
|
+
var tickPair = useState(0);
|
|
554
|
+
var tick = tickPair[0], bump = tickPair[1];
|
|
555
|
+
function mutate(fn) {
|
|
556
|
+
tRef.current = fn(tRef.current);
|
|
557
|
+
bump(function (n) { return n + 1; });
|
|
558
|
+
}
|
|
559
|
+
useEffect(function () {
|
|
560
|
+
tRef.current = { segIdx: 0, transText: "", done: false, failed: false, busy: false, failCount: 0, retryAt: 0 };
|
|
561
|
+
bump(function (n) { return n + 1; });
|
|
562
|
+
}, [lang, enabled]);
|
|
563
|
+
// Latest text/running via ref so the self-driven loop below never
|
|
564
|
+
// re-runs on streaming text changes. The old per-effect approach
|
|
565
|
+
// depended on [text, running, tick]: every mutate bumped tick, the
|
|
566
|
+
// cleanup set alive=false, and the re-run saw busy=true and bailed
|
|
567
|
+
// — the in-flight result was dropped and transLen stayed 0 forever
|
|
568
|
+
// (2026-08-24 root cause: chain works, thinking rows never fill).
|
|
569
|
+
var stateRef = useState(function () { return { current: {} }; })[0];
|
|
570
|
+
stateRef.current = { text: text, running: running };
|
|
571
|
+
useEffect(function () {
|
|
572
|
+
if (!enabled) return undefined; if (!(mode === "eager" || (mode === "lazy" && eager)) && !open) return undefined;
|
|
573
|
+
var alive = true;
|
|
574
|
+
(async function () {
|
|
575
|
+
while (alive) {
|
|
576
|
+
var cur = tRef.current;
|
|
577
|
+
if (cur.failed || cur.done) return;
|
|
578
|
+
if (cur.busy) { await sleepMs(150); continue; }
|
|
579
|
+
if (cur.retryAt && Date.now() < cur.retryAt) { await sleepMs(300); continue; }
|
|
580
|
+
var latest = stateRef.current.text;
|
|
581
|
+
if (!latest || !latest.trim()) return;
|
|
582
|
+
var isRunning = stateRef.current.running;
|
|
583
|
+
var segs = splitSentences(latest);
|
|
584
|
+
if (cur.segIdx >= segs.length) {
|
|
585
|
+
mutate(function (p) { return Object.assign({}, p, { done: true }); });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
// Streaming: only translate sentences before the live
|
|
589
|
+
// tail margin. Settled: translate everything, in small
|
|
590
|
+
// batches (local 7B models degrade on long blobs).
|
|
591
|
+
var batch = "", count = 0, nextIdx = cur.segIdx;
|
|
592
|
+
for (var i = cur.segIdx; i < segs.length; i++) {
|
|
593
|
+
if (isRunning && segs[i].end > latest.length - XL_STREAM_MARGIN) break;
|
|
594
|
+
batch += segs[i].text;
|
|
595
|
+
nextIdx = i + 1;
|
|
596
|
+
count++;
|
|
597
|
+
if (batch.length >= XL_BATCH_CHARS) break;
|
|
598
|
+
}
|
|
599
|
+
if (count === 0 || (isRunning && batch.trim().length < XL_STREAM_MIN_BATCH)) {
|
|
600
|
+
await sleepMs(200);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
mutate(function (p) { return Object.assign({}, p, { busy: true }); });
|
|
604
|
+
var r = null;
|
|
605
|
+
try { r = await requestTranslate(batch, lang); }
|
|
606
|
+
catch (e) { r = null; }
|
|
607
|
+
if (!alive) return;
|
|
608
|
+
mutate(function (p) {
|
|
609
|
+
var next = Object.assign({}, p, { busy: false });
|
|
610
|
+
if (r && r.ok) {
|
|
611
|
+
if (!r.skipped) next.transText = p.transText + r.text;
|
|
612
|
+
next.segIdx = Math.max(p.segIdx, nextIdx);
|
|
613
|
+
} else {
|
|
614
|
+
var fc = (p.failCount || 0) + 1;
|
|
615
|
+
if (fc >= 3) {
|
|
616
|
+
next.failed = true;
|
|
617
|
+
} else {
|
|
618
|
+
next.failCount = fc;
|
|
619
|
+
next.retryAt = Date.now() + 2500;
|
|
620
|
+
setTimeout(function () {
|
|
621
|
+
mutate(function (q) { return Object.assign({}, q, { retryAt: 0 }); });
|
|
622
|
+
}, 2600);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return next;
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
})();
|
|
629
|
+
return function () { alive = false; tRef.current = Object.assign({}, tRef.current, { busy: false }); };
|
|
630
|
+
}, [lang, enabled, mode, eager, open]);
|
|
631
|
+
var t = tRef.current;
|
|
632
|
+
var tt = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
633
|
+
var hasTr = !!(enabled && t.transText && !t.failed);
|
|
634
|
+
var shown = hasTr ? t.transText : text;
|
|
635
|
+
var summary = running ? latestLine(text) : firstLine(shown);
|
|
636
|
+
return createElement("div", { className: "xl-think", "data-state": running ? "running" : "ok" },
|
|
637
|
+
createElement("button", {
|
|
638
|
+
className: "xl-think-row", type: "button",
|
|
639
|
+
onClick: function () { setOpen(function (v) { return !v; }); }
|
|
640
|
+
},
|
|
641
|
+
createElement("span", { className: "xl-chev", "data-open": open || undefined }, "▶"),
|
|
642
|
+
createElement("span", { className: "xl-think-title" }, running ? tt.thinking : tt.thinkTitle),
|
|
643
|
+
createElement("span", { className: "xl-sep", "aria-hidden": true }),
|
|
644
|
+
createElement("span", { className: "xl-think-summary" }, summary),
|
|
645
|
+
t.failed ? createElement("span", { className: "xl-warn" }, tt.transFail) : null
|
|
646
|
+
),
|
|
647
|
+
open ? createElement("div", { className: "xl-think-body" },
|
|
648
|
+
hasTr ? createElement("div", { className: "xl-trans" }, t.transText) : null,
|
|
649
|
+
running || !hasTr
|
|
650
|
+
? createElement("div", { className: "xl-orig-text" }, text)
|
|
651
|
+
: createElement("details", { className: "xl-orig" },
|
|
652
|
+
createElement("summary", null, tt.original),
|
|
653
|
+
createElement("div", { className: "xl-orig-text" }, text)))
|
|
654
|
+
: null);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function inlineNodes(s, kBase) {
|
|
658
|
+
var out = [];
|
|
659
|
+
var re = /(`[^`\n]+`)|(\*\*[^*]+\*\*)|(\*[^*\n]+\*)|(\[[^\]\n]+\]\([^)\s]+\))/g;
|
|
660
|
+
var last = 0, m, k = 0;
|
|
661
|
+
while ((m = re.exec(s)) !== null) {
|
|
662
|
+
if (m.index > last) out.push(s.slice(last, m.index));
|
|
663
|
+
var tok = m[0];
|
|
664
|
+
k++;
|
|
665
|
+
var key = kBase + "i" + k;
|
|
666
|
+
if (tok.charAt(0) === "`") out.push(createElement("code", { key: key, className: "xl-code" }, tok.slice(1, -1)));
|
|
667
|
+
else if (tok.indexOf("**") === 0) out.push(createElement("strong", { key: key }, tok.slice(2, -2)));
|
|
668
|
+
else if (tok.charAt(0) === "*") out.push(createElement("em", { key: key }, tok.slice(1, -1)));
|
|
669
|
+
else {
|
|
670
|
+
var cut = tok.indexOf("](");
|
|
671
|
+
out.push(createElement("a", { key: key, className: "xl-link", href: tok.slice(cut + 2, -1), target: "_blank", rel: "noreferrer" }, tok.slice(1, cut)));
|
|
672
|
+
}
|
|
673
|
+
last = m.index + tok.length;
|
|
674
|
+
}
|
|
675
|
+
if (last < s.length) out.push(s.slice(last));
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
function tableCells(line) {
|
|
679
|
+
var s = line.trim();
|
|
680
|
+
if (s.charAt(0) === "|") s = s.slice(1);
|
|
681
|
+
if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
|
|
682
|
+
return s.split("|").map(function (c) { return c.trim(); });
|
|
683
|
+
}
|
|
684
|
+
function isDelimRow(line) {
|
|
685
|
+
var s = line.trim();
|
|
686
|
+
if (s.charAt(0) === "|") s = s.slice(1);
|
|
687
|
+
if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
|
|
688
|
+
var parts = s.split("|");
|
|
689
|
+
if (!parts.length) return false;
|
|
690
|
+
for (var p = 0; p < parts.length; p++) { if (!/^[\s:-]*$/.test(parts[p])) return false; }
|
|
691
|
+
return /-/.test(s);
|
|
692
|
+
}
|
|
693
|
+
function Markdownish(props) {
|
|
694
|
+
var lines = props.text.split("\n");
|
|
695
|
+
var blocks = [];
|
|
696
|
+
var i = 0, pk = 0;
|
|
697
|
+
function nk() { pk++; return "b" + pk; }
|
|
698
|
+
while (i < lines.length) {
|
|
699
|
+
var line = lines[i];
|
|
700
|
+
if (line.trim() === "") { i++; continue; }
|
|
701
|
+
if (/^\s*```/.test(line)) {
|
|
702
|
+
var buf = [];
|
|
703
|
+
i++;
|
|
704
|
+
while (i < lines.length && !/^\s*```/.test(lines[i])) { buf.push(lines[i]); i++; }
|
|
705
|
+
i++;
|
|
706
|
+
blocks.push(createElement("pre", { key: nk(), className: "xl-pre" }, createElement("code", null, buf.join("\n"))));
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
var h = line.match(/^(#{1,4})\s+(.*)$/);
|
|
710
|
+
if (h) {
|
|
711
|
+
blocks.push(createElement("div", { key: nk(), className: "xl-h" }, inlineNodes(h[2], nk())));
|
|
712
|
+
i++;
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (/^\s*(?:[-*_]\s*){3,}$/.test(line)) { blocks.push(createElement("hr", { key: nk() })); i++; continue; }
|
|
716
|
+
if (/^\s*>\s?/.test(line)) {
|
|
717
|
+
var bq = [];
|
|
718
|
+
while (i < lines.length && /^\s*>\s?/.test(lines[i])) { bq.push(lines[i].replace(/^\s*>\s?/, "")); i++; }
|
|
719
|
+
blocks.push(createElement("blockquote", { key: nk(), className: "xl-bq" }, inlineNodes(bq.join(" "), nk())));
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (/^\s*[-*+]\s+/.test(line)) {
|
|
723
|
+
var ulItems = [];
|
|
724
|
+
while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) { ulItems.push(lines[i].replace(/^\s*[-*+]\s+/, "")); i++; }
|
|
725
|
+
blocks.push(createElement("ul", { key: nk(), className: "xl-ul" }, ulItems.map(function (it, n) {
|
|
726
|
+
return createElement("li", { key: n }, inlineNodes(it, nk() + "l" + n));
|
|
727
|
+
})));
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (/^\s*\d+[.)]\s+/.test(line)) {
|
|
731
|
+
var olItems = [];
|
|
732
|
+
while (i < lines.length && /^\s*\d+[.)]\s+/.test(lines[i])) { olItems.push(lines[i].replace(/^\s*\d+[.)]\s+/, "")); i++; }
|
|
733
|
+
blocks.push(createElement("ol", { key: nk(), className: "xl-ol" }, olItems.map(function (it, n) {
|
|
734
|
+
return createElement("li", { key: n }, inlineNodes(it, nk() + "l" + n));
|
|
735
|
+
})));
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (/^\s*\|/.test(line) && i + 1 < lines.length && isDelimRow(lines[i + 1])) {
|
|
739
|
+
var headCells = tableCells(line);
|
|
740
|
+
i += 2;
|
|
741
|
+
var bodyRows = [];
|
|
742
|
+
while (i < lines.length && /^\s*\|/.test(lines[i])) { bodyRows.push(tableCells(lines[i])); i++; }
|
|
743
|
+
var thead = createElement("thead", null, createElement("tr", null, headCells.map(function (c, n) {
|
|
744
|
+
return createElement("th", { key: n }, inlineNodes(c, nk() + "h" + n));
|
|
745
|
+
})));
|
|
746
|
+
var tbody = bodyRows.map(function (r, n) {
|
|
747
|
+
return createElement("tr", { key: n }, r.map(function (c, j) {
|
|
748
|
+
return createElement("td", { key: j }, inlineNodes(c, nk() + "d" + n + "j" + j));
|
|
749
|
+
}));
|
|
750
|
+
});
|
|
751
|
+
blocks.push(createElement("div", { key: nk(), className: "xl-table-wrap" },
|
|
752
|
+
createElement("table", { className: "xl-table" }, thead, createElement("tbody", null, tbody))));
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
var para = [line];
|
|
756
|
+
i++;
|
|
757
|
+
while (i < lines.length && lines[i].trim() !== "" && !/^\s*(?:```|#{1,4}\s|>|[-*+]\s|\d+[.)]\s)/.test(lines[i])) { para.push(lines[i]); i++; }
|
|
758
|
+
blocks.push(createElement("p", { key: nk(), className: "xl-p" }, inlineNodes(para.join("\n"), nk())));
|
|
759
|
+
}
|
|
760
|
+
return createElement("div", { className: "xl-md" }, blocks);
|
|
761
|
+
}
|
|
762
|
+
function TextBlock(props) {
|
|
763
|
+
var s = useStore();
|
|
764
|
+
var tt = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
765
|
+
var tr = useXlate(props.text, s.ans && !props.streaming);
|
|
766
|
+
var hasTr = !!(tr && tr.ok && !tr.skipped && tr.text !== props.text);
|
|
767
|
+
// Official MarkdownText when available: keeps the full official
|
|
768
|
+
// formatting (code copy, highlighting, tables...). Translated text
|
|
769
|
+
// replaces the body when answer translation is on; original stays in
|
|
770
|
+
// a collapsible details block.
|
|
771
|
+
var codeLabels = MarkdownText ? { copyLabel: tt.copy, copiedLabel: tt.copied } : undefined;
|
|
772
|
+
var MDT = MarkdownText || Markdownish;
|
|
773
|
+
var body = hasTr ? tr.text : props.text;
|
|
774
|
+
return createElement("div", null,
|
|
775
|
+
createElement(MDT, MarkdownText
|
|
776
|
+
? { text: body, streaming: props.streaming, codeLabels: codeLabels }
|
|
777
|
+
: { text: body, streaming: props.streaming }),
|
|
778
|
+
hasTr ? createElement("details", { className: "xl-orig" },
|
|
779
|
+
createElement("summary", null, tt.original),
|
|
780
|
+
createElement("div", null, createElement(MDT, MarkdownText
|
|
781
|
+
? { text: props.text, codeLabels: codeLabels }
|
|
782
|
+
: { text: props.text }))) : null);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function AssistantView(props) {
|
|
786
|
+
var node = props.node;
|
|
787
|
+
var data = node.data;
|
|
788
|
+
var useTurnData = props.useTurnData;
|
|
789
|
+
var tt = UI_TEXT[useStore().lang] || UI_TEXT.en;
|
|
790
|
+
var tail = useTurnData("turn-tail");
|
|
791
|
+
var loc = node.location || {};
|
|
792
|
+
var turn = (loc.kind === "turn" || loc.kind === "step") ? loc.turn : undefined;
|
|
793
|
+
var owner = undefined;
|
|
794
|
+
if (turn && turn.status === "closed" && data.finalNode !== undefined && tail && tail.closing && tail.closing.finalNode && tail.closing.finalNode.seq === data.finalNode.seq) {
|
|
795
|
+
owner = { turn: turn, seq: data.finalNode.seq, openFile: props.openFile };
|
|
796
|
+
}
|
|
797
|
+
var mentions = owner !== undefined && props.fileMentions ? props.fileMentions(owner) : undefined;
|
|
798
|
+
var streaming = data.status === "running";
|
|
799
|
+
var interrupted = data.status === "interrupted"; var isLatest = !streaming && data.finalNode !== undefined && tail && tail.closing && tail.closing.finalNode && tail.closing.finalNode.seq === data.finalNode.seq; var eager = streaming || isLatest;
|
|
800
|
+
var blocks = data.blocks || [];
|
|
801
|
+
var hasVisible = streaming || interrupted === true || blocks.some(function (b) { return b.kind !== "tool-call"; });
|
|
802
|
+
if (!hasVisible) return null;
|
|
803
|
+
var rendered = [];
|
|
804
|
+
var last = blocks.length - 1;
|
|
805
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
806
|
+
var block = blocks[i];
|
|
807
|
+
if (!block) continue;
|
|
808
|
+
if (block.kind === "text") {
|
|
809
|
+
rendered.push(createElement(TextBlock, { key: "t" + i, text: block.text, streaming: streaming }));
|
|
810
|
+
} else if (block.kind === "reasoning") {
|
|
811
|
+
rendered.push(createElement(ThinkRow, { key: "r" + i, text: block.text, running: streaming && i === last, eager: eager }));
|
|
812
|
+
} else if (block.kind === "image") {
|
|
813
|
+
var start = i;
|
|
814
|
+
var group = [block];
|
|
815
|
+
while (i + 1 < blocks.length && blocks[i + 1] && blocks[i + 1].kind === "image") { group.push(blocks[i + 1]); i++; }
|
|
816
|
+
rendered.push(createElement(Fragment, { key: "g" + start },
|
|
817
|
+
props.renderMessageImages({ images: group.map(function (b) { return { attachment: b.attachment }; }), align: "start" })));
|
|
818
|
+
} else if (block.kind === "tool-call") {
|
|
819
|
+
// grouped into tool rows by the chat view
|
|
820
|
+
} else {
|
|
821
|
+
rendered.push(createElement("pre", { key: "u" + i, className: "xl-pre" }, JSON.stringify(block, null, 2)));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return createElement("div", null, rendered,
|
|
825
|
+
interrupted ? createElement("span", { className: "xl-warn" }, (props.t && props.t("message.stopped")) || tt.stopped) : null);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function planSummaryOf(todos) {
|
|
829
|
+
var done = 0, active = null, extra = 0;
|
|
830
|
+
for (var i = 0; i < todos.length; i++) {
|
|
831
|
+
var td = todos[i];
|
|
832
|
+
if (td.status === "completed") done++;
|
|
833
|
+
else if (td.status === "in_progress") {
|
|
834
|
+
if (active === null && typeof td.content === "string") active = td.content;
|
|
835
|
+
else extra++;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return { done: done, total: todos.length, active: active, extra: extra };
|
|
839
|
+
}
|
|
840
|
+
function TodoItemLine(props) {
|
|
841
|
+
var s = useStore();
|
|
842
|
+
var content = typeof props.item.content === "string" ? props.item.content : "";
|
|
843
|
+
var tr = useXlate(content, s.todo);
|
|
844
|
+
var hasTr = !!(tr && tr.ok && !tr.skipped && tr.text !== content);
|
|
845
|
+
var glyph = props.item.status === "completed" ? "✓" : props.item.status === "in_progress" ? "◐" : "○";
|
|
846
|
+
return createElement("div", { className: "xl-item" },
|
|
847
|
+
createElement("span", { className: "xl-glyph" }, glyph),
|
|
848
|
+
createElement("span", {
|
|
849
|
+
className: "xl-item-text",
|
|
850
|
+
title: hasTr ? content : undefined
|
|
851
|
+
}, hasTr ? tr.text : content));
|
|
852
|
+
}
|
|
853
|
+
function XTodoRow(props) {
|
|
854
|
+
var s = useStore();
|
|
855
|
+
var tt = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
856
|
+
var pair = useState(false);
|
|
857
|
+
var open = pair[0], setOpen = pair[1];
|
|
858
|
+
var block = props.block;
|
|
859
|
+
var argsRaw = (("kind" in block) && block.call && typeof block.call.argsRaw === "string" ? block.call.argsRaw : (typeof block.argsRaw === "string" ? block.argsRaw : "")) || "";
|
|
860
|
+
var todos = null;
|
|
861
|
+
try {
|
|
862
|
+
var parsed = JSON.parse(argsRaw);
|
|
863
|
+
var allOk = Array.isArray(parsed && parsed.todos);
|
|
864
|
+
if (allOk) {
|
|
865
|
+
todos = parsed.todos;
|
|
866
|
+
for (var i = 0; i < todos.length; i++) {
|
|
867
|
+
if (!todos[i] || typeof todos[i] !== "object") { todos = null; break; }
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
} catch (e) { todos = null; }
|
|
871
|
+
var summary = todos !== null ? planSummaryOf(todos) : null;
|
|
872
|
+
var tr = useXlate(summary && summary.active !== null ? summary.active : null, s.todo);
|
|
873
|
+
var hasTr = !!(tr && tr.ok && !tr.skipped && summary && summary.active !== null && tr.text !== summary.active);
|
|
874
|
+
var head = summary
|
|
875
|
+
? tt.doneFmt.replace("{d}", String(summary.done)).replace("{t}", String(summary.total))
|
|
876
|
+
: tt.taskList;
|
|
877
|
+
var summaryText = summary && summary.active !== null
|
|
878
|
+
? head + " · " + (hasTr ? tr.text : summary.active)
|
|
879
|
+
: head;
|
|
880
|
+
return createElement("div", { className: "xl-todo" },
|
|
881
|
+
createElement("button", {
|
|
882
|
+
className: "xl-row", type: "button",
|
|
883
|
+
onClick: function () { setOpen(function (v) { return !v; }); }
|
|
884
|
+
},
|
|
885
|
+
createElement("span", { className: "xl-dot" + (summary && summary.done === summary.total ? " xl-dot-ok" : "") }),
|
|
886
|
+
createElement("span", { className: "xl-title" }, tt.tasks),
|
|
887
|
+
createElement("span", { className: "xl-sep", "aria-hidden": true }),
|
|
888
|
+
createElement("span", { className: "xl-summary" }, summaryText),
|
|
889
|
+
summary && summary.extra > 0 ? createElement("span", { className: "xl-suffix" }, "+" + summary.extra) : null,
|
|
890
|
+
typeof props.inspect === "function"
|
|
891
|
+
? createElement("button", {
|
|
892
|
+
className: "xl-inspect", type: "button",
|
|
893
|
+
onClick: function (e) { e.stopPropagation(); props.inspect(); }
|
|
894
|
+
}, "⤢")
|
|
895
|
+
: null
|
|
896
|
+
),
|
|
897
|
+
open && todos !== null
|
|
898
|
+
? createElement("div", { className: "xl-items" }, todos.map(function (it, n) {
|
|
899
|
+
return createElement(TodoItemLine, { key: n, item: it });
|
|
900
|
+
}))
|
|
901
|
+
: null);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// The plan strip above the composer (official ui-conversation TodoDock,
|
|
905
|
+
// 'conversation.input.dock' id 'todo'). Shadowing that id at a lower
|
|
906
|
+
// priority replaces the shipped panel with a translated one: title and
|
|
907
|
+
// per-status counts stay in the app locale (via the t seat), item
|
|
908
|
+
// contents go through the same useXlate path as the in-flow todo row.
|
|
909
|
+
function XDockTodoItem(props) {
|
|
910
|
+
var s = useStore();
|
|
911
|
+
var gid = useId();
|
|
912
|
+
var content = typeof props.item.content === "string" ? props.item.content : "";
|
|
913
|
+
var tr = useXlate(content, s.todo);
|
|
914
|
+
var hasTr = !!(tr && tr.ok && !tr.skipped && tr.text !== content);
|
|
915
|
+
var status = props.item.status;
|
|
916
|
+
var glyph = null;
|
|
917
|
+
if (status === "completed") {
|
|
918
|
+
glyph = createElement("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, className: "xl-dock-glyph-ok" },
|
|
919
|
+
createElement("circle", { cx: 7, cy: 7, r: 6.4, stroke: "currentColor", strokeWidth: 1.2 }),
|
|
920
|
+
createElement("path", { d: "M10.9631 5.71411L7.70154 8.97571C7.48011 9.19714 7.27736 9.40099 7.09229 9.54993C6.89742 9.70669 6.66314 9.85279 6.3634 9.90027C6.2049 9.92534 6.04339 9.92534 5.88489 9.90027C5.58515 9.85279 5.35087 9.70669 5.15601 9.54993C4.97093 9.40099 4.76818 9.19714 4.54675 8.97571L3.03516 7.46411L3.96313 6.53613L5.47473 8.04773C5.7169 8.28989 5.86196 8.43389 5.97888 8.52795C6.08597 8.61409 6.10875 8.60701 6.08997 8.604C6.11259 8.60758 6.13571 8.60758 6.15833 8.604C6.13954 8.60701 6.16232 8.61409 6.26941 8.52795C6.38633 8.43389 6.53139 8.28989 6.77356 8.04773L10.0352 4.78613L10.9631 5.71411Z", fill: "currentColor" }));
|
|
921
|
+
} else if (status === "in_progress") {
|
|
922
|
+
glyph = createElement("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, className: "xl-dock-glyph-run" },
|
|
923
|
+
createElement("defs", null,
|
|
924
|
+
createElement("linearGradient", { id: gid, x1: 2.5, y1: 12, x2: 10.5, y2: 3.5, gradientUnits: "userSpaceOnUse" },
|
|
925
|
+
createElement("stop", { stopColor: "currentColor" }),
|
|
926
|
+
createElement("stop", { offset: 1, stopColor: "currentColor", stopOpacity: 0 }))),
|
|
927
|
+
createElement("circle", { cx: 7, cy: 7, r: 6.4, stroke: "url(#" + gid + ")", strokeWidth: 1.2 }));
|
|
928
|
+
} else {
|
|
929
|
+
glyph = createElement("svg", { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, className: "xl-dock-glyph-wait" },
|
|
930
|
+
createElement("circle", { cx: 7, cy: 7, r: 6.4, stroke: "currentColor", strokeWidth: 1.2, strokeDasharray: "2.4 2.4" }));
|
|
931
|
+
}
|
|
932
|
+
return createElement("li", { className: "xl-dock-item", "data-status": status },
|
|
933
|
+
createElement("span", { className: "xl-dock-glyph", "aria-hidden": true }, glyph),
|
|
934
|
+
createElement("span", {
|
|
935
|
+
className: "xl-dock-content",
|
|
936
|
+
title: hasTr ? content : undefined
|
|
937
|
+
}, hasTr ? tr.text : content));
|
|
938
|
+
}
|
|
939
|
+
function XDockTodo(props) {
|
|
940
|
+
var s = useStore();
|
|
941
|
+
var t = props.t;
|
|
942
|
+
var todos = props.useProjection("todos");
|
|
943
|
+
var list = Array.isArray(todos) ? todos : [];
|
|
944
|
+
var pairOpen = useState(false);
|
|
945
|
+
var open = pairOpen[0], setOpen = pairOpen[1];
|
|
946
|
+
if (list.length === 0) return null;
|
|
947
|
+
var done = 0, active = 0;
|
|
948
|
+
for (var i = 0; i < list.length; i++) {
|
|
949
|
+
var st = list[i] && list[i].status;
|
|
950
|
+
if (st === "completed") done++;
|
|
951
|
+
else if (st === "in_progress") active++;
|
|
952
|
+
}
|
|
953
|
+
var pending = list.length - done - active;
|
|
954
|
+
var segs = [];
|
|
955
|
+
function seg(key, params, count) {
|
|
956
|
+
if (count <= 0) return;
|
|
957
|
+
var label = null;
|
|
958
|
+
try { label = t ? t(key, params) : null; } catch (e) { label = null; }
|
|
959
|
+
if (typeof label === "string" && label) segs.push(label);
|
|
960
|
+
}
|
|
961
|
+
seg("todo.progress.done", { done: done }, done);
|
|
962
|
+
seg("todo.progress.active", { active: active }, active);
|
|
963
|
+
seg("todo.progress.pending", { pending: pending }, pending);
|
|
964
|
+
var title = null;
|
|
965
|
+
try { title = t ? t("todo.title") : null; } catch (e) { title = null; }
|
|
966
|
+
if (typeof title !== "string" || !title) title = "Tasks";
|
|
967
|
+
var progress = segs.join("\u2002·\u2002");
|
|
968
|
+
return createElement("section", { className: "xl-dock", "data-testid": "todo-panel", "aria-label": title },
|
|
969
|
+
createElement("div", { className: "xl-dock-body" },
|
|
970
|
+
createElement("button", {
|
|
971
|
+
type: "button", className: "xl-dock-header", "aria-expanded": open,
|
|
972
|
+
onClick: function () { setOpen(function (v) { return !v; }); }
|
|
973
|
+
},
|
|
974
|
+
IconChecklist ? createElement("span", { className: "xl-dock-lead", "aria-hidden": true }, createElement(IconChecklist, null)) : null,
|
|
975
|
+
createElement("span", { className: "xl-dock-title" }, title),
|
|
976
|
+
createElement("span", { className: "xl-dock-progress" }, progress),
|
|
977
|
+
createElement("span", { className: "xl-dock-chev", "aria-hidden": true },
|
|
978
|
+
open
|
|
979
|
+
? (IconChevronDown ? createElement(IconChevronDown, null) : null)
|
|
980
|
+
: (IconChevronUp ? createElement(IconChevronUp, null) : null))),
|
|
981
|
+
open ? createElement("ul", { className: "xl-dock-list" }, list.map(function (it) {
|
|
982
|
+
return createElement(XDockTodoItem, { key: it.content, item: it });
|
|
983
|
+
})) : null));
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function StatusBlock() {
|
|
987
|
+
var s = useStore();
|
|
988
|
+
var t = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
989
|
+
return createElement("div", null,
|
|
990
|
+
createElement("div", { className: "xl-kv" }, createElement("b", null, t.status), createElement("span", null, stats.ok + " / " + stats.fail)),
|
|
991
|
+
createElement("div", { className: "xl-kv" }, createElement("b", null, t.providerOf), createElement("span", null, stats.lastProvider ? ((t.labels[stats.lastProvider] || stats.lastProvider) + (stats.lastModel ? " · " + stats.lastModel : "")) : t.providerDesc)),
|
|
992
|
+
createElement("div", { className: "xl-kv" }, createElement("b", null, t.cacheEntries), createElement("span", null, String(mem.size))),
|
|
993
|
+
stats.lastError ? createElement("div", { className: "xl-err" }, t.lastError + ": " + stats.lastError) : null,
|
|
994
|
+
createElement("button", {
|
|
995
|
+
className: "xl-btn", type: "button",
|
|
996
|
+
onClick: function () {
|
|
997
|
+
mem.clear();
|
|
998
|
+
try {
|
|
999
|
+
var keys = [];
|
|
1000
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
1001
|
+
var k = localStorage.key(i);
|
|
1002
|
+
if (k && k.indexOf(MEM_KEY) === 0) keys.push(k);
|
|
1003
|
+
}
|
|
1004
|
+
for (var j = 0; j < keys.length; j++) localStorage.removeItem(keys[j]);
|
|
1005
|
+
} catch (e) {}
|
|
1006
|
+
}
|
|
1007
|
+
}, t.clearCache));
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function ProviderSection() {
|
|
1011
|
+
var s = useStore();
|
|
1012
|
+
var t = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
1013
|
+
var pair = useState(null);
|
|
1014
|
+
var cfg = pair[0], setCfg = pair[1];
|
|
1015
|
+
var msgPair = useState("");
|
|
1016
|
+
var msg = msgPair[0], setMsg = msgPair[1];
|
|
1017
|
+
var modelsPair = useState(null);
|
|
1018
|
+
var models = modelsPair[0], setModels = modelsPair[1];
|
|
1019
|
+
useEffect(function () {
|
|
1020
|
+
var alive = true;
|
|
1021
|
+
var tries = 0;
|
|
1022
|
+
function attempt() {
|
|
1023
|
+
if (!alive) return;
|
|
1024
|
+
fetch("/_xlate/config").then(function (r) {
|
|
1025
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
1026
|
+
return r.json();
|
|
1027
|
+
}).then(function (c) { if (alive) setCfg(c); })
|
|
1028
|
+
.catch(function () {
|
|
1029
|
+
if (!alive) return;
|
|
1030
|
+
tries++;
|
|
1031
|
+
if (tries < 15) { setTimeout(attempt, 1500); }
|
|
1032
|
+
else { setCfg(null); }
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
attempt();
|
|
1036
|
+
return function () { alive = false; };
|
|
1037
|
+
}, []);
|
|
1038
|
+
useEffect(function () {
|
|
1039
|
+
var alive = true;
|
|
1040
|
+
fetch("/_xlate/models").then(function (r) { return r.json(); })
|
|
1041
|
+
.then(function (j) { if (alive) setModels(j && Array.isArray(j.models) ? j.models : []); })
|
|
1042
|
+
.catch(function () { if (alive) setModels([]); });
|
|
1043
|
+
return function () { alive = false; };
|
|
1044
|
+
}, []);
|
|
1045
|
+
// Local-model download flow: pull state + custom-model input + polling.
|
|
1046
|
+
var pullPair = useState(null);
|
|
1047
|
+
var pull = pullPair[0], setPull = pullPair[1];
|
|
1048
|
+
var customPair = useState("");
|
|
1049
|
+
var customModel = customPair[0], setCustomModel = customPair[1];
|
|
1050
|
+
var showPullPair = useState(false);
|
|
1051
|
+
var showPull = showPullPair[0], setShowPull = showPullPair[1];
|
|
1052
|
+
// On mount, resume an in-flight download so re-entering settings
|
|
1053
|
+
// shows the live progress without pressing "+" again (2026-08-25).
|
|
1054
|
+
useEffect(function () {
|
|
1055
|
+
var alive = true;
|
|
1056
|
+
fetch("/_xlate/model/pull-status").then(function (r) { return r.json(); })
|
|
1057
|
+
.then(function (j) {
|
|
1058
|
+
if (!alive || !j || !Array.isArray(j.jobs)) return;
|
|
1059
|
+
var active = null;
|
|
1060
|
+
for (var i = 0; i < j.jobs.length; i++) {
|
|
1061
|
+
if (!j.jobs[i].done && !j.jobs[i].error) { active = j.jobs[i]; break; }
|
|
1062
|
+
}
|
|
1063
|
+
if (active) setPull({ model: active.model, status: active.status, percent: active.percent, done: active.done, error: active.error });
|
|
1064
|
+
})
|
|
1065
|
+
.catch(function () {});
|
|
1066
|
+
return function () { alive = false; };
|
|
1067
|
+
}, []);
|
|
1068
|
+
useEffect(function () {
|
|
1069
|
+
if (!pull || pull.done || pull.error) return undefined;
|
|
1070
|
+
var alive = true;
|
|
1071
|
+
var timer = setInterval(function () {
|
|
1072
|
+
fetch("/_xlate/model/pull-status").then(function (r) { return r.json(); })
|
|
1073
|
+
.then(function (j) {
|
|
1074
|
+
if (!alive) return;
|
|
1075
|
+
var job = null;
|
|
1076
|
+
if (j && Array.isArray(j.jobs)) {
|
|
1077
|
+
for (var i = 0; i < j.jobs.length; i++) {
|
|
1078
|
+
if (j.jobs[i].model === pull.model) { job = j.jobs[i]; break; }
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!job) return;
|
|
1082
|
+
setPull({ model: job.model, status: job.status, percent: job.percent, done: job.done, error: job.error });
|
|
1083
|
+
if (job.done || job.error) {
|
|
1084
|
+
fetch("/_xlate/models").then(function (r) { return r.json(); })
|
|
1085
|
+
.then(function (mj) { if (alive && mj) setModels(mj.models || []); }).catch(function () {});
|
|
1086
|
+
fetch("/_xlate/config").then(function (r) { return r.json(); })
|
|
1087
|
+
.then(function (c) { if (alive) setCfg(c); }).catch(function () {});
|
|
1088
|
+
}
|
|
1089
|
+
})
|
|
1090
|
+
.catch(function () {});
|
|
1091
|
+
}, 1000);
|
|
1092
|
+
return function () { alive = false; clearInterval(timer); };
|
|
1093
|
+
}, [pull ? pull.model + (pull.done ? "|1" : "|0") + (pull.error ? "|1" : "|0") : ""]);
|
|
1094
|
+
if (cfg === null) {
|
|
1095
|
+
return createElement("div", { className: "xl-note" }, t.providerUnavailable);
|
|
1096
|
+
}
|
|
1097
|
+
var startPull = function (m) {
|
|
1098
|
+
if (!m || !m.trim()) return;
|
|
1099
|
+
var name = m.trim();
|
|
1100
|
+
setPull({ model: name, status: "starting", percent: 0, done: false, error: null });
|
|
1101
|
+
fetch("/_xlate/model/pull", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: name }) })
|
|
1102
|
+
.catch(function () { setPull({ model: name, status: "error", percent: 0, done: false, error: "network" }); });
|
|
1103
|
+
};
|
|
1104
|
+
var testConn = function () {
|
|
1105
|
+
setMsg(t.testing);
|
|
1106
|
+
fetch("/_xlate/translate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: "Hello world, this is a connection test. [" + Date.now().toString(36) + "]", target: store.lang }) })
|
|
1107
|
+
.then(function (r) { return r.json(); })
|
|
1108
|
+
.then(function (j) {
|
|
1109
|
+
setMsg(j && j.ok ? t.connOk + " ✓ (" + (t.labels[j.provider] || j.provider) + (j.model ? ": " + j.model : "") + ")" : t.connFail + ": " + (j.error || "unknown"));
|
|
1110
|
+
})
|
|
1111
|
+
.catch(function (e) { setMsg(t.connFail + ": " + (e && e.message ? e.message : e)); });
|
|
1112
|
+
};
|
|
1113
|
+
var priorities = ["google", "bing", "openai"];
|
|
1114
|
+
var pickModel = function (e) {
|
|
1115
|
+
var m = e.target.value;
|
|
1116
|
+
setCfg(Object.assign({}, cfg, { providers: Object.assign({}, cfg.providers, { openai: Object.assign({}, cfg.providers.openai, { model: m }) }) }));
|
|
1117
|
+
fetch("/_xlate/config", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providers: { openai: { model: m } } }) })
|
|
1118
|
+
.then(function (r) { return r.json(); })
|
|
1119
|
+
.then(function (c) { setCfg(c); })
|
|
1120
|
+
.catch(function () {});
|
|
1121
|
+
};
|
|
1122
|
+
var openaiCfg = cfg.providers && cfg.providers.openai ? cfg.providers.openai : {};
|
|
1123
|
+
var downloadButtons = function () {
|
|
1124
|
+
return createElement("div", null,
|
|
1125
|
+
createElement("div", { style: { display: "flex", gap: "6px", marginTop: "4px", flexWrap: "wrap", alignItems: "center" } },
|
|
1126
|
+
createElement("button", { className: "xl-btn", type: "button", onClick: function () { startPull("qwen2.5:7b"); } }, t.downloadModel + " qwen2.5:7b (" + t.recommended + ")"),
|
|
1127
|
+
createElement("button", { className: "xl-btn", type: "button", onClick: function () { startPull("qwen2.5:14b"); } }, t.downloadModel + " qwen2.5:14b")),
|
|
1128
|
+
createElement("div", { style: { display: "flex", gap: "6px", marginTop: "6px", alignItems: "center" } },
|
|
1129
|
+
createElement("input", {
|
|
1130
|
+
type: "text", className: "xl-cfg", style: { flex: "1", padding: "4px 6px", fontSize: "11.5px" },
|
|
1131
|
+
placeholder: t.otherModel + " (qwen3:8b)", value: customModel,
|
|
1132
|
+
onChange: function (e) { setCustomModel(e.target.value); }
|
|
1133
|
+
}),
|
|
1134
|
+
createElement("button", { className: "xl-btn", type: "button", onClick: function () { startPull(customModel); } }, t.startDownload)));
|
|
1135
|
+
};
|
|
1136
|
+
var modelArea = null;
|
|
1137
|
+
if (cfg.priority === "openai") {
|
|
1138
|
+
if (models === null) {
|
|
1139
|
+
modelArea = createElement("div", { className: "xl-note" }, t.loadingModels);
|
|
1140
|
+
} else {
|
|
1141
|
+
// Model picker (when models exist) + a "+" toggle for
|
|
1142
|
+
// downloading more models anytime — previously the download
|
|
1143
|
+
// entry only appeared with zero models, so 14b was
|
|
1144
|
+
// unreachable once 7b was installed (2026-08-25).
|
|
1145
|
+
var picker = null;
|
|
1146
|
+
if (models.length > 0) {
|
|
1147
|
+
picker = createElement("div", { style: { display: "flex", gap: "6px", alignItems: "center" } },
|
|
1148
|
+
createElement("label", { style: { flex: "1 1 auto" } },
|
|
1149
|
+
createElement("span", null, t.model),
|
|
1150
|
+
createElement("select", {
|
|
1151
|
+
className: "xl-sel", value: openaiCfg.model || (models[0] || ""),
|
|
1152
|
+
onChange: pickModel
|
|
1153
|
+
}, models.map(function (m) {
|
|
1154
|
+
return createElement("option", { key: m, value: m }, m);
|
|
1155
|
+
}))),
|
|
1156
|
+
createElement("button", {
|
|
1157
|
+
className: "xl-btn", type: "button",
|
|
1158
|
+
onClick: function () { setShowPull(function (v) { return !v; }); }
|
|
1159
|
+
}, "+"));
|
|
1160
|
+
}
|
|
1161
|
+
var pullArea = null;
|
|
1162
|
+
if (pull && !pull.done && !pull.error) {
|
|
1163
|
+
var stText = t.downloading;
|
|
1164
|
+
// Ollama reports layer downloads as status "pulling
|
|
1165
|
+
// sha256:...", not "downloading" — show the percentage
|
|
1166
|
+
// whenever bytes are moving (2026-08-25).
|
|
1167
|
+
if (pull.status === "verifying sha256 digest" || pull.status === "writing manifest") stText = t.verifying;
|
|
1168
|
+
else if (pull.percent > 0) stText = t.downloading + " " + pull.percent + "%";
|
|
1169
|
+
pullArea = createElement("div", null,
|
|
1170
|
+
createElement("div", { className: "xl-note" }, pull.model + " · " + stText),
|
|
1171
|
+
createElement("div", { className: "xl-progress" },
|
|
1172
|
+
createElement("div", { className: "xl-progress-bar", style: { width: (pull.percent || 0) + "%" } })));
|
|
1173
|
+
} else if (pull && pull.done) {
|
|
1174
|
+
pullArea = createElement("div", { className: "xl-note" }, t.pullDone + " ✓ " + pull.model);
|
|
1175
|
+
} else if (pull && pull.error) {
|
|
1176
|
+
pullArea = createElement("div", null,
|
|
1177
|
+
createElement("div", { className: "xl-err" }, t.pullError + ": " + pull.error),
|
|
1178
|
+
downloadButtons());
|
|
1179
|
+
} else if (models.length === 0) {
|
|
1180
|
+
pullArea = createElement("div", { className: "xl-note" },
|
|
1181
|
+
createElement("div", null, t.noLocalModel + " · " + t.installPrompt),
|
|
1182
|
+
downloadButtons());
|
|
1183
|
+
} else if (showPull) {
|
|
1184
|
+
pullArea = downloadButtons();
|
|
1185
|
+
}
|
|
1186
|
+
modelArea = createElement("div", null, picker, pullArea);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return createElement("div", null,
|
|
1190
|
+
createElement("label", null,
|
|
1191
|
+
createElement("span", null, t.provider),
|
|
1192
|
+
createElement("select", {
|
|
1193
|
+
className: "xl-sel", value: cfg.priority || "google",
|
|
1194
|
+
onChange: function (e) {
|
|
1195
|
+
var p = e.target.value;
|
|
1196
|
+
var next = Object.assign({}, cfg, { priority: p });
|
|
1197
|
+
setCfg(next);
|
|
1198
|
+
// Selecting the local model must also enable the openai
|
|
1199
|
+
// provider — priority and enabled are independent fields,
|
|
1200
|
+
// and a disabled provider is silently skipped by the chain
|
|
1201
|
+
// (falls back to google), which confused users (2026-08-25).
|
|
1202
|
+
var patch = { priority: p };
|
|
1203
|
+
if (p === "openai" && cfg.providers && cfg.providers.openai && !cfg.providers.openai.enabled) {
|
|
1204
|
+
patch.providers = { openai: { enabled: true } };
|
|
1205
|
+
}
|
|
1206
|
+
fetch("/_xlate/config", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patch) })
|
|
1207
|
+
.then(function (r) { return r.json(); })
|
|
1208
|
+
.then(function (c) { setCfg(c); })
|
|
1209
|
+
.catch(function () {});
|
|
1210
|
+
}
|
|
1211
|
+
}, priorities.map(function (p) {
|
|
1212
|
+
return createElement("option", { key: p, value: p }, (t.labels[p] || p));
|
|
1213
|
+
}))),
|
|
1214
|
+
modelArea,
|
|
1215
|
+
createElement("div", { style: { display: "flex", gap: "8px", marginTop: "4px" } },
|
|
1216
|
+
createElement("button", { className: "xl-btn", type: "button", onClick: testConn }, t.testConn)),
|
|
1217
|
+
msg ? createElement("div", { className: "xl-note" }, msg) : null);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function SettingsPanel() {
|
|
1221
|
+
var s = useStore();
|
|
1222
|
+
var t = UI_TEXT[s.lang] || UI_TEXT.en;
|
|
1223
|
+
return createElement("div", { className: "xl-panel" },
|
|
1224
|
+
createElement("label", null,
|
|
1225
|
+
createElement("span", null, t.targetLang),
|
|
1226
|
+
createElement("select", {
|
|
1227
|
+
className: "xl-sel", value: s.lang,
|
|
1228
|
+
onChange: function (e) { store.set({ lang: e.target.value }); }
|
|
1229
|
+
}, LANGS.map(function (l) {
|
|
1230
|
+
return createElement("option", { key: l[0], value: l[0] }, l[1]);
|
|
1231
|
+
}))),
|
|
1232
|
+
createElement("label", null,
|
|
1233
|
+
createElement("input", { type: "checkbox", checked: s.think, onChange: function (e) { store.set({ think: e.target.checked }); } }),
|
|
1234
|
+
createElement("span", null, t.think)),
|
|
1235
|
+
createElement("label", null,
|
|
1236
|
+
createElement("span", null, t.mode),
|
|
1237
|
+
createElement("select", {
|
|
1238
|
+
className: "xl-sel", value: s.mode || "lazy",
|
|
1239
|
+
onChange: function (e) { store.set({ mode: e.target.value }); }
|
|
1240
|
+
},
|
|
1241
|
+
createElement("option", { value: "eager" }, t.modeEager),
|
|
1242
|
+
createElement("option", { value: "lazy" }, t.modeLazy),
|
|
1243
|
+
createElement("option", { value: "expand" }, t.modeExpand))),
|
|
1244
|
+
createElement("label", null,
|
|
1245
|
+
createElement("input", { type: "checkbox", checked: s.todo, onChange: function (e) { store.set({ todo: e.target.checked }); } }),
|
|
1246
|
+
createElement("span", null, t.todo)),
|
|
1247
|
+
createElement("label", null,
|
|
1248
|
+
createElement("input", { type: "checkbox", checked: s.ans, onChange: function (e) { store.set({ ans: e.target.checked }); } }),
|
|
1249
|
+
createElement("span", null, t.ans)),
|
|
1250
|
+
createElement("hr", { className: "xl-hr" }),
|
|
1251
|
+
createElement(ProviderSection, null),
|
|
1252
|
+
createElement("hr", { className: "xl-hr" }),
|
|
1253
|
+
createElement(StatusBlock, null),
|
|
1254
|
+
createElement("hr", { className: "xl-hr" }),
|
|
1255
|
+
createElement("div", { className: "xl-note" }, t.note));
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// ------------------------------------------------------------------
|
|
1259
|
+
// plugin export
|
|
1260
|
+
// ------------------------------------------------------------------
|
|
1261
|
+
var inject = ["slots"];
|
|
1262
|
+
/**
|
|
1263
|
+
* Register the display-layer translators into the conversation view.
|
|
1264
|
+
* @param ctx - client cordis context.
|
|
1265
|
+
*/
|
|
1266
|
+
function apply(ctx) {
|
|
1267
|
+
diag.applied = true;
|
|
1268
|
+
diag.lang = store.lang;
|
|
1269
|
+
var recordSlot = function (name, fn) {
|
|
1270
|
+
try {
|
|
1271
|
+
var d = fn();
|
|
1272
|
+
diag.slots[name] = "ok";
|
|
1273
|
+
return d;
|
|
1274
|
+
} catch (e) {
|
|
1275
|
+
diag.slots[name] = "ERR: " + (e && e.message ? e.message : e);
|
|
1276
|
+
throw e;
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
recordSlot("conversation", function () {
|
|
1280
|
+
return ctx.slots.inject("conversation.chat.node", function () {
|
|
1281
|
+
try {
|
|
1282
|
+
var d = ctx.slots.register(
|
|
1283
|
+
// priority -1: the official ui-conversation AssistantNodeView
|
|
1284
|
+
// registers the same key at priority 0, and keyed slots throw on
|
|
1285
|
+
// same-key + same-priority duplicates (lowest priority renders).
|
|
1286
|
+
// Without this the official renderer wins and the thinking chain
|
|
1287
|
+
// shows untranslated (2026-08-24).
|
|
1288
|
+
{ name: "conversation.chat.node", key: "assistant-step", locale: "conversation", priority: -1 },
|
|
1289
|
+
function (props) {
|
|
1290
|
+
return createElement(XBoundary, null, createElement(AssistantView, props));
|
|
1291
|
+
});
|
|
1292
|
+
diag.slots.conversation = "registered";
|
|
1293
|
+
return d;
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
diag.slots.conversation = "REG-ERR: " + (e && e.message ? e.message : e);
|
|
1296
|
+
throw e;
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
recordSlot("todo", function () {
|
|
1301
|
+
return ctx.slots.inject("tool.call.toolview", function () {
|
|
1302
|
+
return ctx.slots.register(
|
|
1303
|
+
{ name: "tool.call.toolview", key: "todo_write", locale: "conversation" },
|
|
1304
|
+
function (props) {
|
|
1305
|
+
return createElement(XBoundary, null, createElement(XTodoRow, props));
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
// Shadow the official plan strip (TodoDock) so the todo list above
|
|
1310
|
+
// the composer is translated too. Same id 'todo' at a lower priority
|
|
1311
|
+
// replaces the shipped entry; empty list renders nothing, matching
|
|
1312
|
+
// the official panel's self-hiding behaviour.
|
|
1313
|
+
recordSlot("todoDock", function () {
|
|
1314
|
+
return ctx.slots.inject("conversation.input.dock", function () {
|
|
1315
|
+
return ctx.slots.register(
|
|
1316
|
+
{ name: "conversation.input.dock", id: "todo", order: 0, locale: "conversation", priority: -1 },
|
|
1317
|
+
function (props) {
|
|
1318
|
+
return createElement(XBoundary, null, createElement(XDockTodo, props));
|
|
1319
|
+
});
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1322
|
+
// Settings section: the label is registrant-localized — the shell
|
|
1323
|
+
// renders whatever label string the registration carried. Must go
|
|
1324
|
+
// through slots.inject (declaration-aware: register() directly
|
|
1325
|
+
// throws while the slot is not yet declared, which killed apply —
|
|
1326
|
+
// 2026-08-24). To refresh the label on language change we dispose
|
|
1327
|
+
// the injection and re-inject with the new language's title.
|
|
1328
|
+
var sectionInject = null;
|
|
1329
|
+
var lastSectionLang = null;
|
|
1330
|
+
var reRegisterSection = function () {
|
|
1331
|
+
if (store.lang === lastSectionLang) return;
|
|
1332
|
+
lastSectionLang = store.lang;
|
|
1333
|
+
if (sectionInject) { sectionInject(); sectionInject = null; }
|
|
1334
|
+
var st = UI_TEXT[store.lang] || UI_TEXT.en;
|
|
1335
|
+
sectionInject = ctx.slots.inject("settings.section", function () {
|
|
1336
|
+
return ctx.slots.register({ name: "settings.section", id: "dsh-think-translate", order: 50, label: st.sectionTitle }, SettingsPanel);
|
|
1337
|
+
});
|
|
1338
|
+
};
|
|
1339
|
+
recordSlot("settings", reRegisterSection);
|
|
1340
|
+
store.subscribe(reRegisterSection);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
exports.inject = inject;
|
|
1344
|
+
exports.apply = apply;
|
|
1345
|
+
return module.exports;
|
|
1346
|
+
}
|
|
1347
|
+
});
|