xshat-lite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,620 @@
1
+ const streamCompletionScript = `
2
+ ({ selectors }) => {
3
+ const pickLast = (list) => {
4
+ for (const selector of list) {
5
+ const nodes = document.querySelectorAll(selector);
6
+ if (nodes.length > 0) {
7
+ return nodes[nodes.length - 1];
8
+ }
9
+ }
10
+ return null;
11
+ };
12
+
13
+ const hasAny = (list, root = document) => {
14
+ if (!Array.isArray(list) || list.length === 0) {
15
+ return false;
16
+ }
17
+
18
+ return list.some((selector) => {
19
+ try {
20
+ return !!root.querySelector(selector);
21
+ } catch {
22
+ return false;
23
+ }
24
+ });
25
+ };
26
+
27
+ const current = pickLast(selectors.response);
28
+ const text = current?.textContent || '';
29
+ const compactText = text.replace(/\\s+/g, ' ').trim();
30
+ const transient = (selectors.interimTexts || []).some((marker) =>
31
+ compactText.includes(marker)
32
+ );
33
+ const pending = hasAny(selectors.pending) || transient;
34
+ const done =
35
+ !transient &&
36
+ (hasAny(selectors.done, current || document) || (!pending && text.length > 0));
37
+
38
+ return { text, pending, done, transient };
39
+ }
40
+ `;
41
+
42
+ const staticCompletionScript = `
43
+ ({ selectors }) => {
44
+ const pickLast = (list) => {
45
+ for (const selector of list) {
46
+ const nodes = document.querySelectorAll(selector);
47
+ if (nodes.length > 0) {
48
+ return nodes[nodes.length - 1];
49
+ }
50
+ }
51
+ return null;
52
+ };
53
+
54
+ const current = pickLast(selectors.response);
55
+ const text = current?.textContent || '';
56
+
57
+ return { text, pending: false, done: text.length > 0 };
58
+ }
59
+ `;
60
+
61
+ export default {
62
+ defaultProvider: 'gemini',
63
+ providers: [
64
+ {
65
+ id: 'api-openai',
66
+ name: 'API Model',
67
+ description: 'OpenAI-compatible API 提供方,可接 OpenAI / OpenRouter / 本地兼容接口',
68
+ status: 'working',
69
+ enabled: true,
70
+ transport: 'api',
71
+ api: {
72
+ envPrefix: 'OPENAI',
73
+ baseUrl: 'https://api.openai.com/v1',
74
+ model: 'gpt-4o-mini'
75
+ }
76
+ },
77
+ {
78
+ id: 'api-deepseek',
79
+ name: 'DeepSeek API',
80
+ description: 'DeepSeek 官方 API',
81
+ status: 'working',
82
+ enabled: true,
83
+ transport: 'api',
84
+ api: {
85
+ envPrefix: 'DEEPSEEK',
86
+ baseUrl: 'https://api.deepseek.com',
87
+ model: 'deepseek-v4-flash',
88
+ responseFormat: {
89
+ type: 'json_object'
90
+ }
91
+ }
92
+ },
93
+ {
94
+ id: 'duckai',
95
+ name: 'Duck.ai',
96
+ url: 'https://duck.ai/',
97
+ description: 'DuckDuckGo 免登录 AI 聊天',
98
+ status: 'working',
99
+ enabled: true,
100
+ steps: {
101
+ newChat: {
102
+ selectors: [
103
+ 'a[href*="/chat/new"]',
104
+ 'button[aria-label*="New Chat"]',
105
+ 'button[aria-label*="New chat"]',
106
+ 'button:has-text("New Chat")'
107
+ ]
108
+ },
109
+ input: {
110
+ selectors: [
111
+ 'form[data-chat-footer="true"] textarea[name="user-prompt"]',
112
+ '[data-testid="duckai-chat-input"] textarea[name="user-prompt"]',
113
+ 'textarea[placeholder*="回复"]',
114
+ 'textarea[placeholder*="Ask"]',
115
+ 'textarea'
116
+ ]
117
+ },
118
+ send: {
119
+ selectors: [
120
+ 'form[data-chat-footer="true"] button[aria-label="发送"]',
121
+ '[data-testid="duckai-chat-input"] button[aria-label="发送"]',
122
+ 'button[aria-label*="Send"]',
123
+ 'button[type="submit"]'
124
+ ]
125
+ },
126
+ read: {
127
+ selectors: [
128
+ '[data-activeresponse="true"] .space-y-4',
129
+ '[id*="assistant-message"] .space-y-4',
130
+ '.PBQZNIcKgp0FJ_yxBVaB .space-y-4',
131
+ '[data-activeresponse="true"] .VrBPSncUavA1d7C9kAc5',
132
+ '[id*="assistant-message"] .VrBPSncUavA1d7C9kAc5',
133
+ 'main article',
134
+ '[data-testid="chat-message-text"]',
135
+ '[data-role="assistant-message"]'
136
+ ]
137
+ },
138
+ complete: {
139
+ mode: 'script',
140
+ script: streamCompletionScript,
141
+ selectors: {
142
+ response: [
143
+ '[data-activeresponse="true"] .space-y-4',
144
+ '[id*="assistant-message"] .space-y-4',
145
+ '.PBQZNIcKgp0FJ_yxBVaB .space-y-4',
146
+ '[data-activeresponse="true"] .VrBPSncUavA1d7C9kAc5',
147
+ '[id*="assistant-message"] .VrBPSncUavA1d7C9kAc5',
148
+ 'main article',
149
+ '[data-testid="chat-message-text"]',
150
+ '[data-role="assistant-message"]'
151
+ ],
152
+ pending: [
153
+ 'button[aria-label="停止生成"]:not([disabled])',
154
+ '[data-activeresponse="true"]'
155
+ ],
156
+ interimTexts: ['正在生成回复', 'Generating a response']
157
+ }
158
+ }
159
+ }
160
+ },
161
+ {
162
+ id: 'huggingchat',
163
+ name: 'HuggingChat',
164
+ url: 'https://huggingface.co/chat/',
165
+ description: 'Hugging Face 免登录聊天入口',
166
+ status: 'experimental',
167
+ enabled: false,
168
+ steps: {
169
+ preflight: {
170
+ clickSelectors: [
171
+ 'button:has-text("Start chatting")',
172
+ '[role="dialog"] button:has-text("Start chatting")'
173
+ ],
174
+ waitAfterMs: 1000
175
+ },
176
+ input: {
177
+ selectors: ['textarea[placeholder*="Ask anything"]', 'textarea']
178
+ },
179
+ send: {
180
+ selectors: ['button[aria-label="Send message"]', 'button[type="submit"]']
181
+ },
182
+ read: {
183
+ selectors: ['[data-testid="message-content"]', '[role="article"]', '.prose']
184
+ },
185
+ complete: {
186
+ mode: 'script',
187
+ script: streamCompletionScript,
188
+ selectors: {
189
+ response: ['[data-testid="message-content"]', '[role="article"]', '.prose'],
190
+ pending: ['button[aria-label*="Stop"]', '[data-state="streaming"]'],
191
+ done: ['button[aria-label="Copy message"]', 'button[aria-label*="Like"]']
192
+ }
193
+ }
194
+ }
195
+ },
196
+ {
197
+ id: 'deepai',
198
+ name: 'DeepAI Chat',
199
+ url: 'https://deepai.org/chat',
200
+ description: 'DeepAI 公开聊天页',
201
+ status: 'working',
202
+ enabled: true,
203
+ steps: {
204
+ newChat: {
205
+ selectors: [
206
+ 'a[href*="/chat/new"]',
207
+ 'button[data-testid*="new-chat"]',
208
+ 'button[aria-label*="新建对话"]',
209
+ 'button[aria-label*="New chat"]',
210
+ 'button:has-text("新对话")',
211
+ 'button:has-text("新建对话")'
212
+ ]
213
+ },
214
+ input: {
215
+ selectors: ['#persistentChatbox', 'textarea.chatbox']
216
+ },
217
+ send: {
218
+ selectors: ['#chatSubmitButton', 'button[onclick*="chatSubmit"]']
219
+ },
220
+ read: {
221
+ selectors: ['.chat-response', '.outputBox', '.messages .message']
222
+ },
223
+ complete: {
224
+ mode: 'script',
225
+ script: streamCompletionScript,
226
+ selectors: {
227
+ response: ['.chat-response', '.outputBox', '.messages .message'],
228
+ pending: ['#chatStopButton:not(.hidden)', '.typing-animation', '.response-loading'],
229
+ done: ['.copy-button', '.results-tab']
230
+ }
231
+ }
232
+ }
233
+ },
234
+ {
235
+ id: 'deepseek',
236
+ name: 'DeepSeek',
237
+ url: 'https://chat.deepseek.com/',
238
+ description: 'DeepSeek 官方聊天页',
239
+ status: 'working',
240
+ enabled: true,
241
+ steps: {
242
+ newChat: {
243
+ selectors: [
244
+ 'a[href="/app"]',
245
+ 'button[aria-label*="新建对话"]',
246
+ 'button[aria-label*="New chat"]',
247
+ 'button:has-text("新建对话")',
248
+ 'button:has-text("New chat")'
249
+ ]
250
+ },
251
+ input: {
252
+ selectors: [
253
+ '[data-testid="chat-input"]',
254
+ '.ds-textarea textarea',
255
+ '.ds-markdown-editor textarea',
256
+ '[data-testid="chat-input"] textarea',
257
+ 'textarea[placeholder*="Message"]',
258
+ 'textarea[placeholder*="发送"]',
259
+ 'textarea[placeholder*="给 DeepSeek 发送消息"]',
260
+ 'textarea[placeholder*="输入"]',
261
+ 'textarea',
262
+ '[contenteditable="true"]',
263
+ '[role="textbox"]'
264
+ ]
265
+ },
266
+ send: {
267
+ selectors: [
268
+ 'button[data-testid="chat-send-button"]',
269
+ 'button[aria-label*="发送消息"]',
270
+ 'button:has-text("发送")',
271
+ 'button[aria-label*="Send"]',
272
+ 'button[aria-label*="发送"]',
273
+ 'button[type="submit"]'
274
+ ]
275
+ },
276
+ read: {
277
+ selectors: [
278
+ 'main article',
279
+ 'article',
280
+ '[role="article"]',
281
+ '.markdown',
282
+ '.prose',
283
+ '.message-content',
284
+ '.chat-message',
285
+ '[data-testid*="message"]'
286
+ ]
287
+ },
288
+ complete: {
289
+ mode: 'script',
290
+ script: streamCompletionScript,
291
+ selectors: {
292
+ response: [
293
+ 'main article',
294
+ 'article',
295
+ '[role="article"]',
296
+ '.markdown',
297
+ '.prose',
298
+ '.message-content',
299
+ '.chat-message',
300
+ '[data-testid*="message"]'
301
+ ],
302
+ pending: [
303
+ 'button:has-text("停止")',
304
+ 'button[aria-label*="Stop"]',
305
+ 'button[aria-label*="停止"]',
306
+ 'button[data-testid="chat-stop-button"]',
307
+ '[data-state="streaming"]',
308
+ '.typing',
309
+ '.loading',
310
+ '.ds-loading',
311
+ '.ds-thinking',
312
+ '[class*="typing"]',
313
+ '[class*="loading"]'
314
+ ],
315
+ done: [
316
+ 'button:has-text("复制")',
317
+ 'button[aria-label*="Copy"]',
318
+ 'button[aria-label*="复制"]',
319
+ 'button[data-testid*="copy"]'
320
+ ],
321
+ interimTexts: [
322
+ '思考中',
323
+ '正在思考',
324
+ '联网搜索中',
325
+ '正在搜索',
326
+ 'Searching',
327
+ 'Thinking'
328
+ ]
329
+ }
330
+ }
331
+ }
332
+ },
333
+ {
334
+ id: 'qwen',
335
+ name: 'Qwen',
336
+ url: 'https://chat.qwen.ai/',
337
+ description: 'Qwen Studio 公开聊天页',
338
+ status: 'experimental',
339
+ enabled: false,
340
+ steps: {
341
+ preflight: {
342
+ clickSelectors: [
343
+ 'button:has-text("保持注销状态")',
344
+ '[role="dialog"] button:has-text("保持注销状态")'
345
+ ],
346
+ waitAfterMs: 1000
347
+ },
348
+ input: {
349
+ selectors: [
350
+ 'textarea.message-input-textarea',
351
+ 'textarea[placeholder*="How can I help"]',
352
+ 'textarea'
353
+ ]
354
+ },
355
+ send: {
356
+ selectors: [
357
+ '.message-input-right-button button',
358
+ 'button[aria-label*="Send"]',
359
+ 'button[type="submit"]'
360
+ ]
361
+ },
362
+ read: {
363
+ selectors: [
364
+ 'main article',
365
+ 'article',
366
+ '[role="article"]',
367
+ '.markdown',
368
+ '.prose',
369
+ '.message-content',
370
+ '.chat-message'
371
+ ]
372
+ },
373
+ complete: {
374
+ mode: 'script',
375
+ script: staticCompletionScript,
376
+ selectors: {
377
+ response: [
378
+ 'main article',
379
+ 'article',
380
+ '[role="article"]',
381
+ '.markdown',
382
+ '.prose',
383
+ '.message-content',
384
+ '.chat-message'
385
+ ]
386
+ }
387
+ }
388
+ }
389
+ },
390
+ {
391
+ id: 'you',
392
+ name: 'You.com',
393
+ url: 'https://you.com/chat?chatMode=default',
394
+ description: 'You.com 公开聊天页',
395
+ status: 'experimental',
396
+ enabled: false,
397
+ steps: {
398
+ input: {
399
+ selectors: [
400
+ 'textarea[placeholder*="Message"]',
401
+ 'textarea[placeholder*="Ask"]',
402
+ 'textarea',
403
+ '[contenteditable="true"]'
404
+ ]
405
+ },
406
+ send: {
407
+ selectors: [
408
+ 'button[aria-label*="Send"]',
409
+ 'button[type="submit"]'
410
+ ]
411
+ },
412
+ read: {
413
+ selectors: [
414
+ 'main article',
415
+ 'article',
416
+ '[role="article"]',
417
+ '.markdown',
418
+ '.prose',
419
+ '.message-content',
420
+ '.chat-message',
421
+ '[data-testid*="message"]'
422
+ ]
423
+ },
424
+ complete: {
425
+ mode: 'script',
426
+ script: staticCompletionScript,
427
+ selectors: {
428
+ response: [
429
+ 'main article',
430
+ 'article',
431
+ '[role="article"]',
432
+ '.markdown',
433
+ '.prose',
434
+ '.message-content',
435
+ '.chat-message',
436
+ '[data-testid*="message"]'
437
+ ]
438
+ }
439
+ }
440
+ }
441
+ },
442
+ {
443
+ id: 'copilot',
444
+ name: 'Microsoft Copilot',
445
+ url: 'https://copilot.microsoft.com/',
446
+ description: 'Copilot 公开聊天页',
447
+ status: 'experimental',
448
+ enabled: false,
449
+ steps: {
450
+ input: {
451
+ selectors: ['#userInput', 'textarea[placeholder*="Message Copilot"]']
452
+ },
453
+ send: {
454
+ selectors: ['button[aria-label*="Send"]', 'button[data-testid="composer-submit-button"]']
455
+ },
456
+ read: {
457
+ selectors: ['[data-content="ai-message"]', '[data-testid="conversation-turn-activity"]', '[data-testid="rich-text-content"]']
458
+ },
459
+ complete: {
460
+ mode: 'script',
461
+ script: streamCompletionScript,
462
+ selectors: {
463
+ response: ['[data-content="ai-message"]', '[data-testid="conversation-turn-activity"]', '[data-testid="rich-text-content"]'],
464
+ pending: ['button[aria-label*="Stop"]', '[data-testid="stop-responding-button"]'],
465
+ done: ['button[aria-label*="Copy"]', '[data-testid="feedback-controls"]']
466
+ }
467
+ }
468
+ }
469
+ },
470
+ {
471
+ id: 'chatgpt',
472
+ name: 'ChatGPT',
473
+ url: 'https://chatgpt.com/',
474
+ description: 'ChatGPT 访客页,地区和配额可能变化',
475
+ status: 'experimental',
476
+ enabled: false,
477
+ steps: {
478
+ input: {
479
+ selectors: ['#prompt-textarea', 'textarea[placeholder*="Message"]']
480
+ },
481
+ send: {
482
+ selectors: ['button[data-testid="send-button"]', 'button[aria-label*="Send prompt"]']
483
+ },
484
+ read: {
485
+ selectors: ['[data-message-author-role="assistant"]', '[data-testid^="conversation-turn-"]']
486
+ },
487
+ complete: {
488
+ mode: 'script',
489
+ script: streamCompletionScript,
490
+ selectors: {
491
+ response: ['[data-message-author-role="assistant"]', '[data-testid^="conversation-turn-"]'],
492
+ pending: ['.result-streaming', 'button[data-testid="stop-button"]'],
493
+ done: ['button[data-testid="copy-turn-action-button"]', '[data-testid="good-response-turn-action-button"]']
494
+ }
495
+ }
496
+ }
497
+ },
498
+ {
499
+ id: 'gemini',
500
+ name: 'Gemini',
501
+ url: 'https://gemini.google.com/app',
502
+ description: 'Gemini,通常需要登录',
503
+ status: 'working',
504
+ enabled: true,
505
+ steps: {
506
+ input: {
507
+ selectors: [
508
+ '.simplified-input .ql-editor',
509
+ 'rich-textarea .ql-editor',
510
+ 'rich-textarea [contenteditable="true"]',
511
+ 'rich-textarea [role="textbox"]',
512
+ '[role="textbox"][contenteditable="true"]',
513
+ '[contenteditable="true"][aria-label*="Gemini"]',
514
+ '[contenteditable="true"][aria-label*="message"]',
515
+ '[contenteditable="true"][data-placeholder]',
516
+ 'textarea[aria-label*="Gemini"]',
517
+ 'textarea[placeholder*="Message"]',
518
+ 'textarea[placeholder*="输入"]'
519
+ ]
520
+ },
521
+ send: {
522
+ selectors: ['[data-test-id="send-button-container"] button', 'button[aria-label*="Send message"]']
523
+ },
524
+ read: {
525
+ selectors: ['model-response', '[data-response-id]']
526
+ },
527
+ complete: {
528
+ mode: 'script',
529
+ script: streamCompletionScript,
530
+ selectors: {
531
+ response: ['model-response', '[data-response-id]'],
532
+ pending: ['pending-request'],
533
+ done: ['freemium-rag-disclaimer'],
534
+ interimTexts: [
535
+ '正在搜索网络',
536
+ '搜索网络',
537
+ '搜索网页',
538
+ '正在搜索网页',
539
+ 'Searching the web',
540
+ 'Searching for webpages'
541
+ ]
542
+ }
543
+ }
544
+ }
545
+ }
546
+ ],
547
+ api: {
548
+ baseUrl: 'https://api.openai.com/v1',
549
+ apiKey: '',
550
+ model: 'gpt-4o-mini'
551
+ },
552
+ providersRuntime: {
553
+ mode: 'web-first',
554
+ autoFallback: false,
555
+ includeApiFallback: true,
556
+ sendRetryCount: 2,
557
+ responseRetryCount: 2,
558
+ retryBackoffMs: 1500
559
+ },
560
+ browser: {
561
+ headless: true,
562
+ humanize: true,
563
+ userDataDir: './runtime/profiles/xshat',
564
+ gotoTimeout: 30000,
565
+ autoRecover: false,
566
+ allowVisibleBrowser: false,
567
+ shareAuthState: true,
568
+ observationLimit: 30,
569
+ actionRetryCount: 3,
570
+ actionRetryDelayMs: 350
571
+ },
572
+ chat: {
573
+ pollInterval: 300,
574
+ inputTimeout: 300,
575
+ stableThreshold: 3,
576
+ responseTimeout: 120000,
577
+ idleBeforeSendTimeout: 15000,
578
+ idleStableThreshold: 2,
579
+ idleAfterCompleteMs: 1200
580
+ },
581
+ session: {
582
+ dir: './runtime/sessions',
583
+ compressThreshold: 12,
584
+ keepRecentMessages: 6,
585
+ maxSummaryChars: 1200,
586
+ maxMessageChars: 160
587
+ },
588
+ tasks: {
589
+ dir: './runtime/tasks',
590
+ pollInterval: 1500,
591
+ maxHistory: 60,
592
+ maxMessages: 20
593
+ },
594
+ storage: {
595
+ archiveDir: './runtime/archive',
596
+ cleanupOnStart: true,
597
+ sessionRetention: 200,
598
+ taskRetention: 200,
599
+ removeTempFilesOnStart: true,
600
+ profileWarningSizeMb: 1024
601
+ },
602
+ logger: {
603
+ dir: './runtime/logs',
604
+ filename: 'chat_history.txt',
605
+ maxSize: 10 * 1024 * 1024,
606
+ maxFiles: 5
607
+ },
608
+ agent: {
609
+ enabled: true,
610
+ autoExecuteCommands: false,
611
+ allowShell: true,
612
+ allowFileWrite: true,
613
+ maxAutoSteps: 4,
614
+ longRunning: false,
615
+ multiAgent: true
616
+ },
617
+ debug: {
618
+ enabled: false
619
+ }
620
+ };