@goodandready/dsh-messenger-gateway 0.3.21 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/cordis.patch.yml +1 -1
- package/lib/adapters/discord.js +9 -1
- package/lib/adapters/slack.js +7 -1
- package/lib/adapters/telegram-inbound.js +141 -0
- package/lib/adapters/telegram.js +42 -154
- package/lib/alerts.js +104 -63
- package/lib/api-health.js +38 -0
- package/lib/artifacts.js +118 -118
- package/lib/ask.js +203 -152
- package/lib/client.js +1660 -1312
- package/lib/commands.js +53 -0
- package/lib/config.js +104 -104
- package/lib/content-guard.js +14 -14
- package/lib/documents.js +1 -1
- package/lib/file-manager.js +27 -1
- package/lib/forum-mirror.js +105 -0
- package/lib/gateway-callbacks.js +187 -0
- package/lib/gateway-commands.js +584 -0
- package/lib/gateway-turn.js +285 -0
- package/lib/gateway.js +590 -1737
- package/lib/http.js +12 -1
- package/lib/index.js +48 -8
- package/lib/integrations.js +1 -1
- package/lib/locales/en.js +123 -114
- package/lib/locales/index.js +21 -21
- package/lib/locales/zh.js +123 -114
- package/lib/media.js +1 -9
- package/lib/models.js +6 -3
- package/lib/outbound.js +1 -1
- package/lib/pairing.js +8 -4
- package/lib/personas.js +8 -4
- package/lib/photos.js +1 -1
- package/lib/scheduler.js +14 -4
- package/lib/stream.js +115 -115
- package/lib/text.js +39 -39
- package/lib/topics.js +0 -14
- package/lib/tts.js +0 -10
- package/lib/updater.js +304 -0
- package/lib/voice-prefs.js +8 -4
- package/package.json +69 -65
package/lib/client.js
CHANGED
|
@@ -1,1312 +1,1660 @@
|
|
|
1
|
-
// dsh-messenger-gateway — browser (client) half.
|
|
2
|
-
// Settings card on Plugins → Plugin settings tab (settings.plugin.item).
|
|
3
|
-
// Config managed via reactive ctx.settingsScope snapshot.
|
|
4
|
-
|
|
5
|
-
window.__ModuleLoader__.load({
|
|
6
|
-
id: '@goodandready/dsh-messenger-gateway',
|
|
7
|
-
factory: (require) => {
|
|
8
|
-
var module = { exports: {} }
|
|
9
|
-
var exports = module.exports
|
|
10
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
11
|
-
const React = require('react')
|
|
12
|
-
|
|
13
|
-
const NS = 'dsh-messenger-gateway'
|
|
14
|
-
const ROUTE_PREFIX = '/dsh-messenger-gateway'
|
|
15
|
-
|
|
16
|
-
const en = {
|
|
17
|
-
title: 'Messenger gateway',
|
|
18
|
-
description: 'Telegram bot: text, voice, photos, documents, and interactive asks.',
|
|
19
|
-
'settings.loading': 'Loading Messenger gateway settings…',
|
|
20
|
-
'settings.retry': 'Retry',
|
|
21
|
-
'settings.unavailable': 'Settings unavailable (host namespace is not ready).',
|
|
22
|
-
'header.title': 'Telegram & Multi-Messenger Gateway',
|
|
23
|
-
'header.sub': 'Bridge between DeepSeek Harness and Telegram: sessions, steer, forum topics, inline buttons, and optional voice notes.',
|
|
24
|
-
'badge.online': 'Telegram online (@{username})',
|
|
25
|
-
'badge.running': 'Running',
|
|
26
|
-
'badge.stopped': 'Stopped',
|
|
27
|
-
'badge.token_ok': 'Token ✓',
|
|
28
|
-
'badge.token_missing': 'Token missing',
|
|
29
|
-
'badge.pairing_count': 'Pairing: {count}',
|
|
30
|
-
'badge.transport': 'Transport: {mode}',
|
|
31
|
-
save: 'Save Changes',
|
|
32
|
-
saved: 'Settings saved successfully',
|
|
33
|
-
saving: 'Saving…',
|
|
34
|
-
showAdvanced: '⚙️ Advanced connection & polling settings',
|
|
35
|
-
hideAdvanced: 'Hide advanced settings',
|
|
36
|
-
telegram: 'Telegram Bot Configuration',
|
|
37
|
-
telegramSub: 'Main bot credentials, whitelist, and message delivery settings.',
|
|
38
|
-
agent: 'Agent & Reasoning Execution',
|
|
39
|
-
agentSub: 'Model routing, instruction overrides, and turn timeout control.',
|
|
40
|
-
media: 'Media & File Limits',
|
|
41
|
-
mediaSub: 'Document extraction and image upload limits.',
|
|
42
|
-
tts: 'Voice Replies (dsh-tts)',
|
|
43
|
-
ttsSub: 'Synthesize spoken replies for Telegram voice messages.',
|
|
44
|
-
advanced: 'Connection & Polling Internals',
|
|
45
|
-
botToken: 'Telegram Bot Token',
|
|
46
|
-
botTokenHint: 'Obtained via @BotFather. Leave empty to keep existing configured token.',
|
|
47
|
-
allowedIds: 'Allowed Telegram User IDs',
|
|
48
|
-
allowedIdsHint: 'Comma-separated list of user IDs. Leave empty to allow all users (pairing requests enabled).',
|
|
49
|
-
textFormat: 'Text Format Rendering',
|
|
50
|
-
textFormatHtml: 'Markdown → Telegram HTML (recommended)',
|
|
51
|
-
textFormatPlain: 'Plain text (no formatting)',
|
|
52
|
-
disableTelegram: 'Disable Telegram Bot',
|
|
53
|
-
enableTelegram: 'Enable Telegram Bot',
|
|
54
|
-
provider: 'Provider Override',
|
|
55
|
-
providerHint: 'Leave empty to use DSH default provider',
|
|
56
|
-
model: 'Model Override',
|
|
57
|
-
modelHint: 'Leave empty to use DSH default model',
|
|
58
|
-
photoOnly: 'Photo without Caption Behavior',
|
|
59
|
-
photoPrompt: 'Wait for question (recommended)',
|
|
60
|
-
photoRun: 'Reply immediately',
|
|
61
|
-
instruction: 'Extra Agent Instruction',
|
|
62
|
-
instructionHint: 'Prepended to each inbound messenger message',
|
|
63
|
-
instructionPlaceholder: 'Empty = built-in relay instruction (Russian, concise)',
|
|
64
|
-
maxMessageLength: 'Max Reply Length (characters)',
|
|
65
|
-
turnTimeout: 'Agent Turn Timeout (seconds)',
|
|
66
|
-
maxDocMb: 'Max Document Size (MB)',
|
|
67
|
-
maxImageMb: 'Max Photo Size (MB)',
|
|
68
|
-
maxTextKb: 'Max Extracted Text from Document (KB)',
|
|
69
|
-
ttsEnable: 'Enable Voice Replies (requires dsh-tts)',
|
|
70
|
-
ttsMaxChars: 'Max Spoken Characters per Voice Note',
|
|
71
|
-
pollTimeout: 'Long Poll Timeout (seconds)',
|
|
72
|
-
pollInterval: 'Long Poll Interval (ms)',
|
|
73
|
-
idleTimeout: 'Idle Chat Timeout (seconds)',
|
|
74
|
-
pairing: 'Pairing Requests & Authorization',
|
|
75
|
-
pairingSub: 'Authorize new users when whitelist is restricted.',
|
|
76
|
-
pairingEmpty: 'No pending pairing codes',
|
|
77
|
-
pairingApprove: 'Approve',
|
|
78
|
-
pairingReject: 'Reject',
|
|
79
|
-
pairingRefresh: 'Refresh List',
|
|
80
|
-
groupsEnable: 'Respond in Telegram Groups & Supergroups',
|
|
81
|
-
groupMention: 'Require @mention, reply, or /command in groups',
|
|
82
|
-
reactionsEnable: 'React with 👀 while processing turn',
|
|
83
|
-
progressEnable: 'Progress status message (Thinking → Delete → Final)',
|
|
84
|
-
statusIndicator: 'Bot Status Description Indicator (Online/Offline)',
|
|
85
|
-
statusIndicatorHint: 'Sets bot short description via Telegram API (visible to all users).',
|
|
86
|
-
transport: 'Telegram Transport Mode',
|
|
87
|
-
transportPoll: 'Long poll (getUpdates)',
|
|
88
|
-
transportWebhook: 'Webhook (HTTPS)',
|
|
89
|
-
webhookUrl: 'Public Webhook HTTPS URL',
|
|
90
|
-
webhookUrlHint: 'Public endpoint where Telegram sends updates',
|
|
91
|
-
webhookSecret: 'Webhook Secret Token',
|
|
92
|
-
webhookSecretHint: 'Leave empty to keep current configured secret',
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'diag.
|
|
108
|
-
'diag.
|
|
109
|
-
'diag.
|
|
110
|
-
'diag.
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'stats.
|
|
114
|
-
'stats.
|
|
115
|
-
'stats.
|
|
116
|
-
'stats.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
title: '
|
|
121
|
-
|
|
122
|
-
'
|
|
123
|
-
'
|
|
124
|
-
'
|
|
125
|
-
'
|
|
126
|
-
'
|
|
127
|
-
'
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
.
|
|
258
|
-
.
|
|
259
|
-
.
|
|
260
|
-
.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.
|
|
264
|
-
.
|
|
265
|
-
.
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
.
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
.
|
|
272
|
-
.
|
|
273
|
-
.
|
|
274
|
-
.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
function
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
)
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
'
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
React.createElement(
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
React.createElement(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
React.createElement(
|
|
920
|
-
React.createElement(
|
|
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
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
{ className: 'msgw-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
},
|
|
968
|
-
|
|
969
|
-
)
|
|
970
|
-
),
|
|
971
|
-
React.createElement('
|
|
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
|
-
React.createElement(
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
React.createElement(
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
React.createElement('
|
|
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
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1
|
+
// dsh-messenger-gateway — browser (client) half.
|
|
2
|
+
// Settings card on Plugins → Plugin settings tab (settings.plugin.item).
|
|
3
|
+
// Config managed via reactive ctx.settingsScope snapshot.
|
|
4
|
+
|
|
5
|
+
window.__ModuleLoader__.load({
|
|
6
|
+
id: '@goodandready/dsh-messenger-gateway',
|
|
7
|
+
factory: (require) => {
|
|
8
|
+
var module = { exports: {} }
|
|
9
|
+
var exports = module.exports
|
|
10
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
11
|
+
const React = require('react')
|
|
12
|
+
|
|
13
|
+
const NS = 'dsh-messenger-gateway'
|
|
14
|
+
const ROUTE_PREFIX = '/dsh-messenger-gateway'
|
|
15
|
+
|
|
16
|
+
const en = {
|
|
17
|
+
title: 'Messenger gateway',
|
|
18
|
+
description: 'Telegram bot: text, voice, photos, documents, and interactive asks.',
|
|
19
|
+
'settings.loading': 'Loading Messenger gateway settings…',
|
|
20
|
+
'settings.retry': 'Retry',
|
|
21
|
+
'settings.unavailable': 'Settings unavailable (host namespace is not ready).',
|
|
22
|
+
'header.title': 'Telegram & Multi-Messenger Gateway',
|
|
23
|
+
'header.sub': 'Bridge between DeepSeek Harness and Telegram: sessions, steer, forum topics, inline buttons, and optional voice notes.',
|
|
24
|
+
'badge.online': 'Telegram online (@{username})',
|
|
25
|
+
'badge.running': 'Running',
|
|
26
|
+
'badge.stopped': 'Stopped',
|
|
27
|
+
'badge.token_ok': 'Token ✓',
|
|
28
|
+
'badge.token_missing': 'Token missing',
|
|
29
|
+
'badge.pairing_count': 'Pairing: {count}',
|
|
30
|
+
'badge.transport': 'Transport: {mode}',
|
|
31
|
+
save: 'Save Changes',
|
|
32
|
+
saved: 'Settings saved successfully',
|
|
33
|
+
saving: 'Saving…',
|
|
34
|
+
showAdvanced: '⚙️ Advanced connection & polling settings',
|
|
35
|
+
hideAdvanced: 'Hide advanced settings',
|
|
36
|
+
telegram: 'Telegram Bot Configuration',
|
|
37
|
+
telegramSub: 'Main bot credentials, whitelist, and message delivery settings.',
|
|
38
|
+
agent: 'Agent & Reasoning Execution',
|
|
39
|
+
agentSub: 'Model routing, instruction overrides, and turn timeout control.',
|
|
40
|
+
media: 'Media & File Limits',
|
|
41
|
+
mediaSub: 'Document extraction and image upload limits.',
|
|
42
|
+
tts: 'Voice Replies (dsh-tts)',
|
|
43
|
+
ttsSub: 'Synthesize spoken replies for Telegram voice messages.',
|
|
44
|
+
advanced: 'Connection & Polling Internals',
|
|
45
|
+
botToken: 'Telegram Bot Token',
|
|
46
|
+
botTokenHint: 'Obtained via @BotFather. Leave empty to keep existing configured token.',
|
|
47
|
+
allowedIds: 'Allowed Telegram User IDs',
|
|
48
|
+
allowedIdsHint: 'Comma-separated list of user IDs. Leave empty to allow all users (pairing requests enabled).',
|
|
49
|
+
textFormat: 'Text Format Rendering',
|
|
50
|
+
textFormatHtml: 'Markdown → Telegram HTML (recommended)',
|
|
51
|
+
textFormatPlain: 'Plain text (no formatting)',
|
|
52
|
+
disableTelegram: 'Disable Telegram Bot',
|
|
53
|
+
enableTelegram: 'Enable Telegram Bot',
|
|
54
|
+
provider: 'Provider Override',
|
|
55
|
+
providerHint: 'Leave empty to use DSH default provider',
|
|
56
|
+
model: 'Model Override',
|
|
57
|
+
modelHint: 'Leave empty to use DSH default model',
|
|
58
|
+
photoOnly: 'Photo without Caption Behavior',
|
|
59
|
+
photoPrompt: 'Wait for question (recommended)',
|
|
60
|
+
photoRun: 'Reply immediately',
|
|
61
|
+
instruction: 'Extra Agent Instruction',
|
|
62
|
+
instructionHint: 'Prepended to each inbound messenger message',
|
|
63
|
+
instructionPlaceholder: 'Empty = built-in relay instruction (Russian, concise)',
|
|
64
|
+
maxMessageLength: 'Max Reply Length (characters)',
|
|
65
|
+
turnTimeout: 'Agent Turn Timeout (seconds)',
|
|
66
|
+
maxDocMb: 'Max Document Size (MB)',
|
|
67
|
+
maxImageMb: 'Max Photo Size (MB)',
|
|
68
|
+
maxTextKb: 'Max Extracted Text from Document (KB)',
|
|
69
|
+
ttsEnable: 'Enable Voice Replies (requires dsh-tts)',
|
|
70
|
+
ttsMaxChars: 'Max Spoken Characters per Voice Note',
|
|
71
|
+
pollTimeout: 'Long Poll Timeout (seconds)',
|
|
72
|
+
pollInterval: 'Long Poll Interval (ms)',
|
|
73
|
+
idleTimeout: 'Idle Chat Timeout (seconds)',
|
|
74
|
+
pairing: 'Pairing Requests & Authorization',
|
|
75
|
+
pairingSub: 'Authorize new users when whitelist is restricted.',
|
|
76
|
+
pairingEmpty: 'No pending pairing codes',
|
|
77
|
+
pairingApprove: 'Approve',
|
|
78
|
+
pairingReject: 'Reject',
|
|
79
|
+
pairingRefresh: 'Refresh List',
|
|
80
|
+
groupsEnable: 'Respond in Telegram Groups & Supergroups',
|
|
81
|
+
groupMention: 'Require @mention, reply, or /command in groups',
|
|
82
|
+
reactionsEnable: 'React with 👀 while processing turn',
|
|
83
|
+
progressEnable: 'Progress status message (Thinking → Delete → Final)',
|
|
84
|
+
statusIndicator: 'Bot Status Description Indicator (Online/Offline)',
|
|
85
|
+
statusIndicatorHint: 'Sets bot short description via Telegram API (visible to all users).',
|
|
86
|
+
transport: 'Telegram Transport Mode',
|
|
87
|
+
transportPoll: 'Long poll (getUpdates)',
|
|
88
|
+
transportWebhook: 'Webhook (HTTPS)',
|
|
89
|
+
webhookUrl: 'Public Webhook HTTPS URL',
|
|
90
|
+
webhookUrlHint: 'Public endpoint where Telegram sends updates',
|
|
91
|
+
webhookSecret: 'Webhook Secret Token',
|
|
92
|
+
webhookSecretHint: 'Leave empty to keep current configured secret',
|
|
93
|
+
webhookSecretConfigured: 'Secret set',
|
|
94
|
+
webhookSecretRequired: 'Secret required',
|
|
95
|
+
sessionScope: 'Group Session Scope',
|
|
96
|
+
sessionScopeChat: 'Shared per chat/topic',
|
|
97
|
+
sessionScopeUser: 'Per-user session (recommended)',
|
|
98
|
+
voiceMode: 'Voice Reply Mode',
|
|
99
|
+
voiceModeMirror: 'Mirror inbound voice (reply voice only if input was voice)',
|
|
100
|
+
voiceModeAlways: 'Always synthesize voice note',
|
|
101
|
+
voiceModeOff: 'Off (unless explicitly toggled via /voice)',
|
|
102
|
+
notifyBridge: 'Notify Bridge → Telegram Home',
|
|
103
|
+
notifyBridgeHint: 'Forward task_done/error events from web sessions into designated Telegram home',
|
|
104
|
+
notifyHome: 'Notify Target Home Name',
|
|
105
|
+
quickActions: 'Quick Actions Keyboard (/new, /stop, /voice, /status)',
|
|
106
|
+
artifactPreviews: 'Render diagram and table previews in Telegram',
|
|
107
|
+
'diag.title': '⚡ Diagnostics & Telegram Smoke Test',
|
|
108
|
+
'diag.desc': 'Test connection to Telegram Bot API, measure network latency, and verify bot identity.',
|
|
109
|
+
'diag.smoke_btn': 'Run Telegram Smoke Test (Ping)',
|
|
110
|
+
'diag.smoke_testing': 'Testing Telegram API…',
|
|
111
|
+
'diag.smoke_ok': 'Telegram API OK! Latency: {latency} ms (@{username}, ID: {id})',
|
|
112
|
+
'diag.smoke_err': 'Smoke test failed: {error}',
|
|
113
|
+
'stats.title': '📊 Gateway Telemetry & Session Counters',
|
|
114
|
+
'stats.desc': 'Real-time counters from the running gateway process.',
|
|
115
|
+
'stats.sent': 'Messages Delivered',
|
|
116
|
+
'stats.errors': 'Delivery Errors',
|
|
117
|
+
'stats.uptime': 'Gateway Uptime',
|
|
118
|
+
'stats.active_chats': 'Active Chats in Memory',
|
|
119
|
+
// Updater
|
|
120
|
+
'updater.title': 'Plugin Self-Updater',
|
|
121
|
+
'updater.desc': 'Check npmjs registry for updates and upgrade in-place.',
|
|
122
|
+
'updater.current': 'Current version: {version}',
|
|
123
|
+
'updater.checking': 'Checking for updates…',
|
|
124
|
+
'updater.available': 'Update available: v{version}',
|
|
125
|
+
'updater.upToDate': 'Up to date',
|
|
126
|
+
'updater.btn_check': 'Check for Updates',
|
|
127
|
+
'updater.btn_update': 'Update to v{version}',
|
|
128
|
+
'updater.updating': 'Updating…',
|
|
129
|
+
'updater.success': 'Updated to v{version}! Please restart DSH service if required.',
|
|
130
|
+
'updater.failed': 'Update failed: {error}',
|
|
131
|
+
'updater.check_failed': 'Update check failed: {error}',
|
|
132
|
+
// Discord
|
|
133
|
+
discord: 'Discord Gateway',
|
|
134
|
+
discordSub: 'Discord bot and outbound webhook delivery settings.',
|
|
135
|
+
enableDiscord: 'Enable Discord',
|
|
136
|
+
disableDiscord: 'Disable Discord',
|
|
137
|
+
discordToken: 'Discord Bot Token',
|
|
138
|
+
discordTokenHint: 'From Discord Developer Portal. Leave empty to keep existing.',
|
|
139
|
+
discordWebhook: 'Discord Webhook URL',
|
|
140
|
+
discordWebhookHint: 'Incoming webhook for sending messages/notifications.',
|
|
141
|
+
discordChannels: 'Allowed Discord Channel IDs',
|
|
142
|
+
discordChannelsHint: 'Comma-separated channel IDs. Leave empty to allow all configured.',
|
|
143
|
+
// Slack
|
|
144
|
+
slack: 'Slack Gateway',
|
|
145
|
+
slackSub: 'Slack bot and incoming webhook settings.',
|
|
146
|
+
enableSlack: 'Enable Slack',
|
|
147
|
+
disableSlack: 'Disable Slack',
|
|
148
|
+
slackToken: 'Slack Bot Token (xoxb-…)',
|
|
149
|
+
slackTokenHint: 'Bot User OAuth Token. Leave empty to keep existing.',
|
|
150
|
+
slackWebhook: 'Slack Webhook URL',
|
|
151
|
+
slackWebhookHint: 'Incoming webhook URL for channel notifications.',
|
|
152
|
+
slackDefaultChannel: 'Slack Default Channel',
|
|
153
|
+
slackDefaultChannelHint: 'Default channel or member ID for outbound messages (e.g. #general or C12345).',
|
|
154
|
+
// Cron badge
|
|
155
|
+
'badge.cron_count': 'Cron: {count}',
|
|
156
|
+
'badge.api_degraded': 'Telegram API degraded ({count})',
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const zh = {
|
|
160
|
+
title: 'Messenger gateway',
|
|
161
|
+
description: 'Telegram 机器人:文本、语音、图片、文档和交互式询问。',
|
|
162
|
+
'settings.loading': '正在加载 Messenger gateway 设置…',
|
|
163
|
+
'settings.retry': '重试',
|
|
164
|
+
'settings.unavailable': '设置不可用(主机命名空间未就绪)。',
|
|
165
|
+
'header.title': 'Telegram 与多消息平台网关',
|
|
166
|
+
'header.sub': 'DeepSeek Harness 与 Telegram 之间的桥梁:会话、转向提示、论坛话题、内联按钮和可选语音。',
|
|
167
|
+
'badge.online': 'Telegram 在线 (@{username})',
|
|
168
|
+
'badge.running': '运行中',
|
|
169
|
+
'badge.stopped': '已停止',
|
|
170
|
+
'badge.token_ok': '令牌有效 ✓',
|
|
171
|
+
'badge.token_missing': '缺少令牌',
|
|
172
|
+
'badge.pairing_count': '配对: {count}',
|
|
173
|
+
'badge.transport': '传输方式: {mode}',
|
|
174
|
+
save: '保存更改',
|
|
175
|
+
saved: '设置保存成功',
|
|
176
|
+
saving: '保存中…',
|
|
177
|
+
showAdvanced: '⚙️ 高级连接与轮询设置',
|
|
178
|
+
hideAdvanced: '隐藏高级设置',
|
|
179
|
+
telegram: 'Telegram 机器人设置',
|
|
180
|
+
telegramSub: '主机器人凭证、白名单与消息发送设置。',
|
|
181
|
+
agent: '智能体与推理行为',
|
|
182
|
+
agentSub: '模型路由、附加指令与轮次超时设置。',
|
|
183
|
+
media: '媒体与文件限制',
|
|
184
|
+
mediaSub: '文档提取与图片上传限制。',
|
|
185
|
+
tts: '语音回复 (dsh-tts)',
|
|
186
|
+
ttsSub: '为 Telegram 语音消息合成语音回复。',
|
|
187
|
+
advanced: '连接与轮询内部设置',
|
|
188
|
+
botToken: 'Telegram 机器人令牌',
|
|
189
|
+
botTokenHint: '从 @BotFather 获取。留空保持现有配置。',
|
|
190
|
+
allowedIds: '允许的 Telegram 用户 ID',
|
|
191
|
+
allowedIdsHint: '逗号分隔的用户 ID 列表。留空允许所有用户(启用配对请求)。',
|
|
192
|
+
textFormat: '文本格式化渲染',
|
|
193
|
+
textFormatHtml: 'Markdown → Telegram HTML (推荐)',
|
|
194
|
+
textFormatPlain: '纯文本 (无格式化)',
|
|
195
|
+
disableTelegram: '停用 Telegram 机器人',
|
|
196
|
+
enableTelegram: '启用 Telegram 机器人',
|
|
197
|
+
provider: '提供商覆盖',
|
|
198
|
+
providerHint: '留空使用 DSH 默认提供商',
|
|
199
|
+
model: '模型覆盖',
|
|
200
|
+
modelHint: '留空使用 DSH 默认模型',
|
|
201
|
+
photoOnly: '无附言图片处理方式',
|
|
202
|
+
photoPrompt: '等待问题 (推荐)',
|
|
203
|
+
photoRun: '立即回复',
|
|
204
|
+
instruction: '额外智能体指令',
|
|
205
|
+
instructionHint: '添加到每条来自网关的消息前',
|
|
206
|
+
instructionPlaceholder: '留空 = 内置指令',
|
|
207
|
+
maxMessageLength: '最大回复长度 (字符)',
|
|
208
|
+
turnTimeout: '智能体轮次超时 (秒)',
|
|
209
|
+
maxDocMb: '最大文档大小 (MB)',
|
|
210
|
+
maxImageMb: '最大图片大小 (MB)',
|
|
211
|
+
maxTextKb: '文档提取最大文本量 (KB)',
|
|
212
|
+
ttsEnable: '启用语音回复 (需 dsh-tts)',
|
|
213
|
+
ttsMaxChars: '每条语音消息最大字数',
|
|
214
|
+
pollTimeout: '长轮询超时 (秒)',
|
|
215
|
+
pollInterval: '长轮询间隔 (毫秒)',
|
|
216
|
+
idleTimeout: '空闲会话超时 (秒)',
|
|
217
|
+
pairing: '配对请求与授权',
|
|
218
|
+
pairingSub: '受限白名单下授权新用户。',
|
|
219
|
+
pairingEmpty: '暂无待处理配对码',
|
|
220
|
+
pairingApprove: '批准',
|
|
221
|
+
pairingReject: '拒绝',
|
|
222
|
+
pairingRefresh: '刷新列表',
|
|
223
|
+
groupsEnable: '在群组和超级群组中响应',
|
|
224
|
+
groupMention: '群组中需 @提及、回复或 /命令',
|
|
225
|
+
reactionsEnable: '处理中添加 👀 表情反应',
|
|
226
|
+
progressEnable: '进度状态提示 (思考中 → 删除 → 最终回复)',
|
|
227
|
+
statusIndicator: '机器人状态描述指示器 (Online/Offline)',
|
|
228
|
+
statusIndicatorHint: '通过 Telegram API 设置机器人简介(所有人可见)。',
|
|
229
|
+
transport: 'Telegram 传输模式',
|
|
230
|
+
transportPoll: '长轮询 (getUpdates)',
|
|
231
|
+
transportWebhook: 'Webhook (HTTPS)',
|
|
232
|
+
webhookUrl: '公开 Webhook HTTPS 地址',
|
|
233
|
+
webhookUrlHint: 'Telegram 投递更新的公开端点',
|
|
234
|
+
webhookSecret: 'Webhook 密钥令牌',
|
|
235
|
+
webhookSecretHint: '留空保持当前配置的密钥',
|
|
236
|
+
webhookSecretConfigured: '密钥已设置',
|
|
237
|
+
webhookSecretRequired: '必须设置密钥',
|
|
238
|
+
sessionScope: '群组会话范围',
|
|
239
|
+
sessionScopeChat: '每个聊天/话题共享',
|
|
240
|
+
sessionScopeUser: '每用户独立会话 (推荐)',
|
|
241
|
+
voiceMode: '语音回复模式',
|
|
242
|
+
voiceModeMirror: '镜像输入语音 (仅语音输入时回复语音)',
|
|
243
|
+
voiceModeAlways: '始终合成语音回复',
|
|
244
|
+
voiceModeOff: '关闭 (除非通过 /voice 启用)',
|
|
245
|
+
notifyBridge: '通知网桥 → Telegram Home',
|
|
246
|
+
notifyBridgeHint: '将 Web 会话的 task_done/error 事件转发至指定频道/话题',
|
|
247
|
+
notifyHome: '目标频道名称 (Home)',
|
|
248
|
+
quickActions: '快捷操作键盘 (/new, /stop, /voice, /status)',
|
|
249
|
+
artifactPreviews: '在 Telegram 中预览图表与表格',
|
|
250
|
+
'diag.title': '⚡ 诊断与连通性检查 (Smoke test)',
|
|
251
|
+
'diag.desc': '检查 Telegram Bot API 连接、测量延迟并验证机器人名称。',
|
|
252
|
+
'diag.smoke_btn': '测试 Telegram 连接 (Ping)',
|
|
253
|
+
'diag.smoke_testing': '正在连接 Telegram API…',
|
|
254
|
+
'diag.smoke_ok': 'Telegram API 连接成功!延迟: {latency} 毫秒 (@{username}, ID: {id})',
|
|
255
|
+
'diag.smoke_err': '连通性检查失败: {error}',
|
|
256
|
+
'stats.title': '📊 网关遥测与计数器',
|
|
257
|
+
'stats.desc': '运行中网关进程的实时统计。',
|
|
258
|
+
'stats.sent': '已发送消息',
|
|
259
|
+
'stats.errors': '发送错误数',
|
|
260
|
+
'stats.uptime': '运行时间',
|
|
261
|
+
'stats.active_chats': '内存中活跃聊天数',
|
|
262
|
+
// Updater
|
|
263
|
+
'updater.title': '插件自更新',
|
|
264
|
+
'updater.desc': '从 npmjs 仓库检查更新并直接就地升级。',
|
|
265
|
+
'updater.current': '当前版本: {version}',
|
|
266
|
+
'updater.checking': '正在检查更新…',
|
|
267
|
+
'updater.available': '发现新版本: v{version}',
|
|
268
|
+
'updater.upToDate': '已是最新版本',
|
|
269
|
+
'updater.btn_check': '检查更新',
|
|
270
|
+
'updater.btn_update': '更新至 v{version}',
|
|
271
|
+
'updater.updating': '正在更新…',
|
|
272
|
+
'updater.success': '已更新至 v{version}!如需生效请重启 DSH 服务。',
|
|
273
|
+
'updater.failed': '更新失败: {error}',
|
|
274
|
+
'updater.check_failed': '检查更新失败: {error}',
|
|
275
|
+
// Discord
|
|
276
|
+
discord: 'Discord 网关',
|
|
277
|
+
discordSub: 'Discord 机器人与出站 Webhook 设置。',
|
|
278
|
+
enableDiscord: '启用 Discord',
|
|
279
|
+
disableDiscord: '停用 Discord',
|
|
280
|
+
discordToken: 'Discord 机器人令牌',
|
|
281
|
+
discordTokenHint: '从 Discord 开发者平台获取。留空保持现有配置。',
|
|
282
|
+
discordWebhook: 'Discord Webhook URL',
|
|
283
|
+
discordWebhookHint: '用于发送消息或通知的传入 Webhook。',
|
|
284
|
+
discordChannels: '允许的 Discord 频道 ID',
|
|
285
|
+
discordChannelsHint: '逗号分隔的频道 ID。留空允许所有已配置频道。',
|
|
286
|
+
// Slack
|
|
287
|
+
slack: 'Slack 网关',
|
|
288
|
+
slackSub: 'Slack 机器人与 Webhook 设置。',
|
|
289
|
+
enableSlack: '启用 Slack',
|
|
290
|
+
disableSlack: '停用 Slack',
|
|
291
|
+
slackToken: 'Slack 机器人令牌 (xoxb-…)',
|
|
292
|
+
slackTokenHint: 'Bot 用户 OAuth 令牌。留空保持现有配置。',
|
|
293
|
+
slackWebhook: 'Slack Webhook URL',
|
|
294
|
+
slackWebhookHint: '用于频道通知的传入 Webhook URL。',
|
|
295
|
+
slackDefaultChannel: 'Slack 默认频道',
|
|
296
|
+
slackDefaultChannelHint: '出站消息的默认频道或成员 ID (例如 #general 或 C12345)。',
|
|
297
|
+
// Cron badge
|
|
298
|
+
'badge.cron_count': '定时任务: {count}',
|
|
299
|
+
'badge.api_degraded': 'Telegram API 异常 ({count} 次连续错误)',
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function useActiveLocale(ctx) {
|
|
303
|
+
const loc = ctx?.get?.('locale') || ctx?.locale
|
|
304
|
+
return React.useSyncExternalStore(
|
|
305
|
+
React.useMemo(() => (cb) => (loc ? loc.subscribe(cb) : () => {}), [loc]),
|
|
306
|
+
React.useCallback(() => {
|
|
307
|
+
if (loc) {
|
|
308
|
+
const active = loc.getSnapshot?.()?.active
|
|
309
|
+
if (typeof active === 'string' && active) return active
|
|
310
|
+
}
|
|
311
|
+
return typeof navigator !== 'undefined' ? String(navigator.language || '').slice(0, 2) : 'en'
|
|
312
|
+
}, [loc]),
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function makeT(locale) {
|
|
317
|
+
const DICT = String(locale || '').startsWith('zh') ? zh : en
|
|
318
|
+
return (key, params) => {
|
|
319
|
+
let text = DICT[key] || en[key] || key
|
|
320
|
+
if (params && typeof params === 'object') {
|
|
321
|
+
for (const k of Object.keys(params)) {
|
|
322
|
+
text = text.replace(new RegExp('\\{' + k + '\\}', 'g'), String(params[k]))
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return text
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function ensureCss() {
|
|
330
|
+
if (typeof document === 'undefined') return
|
|
331
|
+
const id = 'dsh-messenger-gateway-full-css'
|
|
332
|
+
if (document.getElementById(id)) return
|
|
333
|
+
const style = document.createElement('style')
|
|
334
|
+
style.id = id
|
|
335
|
+
style.dataset.dshPlugin = NS
|
|
336
|
+
style.textContent = `
|
|
337
|
+
.msgw-page{display:flex;flex-direction:column;gap:16px;color:var(--dsw-alias-label-primary);font-family:inherit}
|
|
338
|
+
.msgw-header{display:flex;flex-direction:column;gap:4px;padding-bottom:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
339
|
+
.msgw-page-title{font-size:18px;font-weight:700;display:flex;align-items:center;gap:10px;flex-wrap:wrap;color:var(--dsw-alias-label-primary)}
|
|
340
|
+
.msgw-page-sub{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.4}
|
|
341
|
+
|
|
342
|
+
.msgw-badge{font-size:11px;font-weight:600;padding:2px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
|
|
343
|
+
.msgw-badge-ok{background:var(--dsw-alias-state-success-bg, var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-state-success-border, var(--dsw-alias-border-l2))}
|
|
344
|
+
.msgw-badge-warn{background:var(--dsw-alias-state-warning-bg, var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-warning-primary);border:1px solid var(--dsw-alias-state-warning-border, var(--dsw-alias-border-l2))}
|
|
345
|
+
.msgw-badge-bad{background:var(--dsw-alias-state-error-bg, var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-border, var(--dsw-alias-border-l2))}
|
|
346
|
+
|
|
347
|
+
.msgw-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:12px}
|
|
348
|
+
.msgw-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}
|
|
349
|
+
.msgw-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4;margin-top:-6px}
|
|
350
|
+
|
|
351
|
+
.msgw-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
352
|
+
.msgw-field{display:flex;flex-direction:column;gap:6px}
|
|
353
|
+
.msgw-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
354
|
+
.msgw-hint{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.3}
|
|
355
|
+
|
|
356
|
+
.msgw-input,.msgw-select,.msgw-textarea{width:100%;box-sizing:border-box;height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font:inherit;font-size:13px;outline:none;transition:border-color .15s}
|
|
357
|
+
.msgw-input:focus,.msgw-select:focus,.msgw-textarea:focus{border-color:var(--dsw-alias-label-primary)}
|
|
358
|
+
.msgw-textarea{height:auto;min-height:72px;padding:8px 12px}
|
|
359
|
+
.msgw-check{display:flex;gap:8px;align-items:center;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-primary);user-select:none}
|
|
360
|
+
|
|
361
|
+
.msgw-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}
|
|
362
|
+
.msgw-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}
|
|
363
|
+
.msgw-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
|
|
364
|
+
.msgw-btn-primary:hover:not(:disabled){opacity:0.88}
|
|
365
|
+
.msgw-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-border, var(--dsw-alias-border-l2))}
|
|
366
|
+
.msgw-btn-danger:hover:not(:disabled){background:var(--dsw-alias-state-error-bg, var(--dsw-alias-bg-hover));border-color:var(--dsw-alias-state-error-primary)}
|
|
367
|
+
.msgw-btn-mini{padding:4px 10px;font-size:12px;border-radius:6px}
|
|
368
|
+
.msgw-btn:disabled{opacity:0.5;cursor:not-allowed}
|
|
369
|
+
|
|
370
|
+
.msgw-alert-ok{padding:10px 14px;border-radius:8px;background:var(--dsw-alias-state-success-bg, var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-success-primary);font-size:13px;border:1px solid var(--dsw-alias-state-success-border, var(--dsw-alias-border-l2))}
|
|
371
|
+
.msgw-alert-err{padding:10px 14px;border-radius:8px;background:var(--dsw-alias-state-error-bg, var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-error-primary);font-size:13px;border:1px solid var(--dsw-alias-state-error-border, var(--dsw-alias-border-l2))}
|
|
372
|
+
.msgw-banner-warning{padding:12px 16px;border-radius:8px;background:var(--dsw-alias-state-warning-bg, var(--dsw-alias-bg-layer-2));border:1px solid var(--dsw-alias-state-warning-border, var(--dsw-alias-state-warning-primary));color:var(--dsw-alias-state-warning-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:500}
|
|
373
|
+
|
|
374
|
+
.msgw-stat-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}
|
|
375
|
+
.msgw-stat-box{padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
376
|
+
.msgw-stat-val{font-size:18px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
377
|
+
.msgw-stat-lbl{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
378
|
+
|
|
379
|
+
.msgw-table{width:100%;border-collapse:collapse;margin-top:4px}
|
|
380
|
+
.msgw-table th{text-align:left;font-size:12px;color:var(--dsw-alias-label-secondary);padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l2);font-weight:600}
|
|
381
|
+
.msgw-table td{padding:8px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px;color:var(--dsw-alias-label-primary)}
|
|
382
|
+
.msgw-table tr:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
383
|
+
.msgw-preview{padding:12px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);font-family:monospace;font-size:12px;white-space:pre-wrap;word-break:break-all;border:1px solid var(--dsw-alias-border-l2)}
|
|
384
|
+
`
|
|
385
|
+
document.head.appendChild(style)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function createErrorBoundary() {
|
|
389
|
+
if (!React || typeof React.Component !== 'function') {
|
|
390
|
+
return function NoopBoundary(props) { return props?.children || null }
|
|
391
|
+
}
|
|
392
|
+
return class ErrorBoundary extends React.Component {
|
|
393
|
+
constructor(props) {
|
|
394
|
+
super(props)
|
|
395
|
+
this.state = { hasError: false, error: null }
|
|
396
|
+
}
|
|
397
|
+
static getDerivedStateFromError(error) {
|
|
398
|
+
return { hasError: true, error }
|
|
399
|
+
}
|
|
400
|
+
componentDidCatch(error, errorInfo) {
|
|
401
|
+
console.error('[dsh-messenger-gateway] React UI Error:', error, errorInfo)
|
|
402
|
+
}
|
|
403
|
+
render() {
|
|
404
|
+
if (this.state.hasError) {
|
|
405
|
+
return React.createElement(
|
|
406
|
+
'div',
|
|
407
|
+
{ className: 'msgw-alert-err', style: { margin: '12px 0', padding: '14px', borderRadius: '8px' } },
|
|
408
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ Messenger Gateway UI Error:'),
|
|
409
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
410
|
+
React.createElement(
|
|
411
|
+
'button',
|
|
412
|
+
{
|
|
413
|
+
type: 'button',
|
|
414
|
+
className: 'msgw-btn msgw-btn-mini',
|
|
415
|
+
style: { marginTop: '10px' },
|
|
416
|
+
onClick: () => this.setState({ hasError: false, error: null }),
|
|
417
|
+
},
|
|
418
|
+
'Retry'
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
return this.props?.children || null
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const ErrorBoundary = createErrorBoundary()
|
|
427
|
+
|
|
428
|
+
function FallbackChevron() {
|
|
429
|
+
return React.createElement(
|
|
430
|
+
'svg',
|
|
431
|
+
{ width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
432
|
+
React.createElement('polyline', { points: '6 9 12 15 18 9' })
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
const Chevron = FallbackChevron
|
|
436
|
+
|
|
437
|
+
function idsToText(ids) { return (Array.isArray(ids) ? ids : []).map(String).filter(Boolean).join(', ') }
|
|
438
|
+
function textToIds(text) {
|
|
439
|
+
return String(text || '').split(/[\s,]+/).map((s) => s.trim()).filter(Boolean).map(Number).filter((n) => Number.isFinite(n))
|
|
440
|
+
}
|
|
441
|
+
function bytesToMb(n) { return Math.round(Number(n || 0) / (1024 * 1024)) }
|
|
442
|
+
function mbToBytes(n) { return Math.max(1, Math.round(Number(n || 1))) * 1024 * 1024 }
|
|
443
|
+
|
|
444
|
+
function Field({ label, hint, children }) {
|
|
445
|
+
return React.createElement('div', { className: 'msgw-field' },
|
|
446
|
+
React.createElement('label', { className: 'msgw-label' }, label),
|
|
447
|
+
hint ? React.createElement('div', { className: 'msgw-hint' }, hint) : null,
|
|
448
|
+
children,
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const SETTINGS_KEYS = ['enabled', 'telegram', 'discord', 'slack', 'agent', 'media', 'tts']
|
|
453
|
+
|
|
454
|
+
function draftFromStored(s) {
|
|
455
|
+
const src = (s && typeof s === 'object') ? s : {}
|
|
456
|
+
const t = src.telegram || {}
|
|
457
|
+
const d = src.discord || {}
|
|
458
|
+
const sl = src.slack || {}
|
|
459
|
+
const a = src.agent || {}
|
|
460
|
+
const m = src.media || {}
|
|
461
|
+
const tts = src.tts || {}
|
|
462
|
+
return {
|
|
463
|
+
enabled: src.enabled !== false,
|
|
464
|
+
telegram: {
|
|
465
|
+
enabled: t.enabled === true,
|
|
466
|
+
allowedUserIds: Array.isArray(t.allowedUserIds) ? t.allowedUserIds : [],
|
|
467
|
+
pollTimeoutSeconds: Number(t.pollTimeoutSeconds) || 50,
|
|
468
|
+
pollIntervalMs: Number(t.pollIntervalMs) || 500,
|
|
469
|
+
commands: Array.isArray(t.commands) ? t.commands : [],
|
|
470
|
+
textFormat: t.textFormat === 'plain' ? 'plain' : 'html',
|
|
471
|
+
homeChatId: t.homeChatId != null ? t.homeChatId : '',
|
|
472
|
+
homeThreadId: Number(t.homeThreadId) || 0,
|
|
473
|
+
homes: Array.isArray(t.homes) ? t.homes : [],
|
|
474
|
+
pairingEnabled: t.pairingEnabled !== false,
|
|
475
|
+
streaming: t.streaming === true,
|
|
476
|
+
streamEditIntervalMs: Number(t.streamEditIntervalMs) || 1200,
|
|
477
|
+
progressEnabled: t.progressEnabled !== false,
|
|
478
|
+
approvalsEnabled: t.approvalsEnabled !== false,
|
|
479
|
+
groupsEnabled: t.groupsEnabled !== false,
|
|
480
|
+
groupRequireMention: t.groupRequireMention !== false,
|
|
481
|
+
reactionsEnabled: t.reactionsEnabled !== false,
|
|
482
|
+
statusIndicator: t.statusIndicator === true,
|
|
483
|
+
statusOnline: t.statusOnline || 'Online',
|
|
484
|
+
statusOffline: t.statusOffline || 'Offline',
|
|
485
|
+
transport: t.transport === 'webhook' ? 'webhook' : 'poll',
|
|
486
|
+
webhookUrl: t.webhookUrl || '',
|
|
487
|
+
webhookPath: t.webhookPath || '/dsh-messenger-gateway/telegram/webhook',
|
|
488
|
+
voiceMode: t.voiceMode || 'mirror',
|
|
489
|
+
quickActions: t.quickActions === true,
|
|
490
|
+
artifactPreviews: t.artifactPreviews !== false,
|
|
491
|
+
notifyBridge: t.notifyBridge || { enabled: false, events: ['task_done', 'error'], home: 'default', excludeSessionPrefixes: ['msgw-'] },
|
|
492
|
+
alerts: t.alerts || { enabled: false, chatId: '', threadId: 0, home: '', events: ['error', 'pairing'] },
|
|
493
|
+
botToken: t.botToken || '',
|
|
494
|
+
webhookSecret: t.webhookSecret || '',
|
|
495
|
+
},
|
|
496
|
+
discord: {
|
|
497
|
+
enabled: d.enabled === true,
|
|
498
|
+
botToken: d.botToken || '',
|
|
499
|
+
webhookUrl: d.webhookUrl || '',
|
|
500
|
+
allowedChannelIds: Array.isArray(d.allowedChannelIds) ? d.allowedChannelIds : [],
|
|
501
|
+
},
|
|
502
|
+
slack: {
|
|
503
|
+
enabled: sl.enabled === true,
|
|
504
|
+
botToken: sl.botToken || '',
|
|
505
|
+
webhookUrl: sl.webhookUrl || '',
|
|
506
|
+
defaultChannel: sl.defaultChannel || '',
|
|
507
|
+
},
|
|
508
|
+
agent: {
|
|
509
|
+
provider: a.provider || '',
|
|
510
|
+
model: a.model || '',
|
|
511
|
+
instructionPrefix: a.instructionPrefix || '',
|
|
512
|
+
maxMessageLength: Number(a.maxMessageLength) || 4000,
|
|
513
|
+
turnTimeoutMs: Number(a.turnTimeoutMs) || 600000,
|
|
514
|
+
idleTimeoutMs: Number(a.idleTimeoutMs) || 3600000,
|
|
515
|
+
photoOnlyMode: a.photoOnlyMode || 'prompt',
|
|
516
|
+
sessionScope: a.sessionScope || 'user',
|
|
517
|
+
},
|
|
518
|
+
media: {
|
|
519
|
+
maxDocBytes: Number(m.maxDocBytes) || 20971520,
|
|
520
|
+
maxImageBytes: Number(m.maxImageBytes) || 20971520,
|
|
521
|
+
maxTextInjectBytes: Number(m.maxTextInjectBytes) || 102400,
|
|
522
|
+
},
|
|
523
|
+
tts: {
|
|
524
|
+
enabled: tts.enabled === true,
|
|
525
|
+
maxChars: Number(tts.maxChars) || 4000,
|
|
526
|
+
voiceSummary: tts.voiceSummary === true,
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function SettingsPage(props) {
|
|
532
|
+
const ctx = props?.ctx
|
|
533
|
+
const t = props?.t || makeT(props?.locale)
|
|
534
|
+
|
|
535
|
+
const [token, setToken] = React.useState('')
|
|
536
|
+
const [webhookSecret, setWebhookSecret] = React.useState('')
|
|
537
|
+
const [allowText, setAllowText] = React.useState('')
|
|
538
|
+
const [discordToken, setDiscordToken] = React.useState('')
|
|
539
|
+
const [slackToken, setSlackToken] = React.useState('')
|
|
540
|
+
const [discordChannelsText, setDiscordChannelsText] = React.useState('')
|
|
541
|
+
const [updateState, setUpdateState] = React.useState({
|
|
542
|
+
checking: false,
|
|
543
|
+
updating: false,
|
|
544
|
+
currentVersion: '',
|
|
545
|
+
latestVersion: '',
|
|
546
|
+
updateAvailable: false,
|
|
547
|
+
error: '',
|
|
548
|
+
notice: '',
|
|
549
|
+
})
|
|
550
|
+
const [pending, setPending] = React.useState([])
|
|
551
|
+
const [status, setStatus] = React.useState(null)
|
|
552
|
+
const [smokeResult, setSmokeResult] = React.useState(null)
|
|
553
|
+
const [err, setErr] = React.useState('')
|
|
554
|
+
const [msg, setMsg] = React.useState('')
|
|
555
|
+
const [busy, setBusy] = React.useState('')
|
|
556
|
+
const [showAdvanced, setShowAdvanced] = React.useState(false)
|
|
557
|
+
|
|
558
|
+
React.useEffect(() => {
|
|
559
|
+
ensureCss()
|
|
560
|
+
}, [])
|
|
561
|
+
|
|
562
|
+
const scope = React.useMemo(() => {
|
|
563
|
+
const settingsScope = ctx?.get?.('settingsScope') || ctx?.settingsScope
|
|
564
|
+
return settingsScope ? settingsScope.bind({ namespace: NS }) : undefined
|
|
565
|
+
}, [ctx])
|
|
566
|
+
|
|
567
|
+
const snapshot = React.useSyncExternalStore(
|
|
568
|
+
React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
|
|
569
|
+
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'loading' }), [scope]),
|
|
570
|
+
React.useCallback(() => ({ status: 'loading' }), []),
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
const snapStatus = (snapshot && snapshot.status) || 'loading'
|
|
574
|
+
const stored = (snapshot && snapshot.value) || {}
|
|
575
|
+
const [cfg, setCfg] = React.useState(null)
|
|
576
|
+
|
|
577
|
+
React.useEffect(() => {
|
|
578
|
+
if (snapStatus === 'ready' && cfg === null) {
|
|
579
|
+
setCfg(draftFromStored(stored))
|
|
580
|
+
setAllowText(idsToText(stored?.telegram?.allowedUserIds))
|
|
581
|
+
setDiscordChannelsText(idsToText(stored?.discord?.allowedChannelIds))
|
|
582
|
+
}
|
|
583
|
+
}, [snapStatus, stored, cfg])
|
|
584
|
+
|
|
585
|
+
const loadStatus = React.useCallback(async () => {
|
|
586
|
+
try {
|
|
587
|
+
const res = await fetch(`${ROUTE_PREFIX}/status`, { cache: 'no-store' })
|
|
588
|
+
const data = await res.json().catch(() => ({}))
|
|
589
|
+
if (res.ok && data.ok) {
|
|
590
|
+
setStatus(data)
|
|
591
|
+
}
|
|
592
|
+
} catch (err) {
|
|
593
|
+
console.debug?.('[dsh-messenger-gateway] client status poll error:', err?.message || err)
|
|
594
|
+
}
|
|
595
|
+
}, [])
|
|
596
|
+
|
|
597
|
+
const loadPairing = React.useCallback(async () => {
|
|
598
|
+
try {
|
|
599
|
+
const res = await fetch(`${ROUTE_PREFIX}/pairing`, { credentials: 'same-origin', cache: 'no-store' })
|
|
600
|
+
const data = await res.json().catch(() => ({}))
|
|
601
|
+
if (res.ok && data.ok) setPending(Array.isArray(data.pending) ? data.pending : [])
|
|
602
|
+
} catch (err) {
|
|
603
|
+
console.debug?.('[dsh-messenger-gateway] client pairing poll error:', err?.message || err)
|
|
604
|
+
}
|
|
605
|
+
}, [])
|
|
606
|
+
|
|
607
|
+
React.useEffect(() => {
|
|
608
|
+
loadStatus()
|
|
609
|
+
loadPairing()
|
|
610
|
+
const timer = setInterval(() => {
|
|
611
|
+
loadStatus()
|
|
612
|
+
loadPairing()
|
|
613
|
+
}, 10000)
|
|
614
|
+
return () => clearInterval(timer)
|
|
615
|
+
}, [loadStatus, loadPairing])
|
|
616
|
+
|
|
617
|
+
const loadUpdateStatus = React.useCallback(async () => {
|
|
618
|
+
setUpdateState((s) => ({ ...s, checking: true, error: '' }))
|
|
619
|
+
try {
|
|
620
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`, { cache: 'no-store' })
|
|
621
|
+
const data = await res.json().catch(() => ({}))
|
|
622
|
+
if (res.ok && data.ok) {
|
|
623
|
+
setUpdateState((s) => ({
|
|
624
|
+
...s,
|
|
625
|
+
checking: false,
|
|
626
|
+
currentVersion: data.currentVersion || '',
|
|
627
|
+
latestVersion: data.latestVersion || '',
|
|
628
|
+
updateAvailable: !!data.updateAvailable,
|
|
629
|
+
}))
|
|
630
|
+
} else {
|
|
631
|
+
setUpdateState((s) => ({ ...s, checking: false, error: data.error || `HTTP ${res.status}` }))
|
|
632
|
+
}
|
|
633
|
+
} catch (e) {
|
|
634
|
+
setUpdateState((s) => ({ ...s, checking: false, error: String(e.message || e) }))
|
|
635
|
+
}
|
|
636
|
+
}, [])
|
|
637
|
+
|
|
638
|
+
const handleTriggerUpdate = async () => {
|
|
639
|
+
if (updateState.updating) return
|
|
640
|
+
setUpdateState((s) => ({ ...s, updating: true, error: '', notice: '' }))
|
|
641
|
+
try {
|
|
642
|
+
const res = await fetch(`${ROUTE_PREFIX}/update`, {
|
|
643
|
+
method: 'POST',
|
|
644
|
+
credentials: 'same-origin',
|
|
645
|
+
headers: {
|
|
646
|
+
'Content-Type': 'application/json',
|
|
647
|
+
'x-dsh-plugin-update': '1',
|
|
648
|
+
},
|
|
649
|
+
})
|
|
650
|
+
const data = await res.json().catch(() => ({}))
|
|
651
|
+
if (!res.ok || !data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
652
|
+
setUpdateState((s) => ({
|
|
653
|
+
...s,
|
|
654
|
+
updating: false,
|
|
655
|
+
updateAvailable: false,
|
|
656
|
+
currentVersion: data.updatedVersion || s.latestVersion,
|
|
657
|
+
notice: t('updater.success', { version: data.updatedVersion || s.latestVersion }),
|
|
658
|
+
}))
|
|
659
|
+
} catch (e) {
|
|
660
|
+
setUpdateState((s) => ({
|
|
661
|
+
...s,
|
|
662
|
+
updating: false,
|
|
663
|
+
error: t('updater.failed', { error: String(e.message || e) }),
|
|
664
|
+
}))
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
React.useEffect(() => {
|
|
669
|
+
loadUpdateStatus()
|
|
670
|
+
}, [loadUpdateStatus])
|
|
671
|
+
|
|
672
|
+
const handleSmoke = async () => {
|
|
673
|
+
setBusy('smoke')
|
|
674
|
+
setErr('')
|
|
675
|
+
setMsg('')
|
|
676
|
+
setSmokeResult(null)
|
|
677
|
+
try {
|
|
678
|
+
const res = await fetch(`${ROUTE_PREFIX}/smoke`, {
|
|
679
|
+
method: 'POST',
|
|
680
|
+
credentials: 'same-origin',
|
|
681
|
+
headers: { 'Content-Type': 'application/json' },
|
|
682
|
+
body: JSON.stringify({ timeoutMs: 8000 }),
|
|
683
|
+
})
|
|
684
|
+
const data = await res.json().catch(() => ({}))
|
|
685
|
+
if (!data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
686
|
+
setSmokeResult(data)
|
|
687
|
+
setMsg(t('diag.smoke_ok', { latency: data.latencyMs, username: data.botUsername, id: data.botId }))
|
|
688
|
+
await loadStatus()
|
|
689
|
+
} catch (e) {
|
|
690
|
+
setErr(t('diag.smoke_err', { error: String(e.message || e) }))
|
|
691
|
+
} finally {
|
|
692
|
+
setBusy('')
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const mergePatch = (base, patch) => ({
|
|
697
|
+
...base,
|
|
698
|
+
...patch,
|
|
699
|
+
telegram: { ...base.telegram, ...(patch.telegram || {}) },
|
|
700
|
+
discord: { ...base.discord, ...(patch.discord || {}) },
|
|
701
|
+
slack: { ...base.slack, ...(patch.slack || {}) },
|
|
702
|
+
tts: { ...base.tts, ...(patch.tts || {}) },
|
|
703
|
+
agent: { ...base.agent, ...(patch.agent || {}) },
|
|
704
|
+
media: { ...base.media, ...(patch.media || {}) },
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
const save = async (patch = {}) => {
|
|
708
|
+
if (!scope || !cfg) return
|
|
709
|
+
setBusy('save')
|
|
710
|
+
setErr('')
|
|
711
|
+
setMsg('')
|
|
712
|
+
try {
|
|
713
|
+
let next = mergePatch(cfg, patch)
|
|
714
|
+
if (token.trim()) next.telegram = { ...next.telegram, botToken: token.trim() }
|
|
715
|
+
if (webhookSecret.trim()) next.telegram = { ...next.telegram, webhookSecret: webhookSecret.trim() }
|
|
716
|
+
next.telegram.allowedUserIds = textToIds(allowText)
|
|
717
|
+
if (discordToken.trim()) next.discord = { ...next.discord, botToken: discordToken.trim() }
|
|
718
|
+
if (slackToken.trim()) next.slack = { ...next.slack, botToken: slackToken.trim() }
|
|
719
|
+
next.discord.allowedChannelIds = textToIds(discordChannelsText)
|
|
720
|
+
|
|
721
|
+
const broken = []
|
|
722
|
+
for (const key of SETTINGS_KEYS) {
|
|
723
|
+
if (next[key] !== undefined) {
|
|
724
|
+
try {
|
|
725
|
+
await scope.set(key, next[key])
|
|
726
|
+
} catch (e) {
|
|
727
|
+
broken.push(key + ': ' + (e && e.message || String(e)))
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (broken.length) {
|
|
732
|
+
setErr('Save failed — ' + broken.join('; '))
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
setCfg(next)
|
|
736
|
+
setToken('')
|
|
737
|
+
setWebhookSecret('')
|
|
738
|
+
setDiscordToken('')
|
|
739
|
+
setSlackToken('')
|
|
740
|
+
setMsg(t('saved'))
|
|
741
|
+
setTimeout(() => setMsg(''), 3000)
|
|
742
|
+
await loadPairing()
|
|
743
|
+
await loadStatus()
|
|
744
|
+
} catch (e) {
|
|
745
|
+
setErr(String(e.message || e))
|
|
746
|
+
} finally {
|
|
747
|
+
setBusy('')
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (snapStatus === 'loading') {
|
|
752
|
+
return React.createElement('div', { className: 'msgw-page' }, t('settings.loading'))
|
|
753
|
+
}
|
|
754
|
+
if (snapStatus !== 'ready') {
|
|
755
|
+
return React.createElement('div', { className: 'msgw-page' },
|
|
756
|
+
React.createElement('div', { className: 'msgw-banner-warning' }, t('settings.unavailable'))
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
if (!cfg) {
|
|
760
|
+
return React.createElement('div', { className: 'msgw-page' }, t('settings.loading'))
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const isRunning = Boolean(status?.running)
|
|
764
|
+
const botUsername = status?.botUsername || (status?.config?.telegram?.botUsername) || ''
|
|
765
|
+
const hasToken = Boolean(status?.config?.telegram?.botTokenConfigured || cfg.telegram?.botTokenConfigured || token.trim())
|
|
766
|
+
const transportMode = cfg.telegram?.transport || 'poll'
|
|
767
|
+
const pendingCount = pending.length
|
|
768
|
+
|
|
769
|
+
return React.createElement(
|
|
770
|
+
'div',
|
|
771
|
+
{ className: 'msgw-page' },
|
|
772
|
+
|
|
773
|
+
// Header with badges
|
|
774
|
+
React.createElement(
|
|
775
|
+
'div',
|
|
776
|
+
{ className: 'msgw-header' },
|
|
777
|
+
React.createElement(
|
|
778
|
+
'div',
|
|
779
|
+
{ className: 'msgw-page-title' },
|
|
780
|
+
`💬 ${t('header.title')}`,
|
|
781
|
+
React.createElement(
|
|
782
|
+
'span',
|
|
783
|
+
{ className: `msgw-badge ${isRunning ? 'msgw-badge-ok' : 'msgw-badge-bad'}` },
|
|
784
|
+
isRunning
|
|
785
|
+
? (botUsername ? t('badge.online', { username: botUsername }) : t('badge.running'))
|
|
786
|
+
: t('badge.stopped')
|
|
787
|
+
),
|
|
788
|
+
React.createElement(
|
|
789
|
+
'span',
|
|
790
|
+
{ className: `msgw-badge ${hasToken ? 'msgw-badge-ok' : 'msgw-badge-warn'}` },
|
|
791
|
+
hasToken ? t('badge.token_ok') : t('badge.token_missing')
|
|
792
|
+
),
|
|
793
|
+
React.createElement(
|
|
794
|
+
'span',
|
|
795
|
+
{ className: 'msgw-badge msgw-badge-ok' },
|
|
796
|
+
t('badge.transport', { mode: transportMode })
|
|
797
|
+
),
|
|
798
|
+
pendingCount > 0
|
|
799
|
+
? React.createElement(
|
|
800
|
+
'span',
|
|
801
|
+
{ className: 'msgw-badge msgw-badge-warn' },
|
|
802
|
+
t('badge.pairing_count', { count: pendingCount })
|
|
803
|
+
)
|
|
804
|
+
: null,
|
|
805
|
+
(status?.scheduledCount > 0)
|
|
806
|
+
? React.createElement(
|
|
807
|
+
'span',
|
|
808
|
+
{ className: 'msgw-badge msgw-badge-ok' },
|
|
809
|
+
t('badge.cron_count', { count: status.scheduledCount })
|
|
810
|
+
)
|
|
811
|
+
: null,
|
|
812
|
+
status?.apiHealth?.degraded
|
|
813
|
+
? React.createElement(
|
|
814
|
+
'span',
|
|
815
|
+
{ className: 'msgw-badge msgw-badge-bad' },
|
|
816
|
+
t('badge.api_degraded', { count: status.apiHealth.consecutiveFailures })
|
|
817
|
+
)
|
|
818
|
+
: null
|
|
819
|
+
),
|
|
820
|
+
React.createElement('div', { className: 'msgw-page-sub' }, t('header.sub'))
|
|
821
|
+
),
|
|
822
|
+
|
|
823
|
+
// Global Alert banners
|
|
824
|
+
err ? React.createElement('div', { className: 'msgw-alert-err' }, err) : null,
|
|
825
|
+
msg ? React.createElement('div', { className: 'msgw-alert-ok' }, msg) : null,
|
|
826
|
+
|
|
827
|
+
// Self-Updater Card
|
|
828
|
+
React.createElement(
|
|
829
|
+
'div',
|
|
830
|
+
{ className: 'msgw-section-card' },
|
|
831
|
+
React.createElement(
|
|
832
|
+
'div',
|
|
833
|
+
{ className: 'msgw-section-title' },
|
|
834
|
+
React.createElement('span', null, `🚀 ${t('updater.title')}`),
|
|
835
|
+
React.createElement(
|
|
836
|
+
'div',
|
|
837
|
+
{ className: 'msgw-row' },
|
|
838
|
+
React.createElement(
|
|
839
|
+
'button',
|
|
840
|
+
{
|
|
841
|
+
type: 'button',
|
|
842
|
+
className: 'msgw-btn msgw-btn-mini',
|
|
843
|
+
disabled: updateState.checking || updateState.updating,
|
|
844
|
+
onClick: loadUpdateStatus,
|
|
845
|
+
},
|
|
846
|
+
updateState.checking ? t('updater.checking') : t('updater.btn_check')
|
|
847
|
+
),
|
|
848
|
+
updateState.updateAvailable
|
|
849
|
+
? React.createElement(
|
|
850
|
+
'button',
|
|
851
|
+
{
|
|
852
|
+
type: 'button',
|
|
853
|
+
className: 'msgw-btn msgw-btn-mini msgw-btn-primary',
|
|
854
|
+
disabled: updateState.updating,
|
|
855
|
+
onClick: handleTriggerUpdate,
|
|
856
|
+
},
|
|
857
|
+
updateState.updating ? t('updater.updating') : t('updater.btn_update', { version: updateState.latestVersion })
|
|
858
|
+
)
|
|
859
|
+
: null
|
|
860
|
+
)
|
|
861
|
+
),
|
|
862
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('updater.desc')),
|
|
863
|
+
React.createElement(
|
|
864
|
+
'div',
|
|
865
|
+
{ className: 'msgw-row', style: { fontSize: '13px' } },
|
|
866
|
+
React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } },
|
|
867
|
+
t('updater.current', { version: updateState.currentVersion || status?.version || '...' })
|
|
868
|
+
),
|
|
869
|
+
updateState.checking
|
|
870
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-warn' }, t('updater.checking'))
|
|
871
|
+
: updateState.updateAvailable
|
|
872
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-warn' }, t('updater.available', { version: updateState.latestVersion }))
|
|
873
|
+
: updateState.currentVersion && !updateState.error
|
|
874
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-ok' }, `✓ ${t('updater.upToDate')}`)
|
|
875
|
+
: null
|
|
876
|
+
),
|
|
877
|
+
updateState.notice ? React.createElement('div', { className: 'msgw-alert-ok' }, updateState.notice) : null,
|
|
878
|
+
updateState.error ? React.createElement('div', { className: 'msgw-alert-err' }, updateState.error) : null
|
|
879
|
+
),
|
|
880
|
+
|
|
881
|
+
// Card 1: Telegram Connection & Bot
|
|
882
|
+
React.createElement(
|
|
883
|
+
'div',
|
|
884
|
+
{ className: 'msgw-section-card' },
|
|
885
|
+
React.createElement(
|
|
886
|
+
'div',
|
|
887
|
+
{ className: 'msgw-section-title' },
|
|
888
|
+
React.createElement('span', null, `🤖 ${t('telegram')}`),
|
|
889
|
+
React.createElement(
|
|
890
|
+
'button',
|
|
891
|
+
{
|
|
892
|
+
type: 'button',
|
|
893
|
+
className: `msgw-btn msgw-btn-mini ${cfg.telegram.enabled ? 'msgw-btn-danger' : 'msgw-btn-primary'}`,
|
|
894
|
+
disabled: !!busy,
|
|
895
|
+
onClick: () => save({ telegram: { enabled: !cfg.telegram.enabled } }),
|
|
896
|
+
},
|
|
897
|
+
cfg.telegram.enabled ? t('disableTelegram') : t('enableTelegram')
|
|
898
|
+
)
|
|
899
|
+
),
|
|
900
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('telegramSub')),
|
|
901
|
+
React.createElement(Field, { label: t('botToken'), hint: t('botTokenHint') },
|
|
902
|
+
React.createElement('input', {
|
|
903
|
+
type: 'password',
|
|
904
|
+
className: 'msgw-input',
|
|
905
|
+
value: token,
|
|
906
|
+
placeholder: hasToken ? '••••••••••••••••••••' : '123456789:ABCdefGHI...',
|
|
907
|
+
onChange: (e) => setToken(e.target.value),
|
|
908
|
+
})
|
|
909
|
+
),
|
|
910
|
+
React.createElement(Field, { label: t('allowedIds'), hint: t('allowedIdsHint') },
|
|
911
|
+
React.createElement('textarea', {
|
|
912
|
+
className: 'msgw-textarea',
|
|
913
|
+
value: allowText,
|
|
914
|
+
onChange: (e) => setAllowText(e.target.value),
|
|
915
|
+
rows: 2,
|
|
916
|
+
placeholder: '108191529, 987654321',
|
|
917
|
+
})
|
|
918
|
+
),
|
|
919
|
+
React.createElement(Field, { label: t('textFormat') },
|
|
920
|
+
React.createElement(
|
|
921
|
+
'select',
|
|
922
|
+
{
|
|
923
|
+
className: 'msgw-select',
|
|
924
|
+
value: cfg.telegram?.textFormat || 'html',
|
|
925
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, textFormat: e.target.value } }),
|
|
926
|
+
},
|
|
927
|
+
React.createElement('option', { value: 'html' }, t('textFormatHtml')),
|
|
928
|
+
React.createElement('option', { value: 'plain' }, t('textFormatPlain'))
|
|
929
|
+
)
|
|
930
|
+
),
|
|
931
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
932
|
+
React.createElement('input', {
|
|
933
|
+
type: 'checkbox',
|
|
934
|
+
checked: cfg.telegram?.groupsEnabled !== false,
|
|
935
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, groupsEnabled: e.target.checked } }),
|
|
936
|
+
}),
|
|
937
|
+
t('groupsEnable')
|
|
938
|
+
),
|
|
939
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
940
|
+
React.createElement('input', {
|
|
941
|
+
type: 'checkbox',
|
|
942
|
+
checked: cfg.telegram?.groupRequireMention !== false,
|
|
943
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, groupRequireMention: e.target.checked } }),
|
|
944
|
+
}),
|
|
945
|
+
t('groupMention')
|
|
946
|
+
),
|
|
947
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
948
|
+
React.createElement('input', {
|
|
949
|
+
type: 'checkbox',
|
|
950
|
+
checked: cfg.telegram?.reactionsEnabled !== false,
|
|
951
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, reactionsEnabled: e.target.checked } }),
|
|
952
|
+
}),
|
|
953
|
+
t('reactionsEnable')
|
|
954
|
+
),
|
|
955
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
956
|
+
React.createElement('input', {
|
|
957
|
+
type: 'checkbox',
|
|
958
|
+
checked: cfg.telegram?.progressEnabled !== false,
|
|
959
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, progressEnabled: e.target.checked } }),
|
|
960
|
+
}),
|
|
961
|
+
t('progressEnable')
|
|
962
|
+
),
|
|
963
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
964
|
+
React.createElement('input', {
|
|
965
|
+
type: 'checkbox',
|
|
966
|
+
checked: cfg.telegram?.quickActions !== false,
|
|
967
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, quickActions: e.target.checked } }),
|
|
968
|
+
}),
|
|
969
|
+
t('quickActions')
|
|
970
|
+
),
|
|
971
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
972
|
+
React.createElement('input', {
|
|
973
|
+
type: 'checkbox',
|
|
974
|
+
checked: cfg.telegram?.artifactPreviews !== false,
|
|
975
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, artifactPreviews: e.target.checked } }),
|
|
976
|
+
}),
|
|
977
|
+
t('artifactPreviews')
|
|
978
|
+
)
|
|
979
|
+
),
|
|
980
|
+
|
|
981
|
+
// Card 2: Diagnostics & Telegram Smoke Test
|
|
982
|
+
React.createElement(
|
|
983
|
+
'div',
|
|
984
|
+
{ className: 'msgw-section-card' },
|
|
985
|
+
React.createElement('div', { className: 'msgw-section-title' }, t('diag.title')),
|
|
986
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('diag.desc')),
|
|
987
|
+
React.createElement(
|
|
988
|
+
'div',
|
|
989
|
+
{ className: 'msgw-row' },
|
|
990
|
+
React.createElement(
|
|
991
|
+
'button',
|
|
992
|
+
{
|
|
993
|
+
type: 'button',
|
|
994
|
+
className: 'msgw-btn msgw-btn-primary',
|
|
995
|
+
disabled: !!busy || !hasToken,
|
|
996
|
+
onClick: handleSmoke,
|
|
997
|
+
},
|
|
998
|
+
busy === 'smoke' ? t('diag.smoke_testing') : t('diag.smoke_btn')
|
|
999
|
+
)
|
|
1000
|
+
),
|
|
1001
|
+
smokeResult
|
|
1002
|
+
? React.createElement(
|
|
1003
|
+
'div',
|
|
1004
|
+
{ className: 'msgw-preview' },
|
|
1005
|
+
`✅ Latency: ${smokeResult.latencyMs} ms | Bot: @${smokeResult.botUsername || 'unknown'} (ID: ${smokeResult.botId})`
|
|
1006
|
+
)
|
|
1007
|
+
: null
|
|
1008
|
+
),
|
|
1009
|
+
|
|
1010
|
+
// Card 3: Telemetry & Gateway Counters
|
|
1011
|
+
status?.stats ? React.createElement(
|
|
1012
|
+
'div',
|
|
1013
|
+
{ className: 'msgw-section-card' },
|
|
1014
|
+
React.createElement('div', { className: 'msgw-section-title' }, t('stats.title')),
|
|
1015
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('stats.desc')),
|
|
1016
|
+
React.createElement(
|
|
1017
|
+
'div',
|
|
1018
|
+
{ className: 'msgw-stat-grid' },
|
|
1019
|
+
React.createElement(
|
|
1020
|
+
'div',
|
|
1021
|
+
{ className: 'msgw-stat-box' },
|
|
1022
|
+
React.createElement('div', { className: 'msgw-stat-val' }, String(status.stats.sent || 0)),
|
|
1023
|
+
React.createElement('div', { className: 'msgw-stat-lbl' }, t('stats.sent'))
|
|
1024
|
+
),
|
|
1025
|
+
React.createElement(
|
|
1026
|
+
'div',
|
|
1027
|
+
{ className: 'msgw-stat-box' },
|
|
1028
|
+
React.createElement('div', { className: 'msgw-stat-val' }, String(status.stats.errors || 0)),
|
|
1029
|
+
React.createElement('div', { className: 'msgw-stat-lbl' }, t('stats.errors'))
|
|
1030
|
+
),
|
|
1031
|
+
React.createElement(
|
|
1032
|
+
'div',
|
|
1033
|
+
{ className: 'msgw-stat-box' },
|
|
1034
|
+
React.createElement('div', { className: 'msgw-stat-val' }, String(status.activeChats || 0)),
|
|
1035
|
+
React.createElement('div', { className: 'msgw-stat-lbl' }, t('stats.active_chats'))
|
|
1036
|
+
),
|
|
1037
|
+
React.createElement(
|
|
1038
|
+
'div',
|
|
1039
|
+
{ className: 'msgw-stat-box' },
|
|
1040
|
+
React.createElement('div', { className: 'msgw-stat-val' }, `${status.uptimeSec || 0}s`),
|
|
1041
|
+
React.createElement('div', { className: 'msgw-stat-lbl' }, t('stats.uptime'))
|
|
1042
|
+
)
|
|
1043
|
+
)
|
|
1044
|
+
) : null,
|
|
1045
|
+
|
|
1046
|
+
// Card 4: Agent & Turn Execution
|
|
1047
|
+
React.createElement(
|
|
1048
|
+
'div',
|
|
1049
|
+
{ className: 'msgw-section-card' },
|
|
1050
|
+
React.createElement('div', { className: 'msgw-section-title' }, `🧠 ${t('agent')}`),
|
|
1051
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('agentSub')),
|
|
1052
|
+
React.createElement(Field, { label: t('provider'), hint: t('providerHint') },
|
|
1053
|
+
React.createElement('input', {
|
|
1054
|
+
className: 'msgw-input',
|
|
1055
|
+
value: cfg.agent?.provider || '',
|
|
1056
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, provider: e.target.value } }),
|
|
1057
|
+
})
|
|
1058
|
+
),
|
|
1059
|
+
React.createElement(Field, { label: t('model'), hint: t('modelHint') },
|
|
1060
|
+
React.createElement('input', {
|
|
1061
|
+
className: 'msgw-input',
|
|
1062
|
+
value: cfg.agent?.model || '',
|
|
1063
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, model: e.target.value } }),
|
|
1064
|
+
})
|
|
1065
|
+
),
|
|
1066
|
+
React.createElement(Field, { label: t('photoOnly') },
|
|
1067
|
+
React.createElement(
|
|
1068
|
+
'select',
|
|
1069
|
+
{
|
|
1070
|
+
className: 'msgw-select',
|
|
1071
|
+
value: cfg.agent?.photoOnlyMode || 'prompt',
|
|
1072
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, photoOnlyMode: e.target.value } }),
|
|
1073
|
+
},
|
|
1074
|
+
React.createElement('option', { value: 'prompt' }, t('photoPrompt')),
|
|
1075
|
+
React.createElement('option', { value: 'run' }, t('photoRun'))
|
|
1076
|
+
)
|
|
1077
|
+
),
|
|
1078
|
+
React.createElement(Field, { label: t('sessionScope') },
|
|
1079
|
+
React.createElement(
|
|
1080
|
+
'select',
|
|
1081
|
+
{
|
|
1082
|
+
className: 'msgw-select',
|
|
1083
|
+
value: cfg.agent?.sessionScope || 'user',
|
|
1084
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, sessionScope: e.target.value } }),
|
|
1085
|
+
},
|
|
1086
|
+
React.createElement('option', { value: 'user' }, t('sessionScopeUser')),
|
|
1087
|
+
React.createElement('option', { value: 'chat' }, t('sessionScopeChat'))
|
|
1088
|
+
)
|
|
1089
|
+
),
|
|
1090
|
+
React.createElement(Field, { label: t('instruction'), hint: t('instructionHint') },
|
|
1091
|
+
React.createElement('textarea', {
|
|
1092
|
+
className: 'msgw-textarea',
|
|
1093
|
+
value: cfg.agent?.instructionPrefix || '',
|
|
1094
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, instructionPrefix: e.target.value } }),
|
|
1095
|
+
rows: 3,
|
|
1096
|
+
placeholder: t('instructionPlaceholder'),
|
|
1097
|
+
})
|
|
1098
|
+
),
|
|
1099
|
+
React.createElement(Field, { label: t('maxMessageLength') },
|
|
1100
|
+
React.createElement('input', {
|
|
1101
|
+
type: 'number',
|
|
1102
|
+
className: 'msgw-input',
|
|
1103
|
+
min: 500,
|
|
1104
|
+
max: 8000,
|
|
1105
|
+
value: cfg.agent?.maxMessageLength ?? 4000,
|
|
1106
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, maxMessageLength: Number(e.target.value) || 4000 } }),
|
|
1107
|
+
})
|
|
1108
|
+
),
|
|
1109
|
+
React.createElement(Field, { label: t('turnTimeout') },
|
|
1110
|
+
React.createElement('input', {
|
|
1111
|
+
type: 'number',
|
|
1112
|
+
className: 'msgw-input',
|
|
1113
|
+
min: 30,
|
|
1114
|
+
max: 3600,
|
|
1115
|
+
value: Math.round((cfg.agent?.turnTimeoutMs ?? 600000) / 1000),
|
|
1116
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, turnTimeoutMs: (Number(e.target.value) || 600) * 1000 } }),
|
|
1117
|
+
})
|
|
1118
|
+
)
|
|
1119
|
+
),
|
|
1120
|
+
|
|
1121
|
+
// Card 5: Media & TTS
|
|
1122
|
+
React.createElement(
|
|
1123
|
+
'div',
|
|
1124
|
+
{ className: 'msgw-section-card' },
|
|
1125
|
+
React.createElement('div', { className: 'msgw-section-title' }, `📎 ${t('media')}`),
|
|
1126
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('mediaSub')),
|
|
1127
|
+
React.createElement(Field, { label: t('maxDocMb') },
|
|
1128
|
+
React.createElement('input', {
|
|
1129
|
+
type: 'number',
|
|
1130
|
+
className: 'msgw-input',
|
|
1131
|
+
min: 1,
|
|
1132
|
+
max: 50,
|
|
1133
|
+
value: bytesToMb(cfg.media?.maxDocBytes),
|
|
1134
|
+
onChange: (e) => setCfg({ ...cfg, media: { ...cfg.media, maxDocBytes: mbToBytes(e.target.value) } }),
|
|
1135
|
+
})
|
|
1136
|
+
),
|
|
1137
|
+
React.createElement(Field, { label: t('maxImageMb') },
|
|
1138
|
+
React.createElement('input', {
|
|
1139
|
+
type: 'number',
|
|
1140
|
+
className: 'msgw-input',
|
|
1141
|
+
min: 1,
|
|
1142
|
+
max: 50,
|
|
1143
|
+
value: bytesToMb(cfg.media?.maxImageBytes),
|
|
1144
|
+
onChange: (e) => setCfg({ ...cfg, media: { ...cfg.media, maxImageBytes: mbToBytes(e.target.value) } }),
|
|
1145
|
+
})
|
|
1146
|
+
),
|
|
1147
|
+
React.createElement(Field, { label: t('maxTextKb') },
|
|
1148
|
+
React.createElement('input', {
|
|
1149
|
+
type: 'number',
|
|
1150
|
+
className: 'msgw-input',
|
|
1151
|
+
min: 16,
|
|
1152
|
+
max: 512,
|
|
1153
|
+
value: Math.round((cfg.media?.maxTextInjectBytes ?? 102400) / 1024),
|
|
1154
|
+
onChange: (e) => setCfg({ ...cfg, media: { ...cfg.media, maxTextInjectBytes: (Number(e.target.value) || 100) * 1024 } }),
|
|
1155
|
+
})
|
|
1156
|
+
),
|
|
1157
|
+
React.createElement('div', { style: { height: '8px' } }),
|
|
1158
|
+
React.createElement('div', { className: 'msgw-section-title' }, `🔊 ${t('tts')}`),
|
|
1159
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('ttsSub')),
|
|
1160
|
+
React.createElement('label', { className: 'msgw-check' },
|
|
1161
|
+
React.createElement('input', {
|
|
1162
|
+
type: 'checkbox',
|
|
1163
|
+
checked: !!cfg.tts?.enabled,
|
|
1164
|
+
onChange: (e) => setCfg({ ...cfg, tts: { ...cfg.tts, enabled: e.target.checked } }),
|
|
1165
|
+
}),
|
|
1166
|
+
t('ttsEnable')
|
|
1167
|
+
),
|
|
1168
|
+
cfg.tts?.enabled ? React.createElement(Field, { label: t('ttsMaxChars') },
|
|
1169
|
+
React.createElement('input', {
|
|
1170
|
+
type: 'number',
|
|
1171
|
+
className: 'msgw-input',
|
|
1172
|
+
min: 100,
|
|
1173
|
+
max: 8000,
|
|
1174
|
+
value: cfg.tts?.maxChars ?? 4000,
|
|
1175
|
+
onChange: (e) => setCfg({ ...cfg, tts: { ...cfg.tts, maxChars: Number(e.target.value) || 4000 } }),
|
|
1176
|
+
})
|
|
1177
|
+
) : null,
|
|
1178
|
+
React.createElement(Field, { label: t('voiceMode') },
|
|
1179
|
+
React.createElement(
|
|
1180
|
+
'select',
|
|
1181
|
+
{
|
|
1182
|
+
className: 'msgw-select',
|
|
1183
|
+
value: cfg.telegram?.voiceMode || 'mirror',
|
|
1184
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, voiceMode: e.target.value } }),
|
|
1185
|
+
},
|
|
1186
|
+
React.createElement('option', { value: 'mirror' }, t('voiceModeMirror')),
|
|
1187
|
+
React.createElement('option', { value: 'always' }, t('voiceModeAlways')),
|
|
1188
|
+
React.createElement('option', { value: 'off' }, t('voiceModeOff'))
|
|
1189
|
+
)
|
|
1190
|
+
)
|
|
1191
|
+
),
|
|
1192
|
+
|
|
1193
|
+
// Card: Discord Gateway
|
|
1194
|
+
React.createElement(
|
|
1195
|
+
'div',
|
|
1196
|
+
{ className: 'msgw-section-card' },
|
|
1197
|
+
React.createElement(
|
|
1198
|
+
'div',
|
|
1199
|
+
{ className: 'msgw-section-title' },
|
|
1200
|
+
React.createElement('span', null, `🎮 ${t('discord')}`),
|
|
1201
|
+
React.createElement(
|
|
1202
|
+
'button',
|
|
1203
|
+
{
|
|
1204
|
+
type: 'button',
|
|
1205
|
+
className: `msgw-btn msgw-btn-mini ${cfg.discord?.enabled ? 'msgw-btn-danger' : 'msgw-btn-primary'}`,
|
|
1206
|
+
disabled: !!busy,
|
|
1207
|
+
onClick: () => save({ discord: { enabled: !cfg.discord?.enabled } }),
|
|
1208
|
+
},
|
|
1209
|
+
cfg.discord?.enabled ? t('disableDiscord') : t('enableDiscord')
|
|
1210
|
+
)
|
|
1211
|
+
),
|
|
1212
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('discordSub')),
|
|
1213
|
+
React.createElement(Field, { label: t('discordToken'), hint: t('discordTokenHint') },
|
|
1214
|
+
React.createElement('input', {
|
|
1215
|
+
type: 'password',
|
|
1216
|
+
className: 'msgw-input',
|
|
1217
|
+
value: discordToken,
|
|
1218
|
+
placeholder: (status?.config?.discord?.botTokenConfigured || cfg.discord?.botTokenConfigured) ? '••••••••••••••••' : 'Discord Bot Token',
|
|
1219
|
+
onChange: (e) => setDiscordToken(e.target.value),
|
|
1220
|
+
})
|
|
1221
|
+
),
|
|
1222
|
+
React.createElement(Field, { label: t('discordWebhook'), hint: t('discordWebhookHint') },
|
|
1223
|
+
React.createElement('input', {
|
|
1224
|
+
className: 'msgw-input',
|
|
1225
|
+
value: cfg.discord?.webhookUrl || '',
|
|
1226
|
+
placeholder: 'https://discord.com/api/webhooks/...',
|
|
1227
|
+
onChange: (e) => setCfg({ ...cfg, discord: { ...cfg.discord, webhookUrl: e.target.value } }),
|
|
1228
|
+
})
|
|
1229
|
+
),
|
|
1230
|
+
React.createElement(Field, { label: t('discordChannels'), hint: t('discordChannelsHint') },
|
|
1231
|
+
React.createElement('textarea', {
|
|
1232
|
+
className: 'msgw-textarea',
|
|
1233
|
+
value: discordChannelsText,
|
|
1234
|
+
onChange: (e) => setDiscordChannelsText(e.target.value),
|
|
1235
|
+
rows: 2,
|
|
1236
|
+
placeholder: '123456789012345678, 987654321098765432',
|
|
1237
|
+
})
|
|
1238
|
+
)
|
|
1239
|
+
),
|
|
1240
|
+
|
|
1241
|
+
// Card: Slack Gateway
|
|
1242
|
+
React.createElement(
|
|
1243
|
+
'div',
|
|
1244
|
+
{ className: 'msgw-section-card' },
|
|
1245
|
+
React.createElement(
|
|
1246
|
+
'div',
|
|
1247
|
+
{ className: 'msgw-section-title' },
|
|
1248
|
+
React.createElement('span', null, `💼 ${t('slack')}`),
|
|
1249
|
+
React.createElement(
|
|
1250
|
+
'button',
|
|
1251
|
+
{
|
|
1252
|
+
type: 'button',
|
|
1253
|
+
className: `msgw-btn msgw-btn-mini ${cfg.slack?.enabled ? 'msgw-btn-danger' : 'msgw-btn-primary'}`,
|
|
1254
|
+
disabled: !!busy,
|
|
1255
|
+
onClick: () => save({ slack: { enabled: !cfg.slack?.enabled } }),
|
|
1256
|
+
},
|
|
1257
|
+
cfg.slack?.enabled ? t('disableSlack') : t('enableSlack')
|
|
1258
|
+
)
|
|
1259
|
+
),
|
|
1260
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('slackSub')),
|
|
1261
|
+
React.createElement(Field, { label: t('slackToken'), hint: t('slackTokenHint') },
|
|
1262
|
+
React.createElement('input', {
|
|
1263
|
+
type: 'password',
|
|
1264
|
+
className: 'msgw-input',
|
|
1265
|
+
value: slackToken,
|
|
1266
|
+
placeholder: (status?.config?.slack?.botTokenConfigured || cfg.slack?.botTokenConfigured) ? '••••••••••••••••' : 'xoxb-...',
|
|
1267
|
+
onChange: (e) => setSlackToken(e.target.value),
|
|
1268
|
+
})
|
|
1269
|
+
),
|
|
1270
|
+
React.createElement(Field, { label: t('slackWebhook'), hint: t('slackWebhookHint') },
|
|
1271
|
+
React.createElement('input', {
|
|
1272
|
+
className: 'msgw-input',
|
|
1273
|
+
value: cfg.slack?.webhookUrl || '',
|
|
1274
|
+
placeholder: 'https://hooks.slack.com/services/...',
|
|
1275
|
+
onChange: (e) => setCfg({ ...cfg, slack: { ...cfg.slack, webhookUrl: e.target.value } }),
|
|
1276
|
+
})
|
|
1277
|
+
),
|
|
1278
|
+
React.createElement(Field, { label: t('slackDefaultChannel'), hint: t('slackDefaultChannelHint') },
|
|
1279
|
+
React.createElement('input', {
|
|
1280
|
+
className: 'msgw-input',
|
|
1281
|
+
value: cfg.slack?.defaultChannel || '',
|
|
1282
|
+
placeholder: '#general or C12345678',
|
|
1283
|
+
onChange: (e) => setCfg({ ...cfg, slack: { ...cfg.slack, defaultChannel: e.target.value } }),
|
|
1284
|
+
})
|
|
1285
|
+
)
|
|
1286
|
+
),
|
|
1287
|
+
|
|
1288
|
+
// Card 6: Pairing Requests
|
|
1289
|
+
React.createElement(
|
|
1290
|
+
'div',
|
|
1291
|
+
{ className: 'msgw-section-card' },
|
|
1292
|
+
React.createElement(
|
|
1293
|
+
'div',
|
|
1294
|
+
{ className: 'msgw-section-title' },
|
|
1295
|
+
React.createElement('span', null, `🔑 ${t('pairing')}`),
|
|
1296
|
+
React.createElement(
|
|
1297
|
+
'button',
|
|
1298
|
+
{
|
|
1299
|
+
type: 'button',
|
|
1300
|
+
className: 'msgw-btn msgw-btn-mini',
|
|
1301
|
+
disabled: !!busy,
|
|
1302
|
+
onClick: loadPairing,
|
|
1303
|
+
},
|
|
1304
|
+
t('pairingRefresh')
|
|
1305
|
+
)
|
|
1306
|
+
),
|
|
1307
|
+
React.createElement('div', { className: 'msgw-section-desc' }, t('pairingSub')),
|
|
1308
|
+
!(pending && pending.length)
|
|
1309
|
+
? React.createElement('div', { className: 'msgw-hint' }, t('pairingEmpty'))
|
|
1310
|
+
: React.createElement(
|
|
1311
|
+
'table',
|
|
1312
|
+
{ className: 'msgw-table' },
|
|
1313
|
+
React.createElement(
|
|
1314
|
+
'tbody',
|
|
1315
|
+
null,
|
|
1316
|
+
pending.map((row) =>
|
|
1317
|
+
React.createElement(
|
|
1318
|
+
'tr',
|
|
1319
|
+
{ key: row.code },
|
|
1320
|
+
React.createElement('td', null, row.username ? `@${row.username}` : '—'),
|
|
1321
|
+
React.createElement('td', null, `ID: ${row.userId}`),
|
|
1322
|
+
React.createElement('td', { style: { fontFamily: 'monospace', fontWeight: 600 } }, row.code),
|
|
1323
|
+
React.createElement(
|
|
1324
|
+
'td',
|
|
1325
|
+
{ style: { textAlign: 'right' } },
|
|
1326
|
+
React.createElement(
|
|
1327
|
+
'button',
|
|
1328
|
+
{
|
|
1329
|
+
type: 'button',
|
|
1330
|
+
className: 'msgw-btn msgw-btn-mini msgw-btn-primary',
|
|
1331
|
+
style: { marginRight: '6px' },
|
|
1332
|
+
disabled: !!busy,
|
|
1333
|
+
onClick: async () => {
|
|
1334
|
+
setBusy('approve')
|
|
1335
|
+
setErr('')
|
|
1336
|
+
try {
|
|
1337
|
+
const res = await fetch(`${ROUTE_PREFIX}/pairing/approve`, {
|
|
1338
|
+
method: 'POST',
|
|
1339
|
+
credentials: 'same-origin',
|
|
1340
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1341
|
+
body: JSON.stringify({ code: row.code }),
|
|
1342
|
+
})
|
|
1343
|
+
const data = await res.json()
|
|
1344
|
+
if (!res.ok || !data.ok) throw new Error(data.error || res.status)
|
|
1345
|
+
await loadPairing()
|
|
1346
|
+
} catch (e) {
|
|
1347
|
+
setErr(String(e.message || e))
|
|
1348
|
+
} finally {
|
|
1349
|
+
setBusy('')
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
t('pairingApprove')
|
|
1354
|
+
),
|
|
1355
|
+
React.createElement(
|
|
1356
|
+
'button',
|
|
1357
|
+
{
|
|
1358
|
+
type: 'button',
|
|
1359
|
+
className: 'msgw-btn msgw-btn-mini msgw-btn-danger',
|
|
1360
|
+
disabled: !!busy,
|
|
1361
|
+
onClick: async () => {
|
|
1362
|
+
setBusy('reject')
|
|
1363
|
+
setErr('')
|
|
1364
|
+
try {
|
|
1365
|
+
const res = await fetch(`${ROUTE_PREFIX}/pairing/reject`, {
|
|
1366
|
+
method: 'POST',
|
|
1367
|
+
credentials: 'same-origin',
|
|
1368
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1369
|
+
body: JSON.stringify({ code: row.code }),
|
|
1370
|
+
})
|
|
1371
|
+
const data = await res.json()
|
|
1372
|
+
if (!res.ok || !data.ok) throw new Error(data.error || res.status)
|
|
1373
|
+
await loadPairing()
|
|
1374
|
+
} catch (e) {
|
|
1375
|
+
setErr(String(e.message || e))
|
|
1376
|
+
} finally {
|
|
1377
|
+
setBusy('')
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
t('pairingReject')
|
|
1382
|
+
)
|
|
1383
|
+
)
|
|
1384
|
+
)
|
|
1385
|
+
)
|
|
1386
|
+
)
|
|
1387
|
+
)
|
|
1388
|
+
),
|
|
1389
|
+
|
|
1390
|
+
// Card 7: Advanced Connection & Webhook
|
|
1391
|
+
React.createElement(
|
|
1392
|
+
'div',
|
|
1393
|
+
{ className: 'msgw-section-card' },
|
|
1394
|
+
React.createElement(
|
|
1395
|
+
'button',
|
|
1396
|
+
{
|
|
1397
|
+
type: 'button',
|
|
1398
|
+
className: 'msgw-btn',
|
|
1399
|
+
style: { width: '100%', justifyContent: 'space-between' },
|
|
1400
|
+
onClick: () => setShowAdvanced((v) => !v),
|
|
1401
|
+
},
|
|
1402
|
+
React.createElement('span', null, showAdvanced ? t('hideAdvanced') : t('showAdvanced')),
|
|
1403
|
+
React.createElement('span', { style: { transform: showAdvanced ? 'rotate(180deg)' : 'none', transition: 'transform .16s' } },
|
|
1404
|
+
React.createElement(Chevron)
|
|
1405
|
+
)
|
|
1406
|
+
),
|
|
1407
|
+
showAdvanced ? React.createElement(
|
|
1408
|
+
'div',
|
|
1409
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '12px', marginTop: '12px' } },
|
|
1410
|
+
React.createElement(Field, { label: t('transport') },
|
|
1411
|
+
React.createElement(
|
|
1412
|
+
'select',
|
|
1413
|
+
{
|
|
1414
|
+
className: 'msgw-select',
|
|
1415
|
+
value: cfg.telegram?.transport || 'poll',
|
|
1416
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, transport: e.target.value } }),
|
|
1417
|
+
},
|
|
1418
|
+
React.createElement('option', { value: 'poll' }, t('transportPoll')),
|
|
1419
|
+
React.createElement('option', { value: 'webhook' }, t('transportWebhook'))
|
|
1420
|
+
)
|
|
1421
|
+
),
|
|
1422
|
+
React.createElement(Field, { label: t('webhookUrl'), hint: t('webhookUrlHint') },
|
|
1423
|
+
React.createElement('input', {
|
|
1424
|
+
className: 'msgw-input',
|
|
1425
|
+
value: cfg.telegram?.webhookUrl || '',
|
|
1426
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, webhookUrl: e.target.value } }),
|
|
1427
|
+
})
|
|
1428
|
+
),
|
|
1429
|
+
React.createElement(Field, {
|
|
1430
|
+
label: React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '8px' } },
|
|
1431
|
+
t('webhookSecret'),
|
|
1432
|
+
cfg.telegram?.webhookSecretConfigured
|
|
1433
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-ok' }, t('webhookSecretConfigured'))
|
|
1434
|
+
: (cfg.telegram?.transport === 'webhook'
|
|
1435
|
+
? React.createElement('span', { className: 'msgw-badge msgw-badge-bad' }, t('webhookSecretRequired'))
|
|
1436
|
+
: null)
|
|
1437
|
+
),
|
|
1438
|
+
hint: t('webhookSecretHint')
|
|
1439
|
+
},
|
|
1440
|
+
React.createElement('input', {
|
|
1441
|
+
type: 'password',
|
|
1442
|
+
className: 'msgw-input',
|
|
1443
|
+
value: webhookSecret,
|
|
1444
|
+
placeholder: cfg.telegram?.webhookSecretConfigured ? '••••••••••••••••' : '',
|
|
1445
|
+
onChange: (e) => setWebhookSecret(e.target.value),
|
|
1446
|
+
})
|
|
1447
|
+
),
|
|
1448
|
+
React.createElement(Field, { label: t('pollTimeout') },
|
|
1449
|
+
React.createElement('input', {
|
|
1450
|
+
type: 'number',
|
|
1451
|
+
className: 'msgw-input',
|
|
1452
|
+
min: 10,
|
|
1453
|
+
max: 60,
|
|
1454
|
+
value: cfg.telegram?.pollTimeoutSeconds ?? 50,
|
|
1455
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, pollTimeoutSeconds: Number(e.target.value) || 50 } }),
|
|
1456
|
+
})
|
|
1457
|
+
),
|
|
1458
|
+
React.createElement(Field, { label: t('pollInterval') },
|
|
1459
|
+
React.createElement('input', {
|
|
1460
|
+
type: 'number',
|
|
1461
|
+
className: 'msgw-input',
|
|
1462
|
+
min: 100,
|
|
1463
|
+
max: 5000,
|
|
1464
|
+
value: cfg.telegram?.pollIntervalMs ?? 500,
|
|
1465
|
+
onChange: (e) => setCfg({ ...cfg, telegram: { ...cfg.telegram, pollIntervalMs: Number(e.target.value) || 500 } }),
|
|
1466
|
+
})
|
|
1467
|
+
),
|
|
1468
|
+
React.createElement(Field, { label: t('idleTimeout') },
|
|
1469
|
+
React.createElement('input', {
|
|
1470
|
+
type: 'number',
|
|
1471
|
+
className: 'msgw-input',
|
|
1472
|
+
min: 300,
|
|
1473
|
+
max: 86400,
|
|
1474
|
+
value: Math.round((cfg.agent?.idleTimeoutMs ?? 3600000) / 1000),
|
|
1475
|
+
onChange: (e) => setCfg({ ...cfg, agent: { ...cfg.agent, idleTimeoutMs: (Number(e.target.value) || 3600) * 1000 } }),
|
|
1476
|
+
})
|
|
1477
|
+
)
|
|
1478
|
+
) : null
|
|
1479
|
+
),
|
|
1480
|
+
|
|
1481
|
+
// Sticky Footer
|
|
1482
|
+
React.createElement(
|
|
1483
|
+
'div',
|
|
1484
|
+
{
|
|
1485
|
+
className: 'msgw-foot',
|
|
1486
|
+
style: {
|
|
1487
|
+
display: 'flex',
|
|
1488
|
+
justifyContent: 'flex-end',
|
|
1489
|
+
gap: '10px',
|
|
1490
|
+
paddingTop: '12px',
|
|
1491
|
+
borderTop: '1px solid var(--dsw-alias-border-l2)',
|
|
1492
|
+
},
|
|
1493
|
+
},
|
|
1494
|
+
React.createElement(
|
|
1495
|
+
'button',
|
|
1496
|
+
{
|
|
1497
|
+
type: 'button',
|
|
1498
|
+
className: 'msgw-btn msgw-btn-primary',
|
|
1499
|
+
disabled: !!busy,
|
|
1500
|
+
onClick: () => save({}),
|
|
1501
|
+
},
|
|
1502
|
+
busy === 'save' ? t('saving') : t('save')
|
|
1503
|
+
)
|
|
1504
|
+
)
|
|
1505
|
+
)
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function PluginCard(props) {
|
|
1509
|
+
const [open, setOpen] = React.useState(false)
|
|
1510
|
+
const t = props?.t || makeT(props?.locale)
|
|
1511
|
+
React.useEffect(() => {
|
|
1512
|
+
ensureCss()
|
|
1513
|
+
}, [])
|
|
1514
|
+
|
|
1515
|
+
return React.createElement(
|
|
1516
|
+
'li',
|
|
1517
|
+
{ className: 'msgw-card msgw-section-card', style: { listStyle: 'none', marginBottom: '12px' } },
|
|
1518
|
+
React.createElement(
|
|
1519
|
+
'button',
|
|
1520
|
+
{
|
|
1521
|
+
type: 'button',
|
|
1522
|
+
className: 'msgw-head',
|
|
1523
|
+
style: {
|
|
1524
|
+
background: 'none',
|
|
1525
|
+
border: 'none',
|
|
1526
|
+
cursor: 'pointer',
|
|
1527
|
+
display: 'flex',
|
|
1528
|
+
alignItems: 'center',
|
|
1529
|
+
width: '100%',
|
|
1530
|
+
padding: 0,
|
|
1531
|
+
textAlign: 'left',
|
|
1532
|
+
},
|
|
1533
|
+
'aria-expanded': open,
|
|
1534
|
+
onClick: () => setOpen((v) => !v),
|
|
1535
|
+
},
|
|
1536
|
+
React.createElement(
|
|
1537
|
+
'div',
|
|
1538
|
+
{ className: 'msgw-headText', style: { flex: 1 } },
|
|
1539
|
+
React.createElement('div', { className: 'msgw-title', style: { fontWeight: 600, fontSize: '15px' } }, t('title')),
|
|
1540
|
+
React.createElement('div', { className: 'msgw-sub', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('description'))
|
|
1541
|
+
),
|
|
1542
|
+
React.createElement(
|
|
1543
|
+
'span',
|
|
1544
|
+
{ className: 'msgw-chev', style: { transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .16s' } },
|
|
1545
|
+
React.createElement(Chevron)
|
|
1546
|
+
)
|
|
1547
|
+
),
|
|
1548
|
+
open
|
|
1549
|
+
? React.createElement(
|
|
1550
|
+
'div',
|
|
1551
|
+
{ className: 'msgw-body', style: { marginTop: '16px' } },
|
|
1552
|
+
React.createElement(
|
|
1553
|
+
ErrorBoundary,
|
|
1554
|
+
null,
|
|
1555
|
+
React.createElement(SettingsPage, { ...props, ctx: (props && props.ctx) || ctx, t })
|
|
1556
|
+
)
|
|
1557
|
+
)
|
|
1558
|
+
: null
|
|
1559
|
+
)
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function refreshMirrorUntilVisible(ctx) {
|
|
1563
|
+
const visible = () => {
|
|
1564
|
+
try {
|
|
1565
|
+
const s = (ctx?.get && ctx.get('lanSettings')) || (ctx?.get && ctx.get('settingsScope')) || ctx?.settingsScope
|
|
1566
|
+
const view = s?.describe?.()?.getSnapshot?.()?.view
|
|
1567
|
+
return !!view && Array.isArray(view.namespaces) && view.namespaces.some((row) => row.ns === NS)
|
|
1568
|
+
} catch (_) {
|
|
1569
|
+
return false
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (visible()) return () => {}
|
|
1573
|
+
let tries = 0
|
|
1574
|
+
const timer = setInterval(() => {
|
|
1575
|
+
if (visible() || tries >= 15) { clearInterval(timer); return }
|
|
1576
|
+
tries += 1
|
|
1577
|
+
try {
|
|
1578
|
+
const s = (ctx?.get && ctx.get('lanSettings')) || (ctx?.get && ctx.get('settingsScope')) || ctx?.settingsScope
|
|
1579
|
+
s?.describe?.()?.load?.()
|
|
1580
|
+
} catch (_) {
|
|
1581
|
+
/* safe fallback if settingsScope is not yet attached to container */
|
|
1582
|
+
}
|
|
1583
|
+
}, 1000)
|
|
1584
|
+
return () => clearInterval(timer)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function apply(ctx) {
|
|
1588
|
+
try {
|
|
1589
|
+
if (typeof ctx.effect === 'function') {
|
|
1590
|
+
ctx.effect(() => {
|
|
1591
|
+
try {
|
|
1592
|
+
const loc = ctx?.get?.('locale') || ctx?.locale
|
|
1593
|
+
if (loc?.register) {
|
|
1594
|
+
loc.register(NS, { en, zh })
|
|
1595
|
+
}
|
|
1596
|
+
} catch (taken) {
|
|
1597
|
+
console.warn('[dsh-messenger-gateway] dictionary already registered:', taken && taken.message)
|
|
1598
|
+
}
|
|
1599
|
+
}, 'dsh-messenger-gateway: dictionaries')
|
|
1600
|
+
} else {
|
|
1601
|
+
const loc = ctx?.get?.('locale') || ctx?.locale
|
|
1602
|
+
if (loc?.register) {
|
|
1603
|
+
loc.register(NS, { en, zh })
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
console.warn('[dsh-messenger-gateway] dictionary registration error:', err && err.message)
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (typeof ctx.effect === 'function') {
|
|
1611
|
+
ctx.effect(
|
|
1612
|
+
() => refreshMirrorUntilVisible(ctx),
|
|
1613
|
+
'dsh-messenger-gateway: re-read the settings mirror until our namespace appears',
|
|
1614
|
+
)
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function registerSlotWhenReady(slotName, registerFn) {
|
|
1618
|
+
const slots = ctx?.get?.('slots') || ctx?.slots
|
|
1619
|
+
if (!slots) return
|
|
1620
|
+
if (typeof slots.inject === 'function') {
|
|
1621
|
+
try {
|
|
1622
|
+
slots.inject(slotName, () => {
|
|
1623
|
+
try {
|
|
1624
|
+
return registerFn()
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
console.warn('[dsh-messenger-gateway] Error registering slot ' + slotName + ':', err)
|
|
1627
|
+
}
|
|
1628
|
+
})
|
|
1629
|
+
return
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
console.warn('[dsh-messenger-gateway] Failed to inject slot ' + slotName + ':', err)
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
if (typeof slots.register === 'function') {
|
|
1635
|
+
try {
|
|
1636
|
+
registerFn()
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
console.warn('[dsh-messenger-gateway] Failed direct registration for ' + slotName + ':', err)
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
registerSlotWhenReady('settings.plugin.item', () => {
|
|
1644
|
+
const slots = ctx?.get?.('slots') || ctx?.slots
|
|
1645
|
+
return slots.register(
|
|
1646
|
+
{
|
|
1647
|
+
name: 'settings.plugin.item',
|
|
1648
|
+
key: NS,
|
|
1649
|
+
locale: NS,
|
|
1650
|
+
inject: () => ({ ctx }),
|
|
1651
|
+
},
|
|
1652
|
+
(props) => React.createElement(ErrorBoundary, null, React.createElement(PluginCard, { ...props, ctx: (props && props.ctx) || ctx }))
|
|
1653
|
+
)
|
|
1654
|
+
})
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'] }
|
|
1658
|
+
return module.exports
|
|
1659
|
+
},
|
|
1660
|
+
})
|